diff options
Diffstat (limited to 'sw/source/filter/html')
43 files changed, 47555 insertions, 0 deletions
diff --git a/sw/source/filter/html/README b/sw/source/filter/html/README new file mode 100644 index 0000000000..7b16fb0838 --- /dev/null +++ b/sw/source/filter/html/README @@ -0,0 +1,42 @@ +This filter is used when the "HTML (StarWriter)" filter is invoked. + +Import options: + +- FilterOptions: string + + - xhtmlns=reqif-xhtml: actives the ReqIF mode + +- AllowedRTFOLEMimeTypes: sequence<string> + + In case an (UNO) client wants to limit the accepted set of MIME types for + OLE objects in ReqIF mode, that's possible via this parameter. + + Any MIME type is accepted by default. + +Export options: + +- FilterOptions: string + + - SkipImages: skips images + + - SkipHeaderFooter: skips the header and footer + + - EmbedImages: inlines images + + - XHTML: activates the XHTML mode + + - xhtmlns=reqif-xhtml: actives the ReqIF mode + +- RTFOLEMimeType: string + + Defines what MIME type to use for OLE objects in ReqIF mode, defaults to text/rtf. + +- ExportImagesAsOLE: boolean + + Defines if images should be exported as OLE objects or bitmaps, defaults to + false. + +- ShapeDPI: long (32bit signed int) + + Defines a custom DPI when converting vector shapes to bitmaps, defaults to + the system DPI (96 when not on HiDPI). diff --git a/sw/source/filter/html/SwAppletImpl.cxx b/sw/source/filter/html/SwAppletImpl.cxx new file mode 100644 index 0000000000..9dbca27483 --- /dev/null +++ b/sw/source/filter/html/SwAppletImpl.cxx @@ -0,0 +1,194 @@ +/* -*- 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 <SwAppletImpl.hxx> +#include <svtools/htmlkywd.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> + +#include <comphelper/embeddedobjectcontainer.hxx> +#include <comphelper/classids.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <o3tl/string_view.hxx> +#include <svtools/embedhlp.hxx> +#include <tools/globname.hxx> +#include <tools/urlobj.hxx> +#include <hintids.hxx> + +using namespace com::sun::star; + +SwHtmlOptType SwApplet_Impl::GetOptionType( std::u16string_view rName, bool bApplet ) +{ + SwHtmlOptType nType = bApplet ? SwHtmlOptType::PARAM : SwHtmlOptType::TAG; + + switch( rName[0] ) + { + case 'A': + case 'a': + if( o3tl::equalsIgnoreAsciiCase( rName, OOO_STRING_SVTOOLS_HTML_O_align ) || + o3tl::equalsIgnoreAsciiCase( rName, OOO_STRING_SVTOOLS_HTML_O_alt ) ) + nType = SwHtmlOptType::IGNORE; + else if( bApplet && + (rName == u"ARCHIVE" || rName == u"ARCHIVES" ) ) + nType = SwHtmlOptType::TAG; + break; + case 'C': + case 'c': + if( o3tl::equalsIgnoreAsciiCase( rName, OOO_STRING_SVTOOLS_HTML_O_class ) || + (bApplet && (o3tl::equalsIgnoreAsciiCase( rName, OOO_STRING_SVTOOLS_HTML_O_code ) || + o3tl::equalsIgnoreAsciiCase( rName, OOO_STRING_SVTOOLS_HTML_O_codebase ))) ) + nType = SwHtmlOptType::IGNORE; + break; + case 'H': + case 'h': + if( o3tl::equalsIgnoreAsciiCase( rName, OOO_STRING_SVTOOLS_HTML_O_height ) ) + nType = SwHtmlOptType::SIZE; + else if( o3tl::equalsIgnoreAsciiCase( rName, OOO_STRING_SVTOOLS_HTML_O_hspace ) || + (!bApplet && o3tl::equalsIgnoreAsciiCase( rName, OOO_STRING_SW_HTML_O_Hidden )) ) + nType = SwHtmlOptType::IGNORE; + break; + case 'I': + case 'i': + if( o3tl::equalsIgnoreAsciiCase( rName, OOO_STRING_SVTOOLS_HTML_O_id ) ) + nType = SwHtmlOptType::IGNORE; + break; + case 'M': + case 'm': + if( bApplet && o3tl::equalsIgnoreAsciiCase( rName, OOO_STRING_SVTOOLS_HTML_O_mayscript ) ) + nType = SwHtmlOptType::IGNORE; + break; + case 'N': + case 'n': + if( o3tl::equalsIgnoreAsciiCase( rName, OOO_STRING_SVTOOLS_HTML_O_name ) ) + nType = SwHtmlOptType::IGNORE; + break; + case 'O': + case 'o': + if( bApplet && rName == u"OBJECT" ) + nType = SwHtmlOptType::TAG; + break; + case 'S': + case 's': + if( o3tl::equalsIgnoreAsciiCase( rName, OOO_STRING_SVTOOLS_HTML_O_style ) || + (!bApplet && o3tl::equalsIgnoreAsciiCase( rName, OOO_STRING_SVTOOLS_HTML_O_src )) ) + nType = SwHtmlOptType::IGNORE; + break; + case 'T': + case 't': + if( !bApplet && o3tl::equalsIgnoreAsciiCase( rName, OOO_STRING_SVTOOLS_HTML_O_type ) ) + nType = SwHtmlOptType::IGNORE; + break; + case 'V': + case 'v': + if( o3tl::equalsIgnoreAsciiCase( rName, OOO_STRING_SVTOOLS_HTML_O_vspace ) ) + nType = SwHtmlOptType::IGNORE; + break; + case 'W': + case 'w': + if( o3tl::equalsIgnoreAsciiCase( rName, OOO_STRING_SVTOOLS_HTML_O_width ) ) + nType = SwHtmlOptType::SIZE; + break; + } + + return nType; +} +SwApplet_Impl::SwApplet_Impl( SfxItemPool& rPool ) : + m_aItemSet( rPool, svl::Items<RES_FRMATR_BEGIN, RES_FRMATR_END-1> ) +{ +} + +void SwApplet_Impl::CreateApplet( const OUString& rCode, const OUString& rName, + bool bMayScript, const OUString& rCodeBase, + std::u16string_view rDocumentBaseURL ) +{ + comphelper::EmbeddedObjectContainer aCnt; + OUString aName; + + // create Applet; it will be in running state + m_xApplet = aCnt.CreateEmbeddedObject( SvGlobalName( SO3_APPLET_CLASSID ).GetByteSequence(), aName ); + (void)::svt::EmbeddedObjectRef::TryRunningState( m_xApplet ); + + INetURLObject aUrlBase(rDocumentBaseURL); + aUrlBase.removeSegment(); + + OUString sDocBase = aUrlBase.GetMainURL(INetURLObject::DecodeMechanism::NONE); + uno::Reference < beans::XPropertySet > xSet( m_xApplet->getComponent(), uno::UNO_QUERY ); + if ( xSet.is() ) + { + xSet->setPropertyValue("AppletCode", uno::Any( rCode ) ); + xSet->setPropertyValue("AppletName", uno::Any( rName ) ); + xSet->setPropertyValue("AppletIsScript", uno::Any( bMayScript ) ); + xSet->setPropertyValue("AppletDocBase", uno::Any( sDocBase ) ); + if ( !rCodeBase.isEmpty() ) + xSet->setPropertyValue("AppletCodeBase", uno::Any( rCodeBase ) ); + else + xSet->setPropertyValue("AppletCodeBase", uno::Any( sDocBase ) ); + } +} +#if HAVE_FEATURE_JAVA +bool SwApplet_Impl::CreateApplet( std::u16string_view rBaseURL ) +{ + OUString aCode, aName, aCodeBase; + bool bMayScript = false; + + size_t nArgCount = m_aCommandList.size(); + for( size_t i = 0; i < nArgCount; i++ ) + { + const SvCommand& rArg = m_aCommandList[i]; + const OUString& rName = rArg.GetCommand(); + if( rName.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_O_code ) ) + aCode = rArg.GetArgument(); + else if( rName.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_O_codebase ) ) + aCodeBase = INetURLObject::GetAbsURL( rBaseURL, rArg.GetArgument() ); + else if( rName.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_O_name ) ) + aName = rArg.GetArgument(); + else if( rName.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_O_mayscript ) ) + bMayScript = true; + } + + if( aCode.isEmpty() ) + return false; + CreateApplet( aCode, aName, bMayScript, aCodeBase, rBaseURL ); + return true; +} +#endif + +SwApplet_Impl::~SwApplet_Impl() +{ +} +void SwApplet_Impl::FinishApplet() +{ + uno::Reference < beans::XPropertySet > xSet( m_xApplet->getComponent(), uno::UNO_QUERY ); + if ( xSet.is() ) + { + uno::Sequence < beans::PropertyValue > aProps; + m_aCommandList.FillSequence( aProps ); + xSet->setPropertyValue("AppletCommands", uno::Any( aProps ) ); + } +} + +#if HAVE_FEATURE_JAVA +void SwApplet_Impl::AppendParam( const OUString& rName, const OUString& rValue ) +{ + m_aCommandList.Append( rName, rValue ); +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/css1atr.cxx b/sw/source/filter/html/css1atr.cxx new file mode 100644 index 0000000000..30ba8d3c0a --- /dev/null +++ b/sw/source/filter/html/css1atr.cxx @@ -0,0 +1,3671 @@ +/* -*- 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 <hintids.hxx> +#include <comphelper/string.hxx> +#include <comphelper/xmlencode.hxx> +#include <vcl/svapp.hxx> +#include <svl/whiter.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/blinkitem.hxx> +#include <editeng/cmapitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/lspcitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/formatbreakitem.hxx> +#include <editeng/keepitem.hxx> +#include <editeng/widwitem.hxx> +#include <editeng/spltitem.hxx> +#include <editeng/orphitem.hxx> +#include <editeng/charhiddenitem.hxx> +#include <svx/xoutbmp.hxx> +#include <svx/svdobj.hxx> +#include <editeng/langitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <svtools/htmlout.hxx> +#include <svtools/htmlkywd.hxx> +#include <svl/urihelper.hxx> +#include <unotools/charclass.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <charfmt.hxx> +#include <fmtclds.hxx> +#include <fmtcol.hxx> +#include <fmtfsize.hxx> +#include <fmtornt.hxx> +#include <fmtpdsc.hxx> +#include <fmtlsplt.hxx> +#include <pagedesc.hxx> +#include <fmtanchr.hxx> +#include <docary.hxx> +#include <pam.hxx> +#include <viewsh.hxx> +#include <viewopt.hxx> +#include <swtable.hxx> +// NOTES +#include <ftninfo.hxx> +#include <ftnidx.hxx> +#include <txtftn.hxx> +#include <fmtftn.hxx> +// FOOTNOTES +#include <doc.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <swerror.h> +#include <paratr.hxx> +#include <frmatr.hxx> +#include <poolfmt.hxx> +#include "css1kywd.hxx" +#include "wrthtml.hxx" +#include "htmlnum.hxx" +#include "css1atr.hxx" + +#include <IDocumentStylePoolAccess.hxx> +#include <numrule.hxx> +#include <o3tl/typed_flags_set.hxx> +#include <o3tl/unit_conversion.hxx> + +#include <rtl/strbuf.hxx> +#include <osl/diagnose.h> + +using namespace css; +using editeng::SvxBorderLine; + +#define HTML_HEADSPACE (12*20) + +namespace { + +enum class Css1FrameSize { + NONE = 0x00, + Width = 0x01, + MinHeight = 0x02, + FixHeight = 0x04, + Pixel = 0x10, +}; + +} + +namespace o3tl { + template<> struct typed_flags<Css1FrameSize> : is_typed_flags<Css1FrameSize, 0x17> {}; +} + +#define DOT_LEADERS_MAX_WIDTH 18 + +static SwHTMLWriter& OutCSS1_SwFormat( SwHTMLWriter& rWrt, const SwFormat& rFormat, + IDocumentStylePoolAccess /*SwDoc*/ *pDoc, SwDoc *pTemplate ); +static SwHTMLWriter& OutCSS1_SwPageDesc( SwHTMLWriter& rWrt, const SwPageDesc& rFormat, + IDocumentStylePoolAccess /*SwDoc*/ *pDoc, SwDoc *pTemplate, + sal_uInt16 nRefPoolId, bool bExtRef, + bool bPseudo=true ); +static SwHTMLWriter& OutCSS1_SwFootnoteInfo( SwHTMLWriter& rWrt, const SwEndNoteInfo& rInfo, + SwDoc *pDoc, bool bHasNotes, bool bEndNote ); +static void OutCSS1_SwFormatDropAttrs( SwHTMLWriter& rHWrt, + const SwFormatDrop& rDrop, + const SfxItemSet *pCharFormatItemSet=nullptr ); +static SwHTMLWriter& OutCSS1_SvxTextLn_SvxCrOut_SvxBlink( SwHTMLWriter& rWrt, + const SvxUnderlineItem *pUItem, + const SvxOverlineItem *pOItem, + const SvxCrossedOutItem *pCOItem, + const SvxBlinkItem *pBItem ); +static SwHTMLWriter& OutCSS1_SvxFontWeight( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ); +static SwHTMLWriter& OutCSS1_SvxPosture( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ); +static SwHTMLWriter& OutCSS1_SvxULSpace( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ); +static SwHTMLWriter& OutCSS1_SvxFirstLineIndent(SwHTMLWriter& rWrt, const SfxPoolItem& rHt); +static SwHTMLWriter& OutCSS1_SvxTextLeftMargin(SwHTMLWriter& rWrt, const SfxPoolItem& rHt); +static SwHTMLWriter& OutCSS1_SvxRightMargin(SwHTMLWriter& rWrt, const SfxPoolItem& rHt); +static SwHTMLWriter& OutCSS1_SvxLRSpace( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ); +static SwHTMLWriter& OutCSS1_SvxULSpace_SvxLRSpace( SwHTMLWriter& rWrt, + const SvxULSpaceItem *pULSpace, + const SvxLRSpaceItem *pLRSpace ); +static SwHTMLWriter& OutCSS1_SvxULSpace_SvxLRSpace( SwHTMLWriter& rWrt, + const SfxItemSet& rItemSet ); +static SwHTMLWriter& OutCSS1_SvxBrush( SwHTMLWriter& rWrt, const SfxPoolItem& rHt, + sw::Css1Background nMode, + const OUString *pGraphicName ); +static SwHTMLWriter& OutCSS1_SvxBrush( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ); +static SwHTMLWriter& OutCSS1_SwFormatFrameSize( SwHTMLWriter& rWrt, const SfxPoolItem& rHt, + Css1FrameSize nMode ); +static SwHTMLWriter& OutCSS1_SvxFormatBreak_SwFormatPDesc_SvxFormatKeep( SwHTMLWriter& rWrt, + const SfxItemSet& rItemSet, + bool bDeep ); +static SwHTMLWriter& OutCSS1_SwFormatLayoutSplit( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ); + +namespace +{ + +const char sCSS1_rule_end[] = " }"; +const char sCSS1_span_tag_end[] = "\">"; +const char cCSS1_style_opt_end = '\"'; + +const char* const sHTML_FTN_fontheight = "57%"; + +OString lclConvToHex(sal_uInt16 nHex) +{ + char aNToABuf[] = "00"; + + // set pointer to end of buffer + char *pStr = aNToABuf + (sizeof(aNToABuf)-1); + for( sal_uInt8 n = 0; n < 2; ++n ) + { + *(--pStr) = static_cast<char>(nHex & 0xf ) + 48; + if( *pStr > '9' ) + *pStr += 39; + nHex >>= 4; + } + + return OString(aNToABuf, 2); +} +} + +bool IgnorePropertyForReqIF(bool bReqIF, std::string_view rProperty, std::string_view rValue, + std::optional<sw::Css1Background> oMode) +{ + if (!bReqIF) + return false; + + if (oMode.has_value() && *oMode != sw::Css1Background::TableCell) + { + // Table or row. + if (rProperty == sCSS1_P_background && rValue == "transparent") + { + // This is the default already. + return true; + } + + return false; + } + + // Only allow these two keys, nothing else in ReqIF mode. + if (rProperty == sCSS1_P_text_decoration) + { + // Deny other text-decoration values (e.g. "none"). + if (rValue == "underline" || rValue == "line-through") + { + return false; + } + + return true; + } + + if (rProperty == sCSS1_P_color) + return false; + + return true; +} + +OString GetCSS1_Color(const Color& rColor) +{ + return "#" + lclConvToHex(rColor.GetRed()) + lclConvToHex(rColor.GetGreen()) + lclConvToHex(rColor.GetBlue()); +} + +namespace { + +class SwCSS1OutMode +{ + SwHTMLWriter& rWrt; + sal_uInt16 nOldMode; + +public: + + SwCSS1OutMode( SwHTMLWriter& rHWrt, sal_uInt16 nMode, + const OUString *pSelector ) : + rWrt( rHWrt ), + nOldMode( rHWrt.m_nCSS1OutMode ) + { + rWrt.m_nCSS1OutMode = nMode; + rWrt.m_bFirstCSS1Property = true; + if( pSelector ) + rWrt.m_aCSS1Selector = *pSelector; + } + + ~SwCSS1OutMode() + { + rWrt.m_nCSS1OutMode = nOldMode; + } +}; + +} + +void SwHTMLWriter::OutCSS1_Property( std::string_view pProp, + std::string_view sVal, + const OUString *pSVal, + std::optional<sw::Css1Background> oMode ) +{ + OString aPropertyValue(sVal); + if (aPropertyValue.isEmpty() && pSVal) + { + aPropertyValue = OUStringToOString(*pSVal, RTL_TEXTENCODING_UTF8); + } + if (IgnorePropertyForReqIF(mbReqIF, pProp, aPropertyValue, oMode)) + return; + + OStringBuffer sOut; + + if( m_bFirstCSS1Rule && (m_nCSS1OutMode & CSS1_OUTMODE_RULE_ON)!=0 ) + { + m_bFirstCSS1Rule = false; + OutNewLine(); + sOut.append("<" + GetNamespace() + OOO_STRING_SVTOOLS_HTML_style " " + OOO_STRING_SVTOOLS_HTML_O_type "=\"text/css\">"); + // Optional CSS2 code for dot leaders (dotted line between the Table of Contents titles and page numbers): + // (More information: http://www.w3.org/Style/Examples/007/leaders.en.html) + // + // p.leaders { + // /* FIXME: + // (1) dots line up vertically only in the paragraphs with the same alignment/level + // (2) max-width = 18 cm instead of 80em; possible improvement with the new CSS3 calc() */ + // max-width: 18cm; /* note: need to overwrite max-width with max-width - border-left_of_the_actual_paragraph */ + // padding: 0; + // overflow-x: hidden; + // line-height: 120%; /* note: avoid HTML scrollbars and missing descenders of the letters */ + // } + // p.leaders:after { + // float: left; + // width: 0; + // white-space: nowrap; + // content: ". . . . . . . . . . . . . . . . . . ..."; + // } + // p.leaders span:first-child { + // padding-right: 0.33em; + // background: white; + // } + // p.leaders span + span { + // float: right; + // padding-left: 0.33em; + // background: white; + // position: relative; + // z-index: 1 + // } + + if (m_bCfgPrintLayout) { + sOut.append( + "p." sCSS2_P_CLASS_leaders "{max-width:" + OString::number(DOT_LEADERS_MAX_WIDTH) + + "cm;padding:0;overflow-x:hidden;line-height:120%}" + "p." sCSS2_P_CLASS_leaders ":after{float:left;width:0;white-space:nowrap;content:\""); + for (int i = 0; i < 100; i++ ) + sOut.append(". "); + sOut.append( + "\"}p." sCSS2_P_CLASS_leaders " span:first-child{padding-right:0.33em;background:white}" + "p." sCSS2_P_CLASS_leaders " span+span{float:right;padding-left:0.33em;" + "background:white;position:relative;z-index:1}"); + } + Strm().WriteOString( sOut ); + sOut.setLength(0); + + IncIndentLevel(); + } + + if( m_bFirstCSS1Property ) + { + switch( m_nCSS1OutMode & CSS1_OUTMODE_ANY_ON ) + { + case CSS1_OUTMODE_SPAN_TAG_ON: + case CSS1_OUTMODE_SPAN_TAG1_ON: + if( m_bTagOn ) + { + sOut.append("<" + GetNamespace() + OOO_STRING_SVTOOLS_HTML_span + " " OOO_STRING_SVTOOLS_HTML_O_style "=\""); + } + else + { + HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + OOO_STRING_SVTOOLS_HTML_span), false ); + return; + } + break; + + case CSS1_OUTMODE_RULE_ON: + { + OutNewLine(); + sOut.append(OUStringToOString(m_aCSS1Selector, RTL_TEXTENCODING_UTF8) + " { "); + } + break; + + case CSS1_OUTMODE_STYLE_OPT_ON: + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_style "=\""); + break; + } + m_bFirstCSS1Property = false; + } + else + { + sOut.append("; "); + } + + sOut.append(pProp + OString::Concat(": ")); + if( m_nCSS1OutMode & CSS1_OUTMODE_ENCODE ) + { + // for STYLE-Option encode string + Strm().WriteOString( sOut ); + sOut.setLength(0); + if( !sVal.empty() ) + HTMLOutFuncs::Out_String( Strm(), OUString::createFromAscii(sVal) ); + else if( pSVal ) + HTMLOutFuncs::Out_String( Strm(), *pSVal ); + } + else + { + // for STYLE-Tag print string directly + sOut.append(aPropertyValue); + } + + if (!sOut.isEmpty()) + Strm().WriteOString( sOut ); +} + +static void AddUnitPropertyValue(OStringBuffer &rOut, tools::Long nVal, + FieldUnit eUnit) +{ + if( nVal < 0 ) + { + // special-case sign symbol + nVal = -nVal; + rOut.append('-'); + } + + o3tl::Length eTo; + int nFac; // used to get specific number of decimals + std::string_view pUnit; + switch( eUnit ) + { + case FieldUnit::MM_100TH: + OSL_ENSURE( FieldUnit::MM == eUnit, "Measuring unit not supported" ); + [[fallthrough]]; + case FieldUnit::MM: + eTo = o3tl::Length::mm; + nFac = 100; + pUnit = sCSS1_UNIT_mm; + break; + + case FieldUnit::M: + case FieldUnit::KM: + OSL_ENSURE( FieldUnit::CM == eUnit, "Measuring unit not supported" ); + [[fallthrough]]; + case FieldUnit::CM: + eTo = o3tl::Length::cm; + nFac = 100; + pUnit = sCSS1_UNIT_cm; + break; + + case FieldUnit::TWIP: + OSL_ENSURE( FieldUnit::POINT == eUnit, "Measuring unit not supported" ); + [[fallthrough]]; + case FieldUnit::POINT: + eTo = o3tl::Length::pt; + nFac = 10; + pUnit = sCSS1_UNIT_pt; + break; + + case FieldUnit::PICA: + eTo = o3tl::Length::pc; + nFac = 100; + pUnit = sCSS1_UNIT_pc; + break; + + case FieldUnit::NONE: + case FieldUnit::FOOT: + case FieldUnit::MILE: + case FieldUnit::CUSTOM: + case FieldUnit::PERCENT: + case FieldUnit::INCH: + default: + OSL_ENSURE( FieldUnit::INCH == eUnit, "Measuring unit not supported" ); + eTo = o3tl::Length::in; + nFac = 100; + pUnit = sCSS1_UNIT_inch; + break; + } + + sal_Int64 nResult = o3tl::convert(nVal * nFac, o3tl::Length::twip, eTo); + rOut.append(nResult/nFac); + if ((nResult % nFac) != 0) + { + rOut.append('.'); + while (nFac > 1 && (nResult % nFac) != 0) + { + nFac /= 10; + rOut.append((nResult / nFac) % 10); + } + } + + rOut.append(pUnit); +} + +void SwHTMLWriter::OutCSS1_UnitProperty( std::string_view pProp, tools::Long nVal ) +{ + OStringBuffer sOut; + AddUnitPropertyValue(sOut, nVal, m_eCSS1Unit); + OutCSS1_PropertyAscii(pProp, sOut); +} + +void SwHTMLWriter::OutCSS1_PixelProperty( std::string_view pProp, tools::Long nTwips ) +{ + OString sOut(OString::number(ToPixel(nTwips)) + sCSS1_UNIT_px); + OutCSS1_PropertyAscii(pProp, sOut); +} + +void SwHTMLWriter::OutStyleSheet( const SwPageDesc& rPageDesc ) +{ + m_bFirstCSS1Rule = true; + +// Feature: PrintExt + if( IsHTMLMode(HTMLMODE_PRINT_EXT) ) + { + const SwPageDesc *pFirstPageDesc = nullptr; + sal_uInt16 nFirstRefPoolId = RES_POOLPAGE_HTML; + m_bCSS1IgnoreFirstPageDesc = true; + + // First we try to guess how the document is constructed. + // Allowed are only the templates: HTML, 1st page, left page, and right page. + // A first page is only exported, if it matches the template "1st page". + // Left and right pages are only exported, if their templates are linked. + // If other templates are used, only very simple cases are exported. + const SwPageDesc *pPageDesc = &rPageDesc; + const SwPageDesc *pFollow = rPageDesc.GetFollow(); + if( RES_POOLPAGE_FIRST == pPageDesc->GetPoolFormatId() && + pFollow != pPageDesc && + !IsPoolUserFormat( pFollow->GetPoolFormatId() ) ) + { + // the document has a first page + pFirstPageDesc = pPageDesc; + pPageDesc = pFollow; + pFollow = pPageDesc->GetFollow(); + } + + IDocumentStylePoolAccess* pStylePoolAccess = &getIDocumentStylePoolAccess(); + if( pPageDesc == pFollow ) + { + // The document is one-sided; no matter what page, we do not create a 2-sided doc. + // The attribute is exported relative to the HTML page template. + OutCSS1_SwPageDesc( *this, *pPageDesc, pStylePoolAccess, m_xTemplate.get(), + RES_POOLPAGE_HTML, true, false ); + nFirstRefPoolId = pFollow->GetPoolFormatId(); + } + else if( (RES_POOLPAGE_LEFT == pPageDesc->GetPoolFormatId() && + RES_POOLPAGE_RIGHT == pFollow->GetPoolFormatId()) || + (RES_POOLPAGE_RIGHT == pPageDesc->GetPoolFormatId() && + RES_POOLPAGE_LEFT == pFollow->GetPoolFormatId()) ) + { + // the document is double-sided + OutCSS1_SwPageDesc( *this, *pPageDesc, pStylePoolAccess, m_xTemplate.get(), + RES_POOLPAGE_HTML, true ); + OutCSS1_SwPageDesc( *this, *pFollow, pStylePoolAccess, m_xTemplate.get(), + RES_POOLPAGE_HTML, true ); + nFirstRefPoolId = RES_POOLPAGE_RIGHT; + m_bCSS1IgnoreFirstPageDesc = false; + } + // other cases we miss + + if( pFirstPageDesc ) + OutCSS1_SwPageDesc( *this, *pFirstPageDesc, pStylePoolAccess, m_xTemplate.get(), + nFirstRefPoolId, false ); + } + + // The text body style has to be exported always (if it is changed compared + // to the template), because it is used as reference for any style + // that maps to <P>, and that's especially the standard style + getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT, false ); + + // the Default-TextStyle is not also exported !! + // 0-Style is the Default; is never exported !! + const size_t nTextFormats = m_pDoc->GetTextFormatColls()->size(); + for( size_t i = 1; i < nTextFormats; ++i ) + { + const SwTextFormatColl* pColl = (*m_pDoc->GetTextFormatColls())[i]; + sal_uInt16 nPoolId = pColl->GetPoolFormatId(); + if( nPoolId == RES_POOLCOLL_TEXT || m_pDoc->IsUsed( *pColl ) ) + OutCSS1_SwFormat( *this, *pColl, &m_pDoc->getIDocumentStylePoolAccess(), m_xTemplate.get() ); + } + + // the Default-TextStyle is not also exported !! + const size_t nCharFormats = m_pDoc->GetCharFormats()->size(); + for( size_t i = 1; i < nCharFormats; ++i ) + { + const SwCharFormat *pCFormat = (*m_pDoc->GetCharFormats())[i]; + sal_uInt16 nPoolId = pCFormat->GetPoolFormatId(); + if( nPoolId == RES_POOLCHR_INET_NORMAL || + nPoolId == RES_POOLCHR_INET_VISIT || + m_pDoc->IsUsed( *pCFormat ) ) + OutCSS1_SwFormat( *this, *pCFormat, &m_pDoc->getIDocumentStylePoolAccess(), m_xTemplate.get() ); + } + + bool bHasEndNotes {false}; + bool bHasFootNotes {false}; + const SwFootnoteIdxs& rIdxs = m_pDoc->GetFootnoteIdxs(); + for( auto pIdx : rIdxs ) + { + if( pIdx->GetFootnote().IsEndNote() ) + { + bHasEndNotes = true; + if (bHasFootNotes) + break; + } + else + { + bHasFootNotes = true; + if (bHasEndNotes) + break; + } + } + OutCSS1_SwFootnoteInfo( *this, m_pDoc->GetFootnoteInfo(), m_pDoc, bHasFootNotes, false ); + OutCSS1_SwFootnoteInfo( *this, m_pDoc->GetEndNoteInfo(), m_pDoc, bHasEndNotes, true ); + + if( !m_bFirstCSS1Rule ) + { + DecIndentLevel(); + + OutNewLine(); + HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + OOO_STRING_SVTOOLS_HTML_style), false ); + } + else + { + m_bFirstCSS1Rule = false; + } + + m_nDfltTopMargin = 0; + m_nDfltBottomMargin = 0; +} + +// if pPseudo is set, Styles-Sheets will be exported; +// otherwise we only search for Token and Class for a Format +sal_uInt16 SwHTMLWriter::GetCSS1Selector( const SwFormat *pFormat, OString& rToken, + OUString& rClass, sal_uInt16& rRefPoolId, + OUString *pPseudo ) +{ + sal_uInt16 nDeep = 0; + rToken.clear(); + rClass.clear(); + rRefPoolId = 0; + if( pPseudo ) + pPseudo->clear(); + + bool bChrFormat = RES_CHRFMT==pFormat->Which(); + + // search formats above for the nearest standard or HTML-Tag template + const SwFormat *pPFormat = pFormat; + while( pPFormat && !pPFormat->IsDefault() ) + { + bool bStop = false; + sal_uInt16 nPoolId = pPFormat->GetPoolFormatId(); + if( USER_FMT & nPoolId ) + { + // user templates + const OUString& aNm(pPFormat->GetName()); + + if (!bChrFormat && aNm == OOO_STRING_SVTOOLS_HTML_blockquote) + { + rRefPoolId = RES_POOLCOLL_HTML_BLOCKQUOTE; + rToken = OOO_STRING_SVTOOLS_HTML_blockquote ""_ostr; + } + else if (bChrFormat && aNm == OOO_STRING_SVTOOLS_HTML_citation) + { + rRefPoolId = RES_POOLCHR_HTML_CITATION; + rToken = OOO_STRING_SVTOOLS_HTML_citation ""_ostr; + } + else if (bChrFormat && aNm == OOO_STRING_SVTOOLS_HTML_code) + { + rRefPoolId = RES_POOLCHR_HTML_CODE; + rToken = OOO_STRING_SVTOOLS_HTML_code ""_ostr; + } + else if (bChrFormat && aNm == OOO_STRING_SVTOOLS_HTML_definstance) + { + rRefPoolId = RES_POOLCHR_HTML_DEFINSTANCE; + rToken = OOO_STRING_SVTOOLS_HTML_definstance ""_ostr; + } + else if (!bChrFormat && (aNm == OOO_STRING_SVTOOLS_HTML_dd || + aNm == OOO_STRING_SVTOOLS_HTML_dt)) + { + sal_uInt16 nDefListLvl = GetDefListLvl(aNm, nPoolId); + // Export the templates DD 1/DT 1, + // but none of their derived templates, + // also not DD 2/DT 2 etc. + if (nDefListLvl) + { + if (pPseudo && (nDeep || (nDefListLvl & 0x0fff) > 1)) + { + bStop = true; + } + else if (nDefListLvl & HTML_DLCOLL_DD) + { + rRefPoolId = RES_POOLCOLL_HTML_DD; + rToken = OOO_STRING_SVTOOLS_HTML_dd ""_ostr; + } + else + { + rRefPoolId = RES_POOLCOLL_HTML_DT; + rToken = OOO_STRING_SVTOOLS_HTML_dt ""_ostr; + } + } + } + else if (bChrFormat && aNm == OOO_STRING_SVTOOLS_HTML_emphasis) + { + rRefPoolId = RES_POOLCHR_HTML_EMPHASIS; + rToken = OOO_STRING_SVTOOLS_HTML_emphasis ""_ostr; + } + else if (!bChrFormat && aNm == OOO_STRING_SVTOOLS_HTML_horzrule) + { + // do not export HR ! + bStop = (nDeep==0); + } + else if (bChrFormat && aNm == OOO_STRING_SVTOOLS_HTML_keyboard) + { + rRefPoolId = RES_POOLCHR_HTML_KEYBOARD; + rToken = OOO_STRING_SVTOOLS_HTML_keyboard ""_ostr; + } + else if (!bChrFormat && aNm == OOO_STRING_SVTOOLS_HTML_listing) + { + // Export Listings as PRE or PRE-derived template + rToken = OOO_STRING_SVTOOLS_HTML_preformtxt ""_ostr; + rRefPoolId = RES_POOLCOLL_HTML_PRE; + nDeep = CSS1_FMT_CMPREF; + } + else if (!bChrFormat && aNm == OOO_STRING_SVTOOLS_HTML_preformtxt) + { + rRefPoolId = RES_POOLCOLL_HTML_PRE; + rToken = OOO_STRING_SVTOOLS_HTML_preformtxt ""_ostr; + } + else if (bChrFormat && aNm == OOO_STRING_SVTOOLS_HTML_sample) + { + rRefPoolId = RES_POOLCHR_HTML_SAMPLE; + rToken = OOO_STRING_SVTOOLS_HTML_sample ""_ostr; + } + else if (bChrFormat && aNm == OOO_STRING_SVTOOLS_HTML_strong) + { + rRefPoolId = RES_POOLCHR_HTML_STRONG; + rToken = OOO_STRING_SVTOOLS_HTML_strong ""_ostr; + } + else if (bChrFormat && aNm == OOO_STRING_SVTOOLS_HTML_teletype) + { + rRefPoolId = RES_POOLCHR_HTML_TELETYPE; + rToken = OOO_STRING_SVTOOLS_HTML_teletype ""_ostr; + } + else if (bChrFormat && aNm == OOO_STRING_SVTOOLS_HTML_variable) + { + rRefPoolId = RES_POOLCHR_HTML_VARIABLE; + rToken = OOO_STRING_SVTOOLS_HTML_variable ""_ostr; + } + else if (!bChrFormat && aNm == OOO_STRING_SVTOOLS_HTML_xmp) + { + // export XMP as PRE (but not the template as Style) + rToken = OOO_STRING_SVTOOLS_HTML_preformtxt ""_ostr; + rRefPoolId = RES_POOLCOLL_HTML_PRE; + nDeep = CSS1_FMT_CMPREF; + } + + // if a PoolId is set, the Name of the template is that of the related Token + OSL_ENSURE( (rRefPoolId != 0) == (!rToken.isEmpty()), + "Token missing" ); + } + else + { + // Pool templates + switch( nPoolId ) + { + // paragraph templates + case RES_POOLCOLL_HEADLINE_BASE: + case RES_POOLCOLL_STANDARD: + // do not export this template + bStop = (nDeep==0); + break; + case RES_POOLCOLL_TEXT: + rToken = OOO_STRING_SVTOOLS_HTML_parabreak ""_ostr; + break; + case RES_POOLCOLL_HEADLINE1: + rToken = OOO_STRING_SVTOOLS_HTML_head1 ""_ostr; + break; + case RES_POOLCOLL_HEADLINE2: + rToken = OOO_STRING_SVTOOLS_HTML_head2 ""_ostr; + break; + case RES_POOLCOLL_HEADLINE3: + rToken = OOO_STRING_SVTOOLS_HTML_head3 ""_ostr; + break; + case RES_POOLCOLL_HEADLINE4: + rToken = OOO_STRING_SVTOOLS_HTML_head4 ""_ostr; + break; + case RES_POOLCOLL_HEADLINE5: + rToken = OOO_STRING_SVTOOLS_HTML_head5 ""_ostr; + break; + case RES_POOLCOLL_HEADLINE6: + rToken = OOO_STRING_SVTOOLS_HTML_head6 ""_ostr; + break; + case RES_POOLCOLL_SEND_ADDRESS: + rToken = OOO_STRING_SVTOOLS_HTML_address ""_ostr; + break; + case RES_POOLCOLL_HTML_BLOCKQUOTE: + rToken = OOO_STRING_SVTOOLS_HTML_blockquote ""_ostr; + break; + case RES_POOLCOLL_HTML_PRE: + rToken = OOO_STRING_SVTOOLS_HTML_preformtxt ""_ostr; + break; + + case RES_POOLCOLL_HTML_DD: + rToken = OOO_STRING_SVTOOLS_HTML_dd ""_ostr; + break; + case RES_POOLCOLL_HTML_DT: + rToken = OOO_STRING_SVTOOLS_HTML_dt ""_ostr; + break; + + case RES_POOLCOLL_TABLE: + if( pPseudo ) + { + rToken = OOO_STRING_SVTOOLS_HTML_tabledata " " + OOO_STRING_SVTOOLS_HTML_parabreak ""_ostr; + } + else + rToken = OOO_STRING_SVTOOLS_HTML_parabreak ""_ostr; + break; + case RES_POOLCOLL_TABLE_HDLN: + if( pPseudo ) + { + rToken = OOO_STRING_SVTOOLS_HTML_tableheader " " + OOO_STRING_SVTOOLS_HTML_parabreak ""_ostr; + } + else + rToken = OOO_STRING_SVTOOLS_HTML_parabreak ""_ostr; + break; + case RES_POOLCOLL_HTML_HR: + // do not export HR ! + bStop = (nDeep==0); + break; + case RES_POOLCOLL_FOOTNOTE: + if( !nDeep ) + { + rToken = OOO_STRING_SVTOOLS_HTML_parabreak ""_ostr; + rClass = OOO_STRING_SVTOOLS_HTML_sdfootnote; + rRefPoolId = RES_POOLCOLL_TEXT; + nDeep = CSS1_FMT_CMPREF; + } + break; + case RES_POOLCOLL_ENDNOTE: + if( !nDeep ) + { + rToken = OOO_STRING_SVTOOLS_HTML_parabreak ""_ostr; + rClass = OOO_STRING_SVTOOLS_HTML_sdendnote; + rRefPoolId = RES_POOLCOLL_TEXT; + nDeep = CSS1_FMT_CMPREF; + } + break; + + // character templates + case RES_POOLCHR_HTML_EMPHASIS: + rToken = OOO_STRING_SVTOOLS_HTML_emphasis ""_ostr; + break; + case RES_POOLCHR_HTML_CITATION: + rToken = OOO_STRING_SVTOOLS_HTML_citation ""_ostr; + break; + case RES_POOLCHR_HTML_STRONG: + rToken = OOO_STRING_SVTOOLS_HTML_strong ""_ostr; + break; + case RES_POOLCHR_HTML_CODE: + rToken = OOO_STRING_SVTOOLS_HTML_code ""_ostr; + break; + case RES_POOLCHR_HTML_SAMPLE: + rToken = OOO_STRING_SVTOOLS_HTML_sample ""_ostr; + break; + case RES_POOLCHR_HTML_KEYBOARD: + rToken = OOO_STRING_SVTOOLS_HTML_keyboard ""_ostr; + break; + case RES_POOLCHR_HTML_VARIABLE: + rToken = OOO_STRING_SVTOOLS_HTML_variable ""_ostr; + break; + case RES_POOLCHR_HTML_DEFINSTANCE: + rToken = OOO_STRING_SVTOOLS_HTML_definstance ""_ostr; + break; + case RES_POOLCHR_HTML_TELETYPE: + rToken = OOO_STRING_SVTOOLS_HTML_teletype ""_ostr; + break; + + case RES_POOLCHR_INET_NORMAL: + if( pPseudo ) + { + rToken = OOO_STRING_SVTOOLS_HTML_anchor ""_ostr; + *pPseudo = OStringToOUString( sCSS1_link, RTL_TEXTENCODING_ASCII_US ); + } + break; + case RES_POOLCHR_INET_VISIT: + if( pPseudo ) + { + rToken = OOO_STRING_SVTOOLS_HTML_anchor ""_ostr; + *pPseudo = OStringToOUString( sCSS1_visited, RTL_TEXTENCODING_ASCII_US ); + } + break; + } + + // if a token is set, PoolId contains the related template + if( !rToken.isEmpty() && !rRefPoolId ) + rRefPoolId = nPoolId; + } + + if( !rToken.isEmpty() || bStop ) + { + // stop if a HTML-Tag template was found + break; + } + else + { + // continue otherwise + nDeep++; + pPFormat = pPFormat->DerivedFrom(); + } + } + + if( !rToken.isEmpty() ) + { + // this is a HTML-Tag template + if( !nDeep ) + nDeep = CSS1_FMT_ISTAG; + } + else + { + // this is not a HTML-Tag template nor derived from one + nDeep = 0; + } + if( nDeep > 0 && nDeep < CSS1_FMT_SPECIAL ) + { + // If the template is derived from a HTML template, + // we export it as <TOKEN>.<CLASS>, otherwise as .<CLASS>. + // <CLASS> is the name of the template after removing all characters + // before and including the first '.' + rClass = pFormat->GetName(); + sal_Int32 nPos = rClass.indexOf( '.' ); + if( nPos >= 0 && rClass.getLength() > nPos+1 ) + { + rClass = rClass.replaceAt( 0, nPos+1, u"" ); + } + + rClass = GetAppCharClass().lowercase( rClass ); + rClass = rClass.replaceAll( ".", "-" ); + rClass = rClass.replaceAll( " ", "-" ); + rClass = rClass.replaceAll( "_", "-" ); + } + + return nDeep; +} + +static sal_uInt16 GetCSS1Selector( const SwFormat *pFormat, OUString& rSelector, + sal_uInt16& rRefPoolId ) +{ + OString aToken; + OUString aClass; + OUString aPseudo; + + sal_uInt16 nDeep = SwHTMLWriter::GetCSS1Selector( pFormat, aToken, aClass, + rRefPoolId, &aPseudo ); + if( nDeep ) + { + if( !aToken.isEmpty() ) + rSelector = OStringToOUString(aToken, RTL_TEXTENCODING_ASCII_US); + else + rSelector.clear(); + + if( !aClass.isEmpty() ) + rSelector += "." + aClass; + if( !aPseudo.isEmpty() ) + rSelector += ":" + aPseudo; + } + + return nDeep; +} + +const SwFormat *SwHTMLWriter::GetTemplateFormat( sal_uInt16 nPoolFormatId, + IDocumentStylePoolAccess* pTemplate /*SwDoc *pTemplate*/) +{ + const SwFormat *pRefFormat = nullptr; + + if( pTemplate ) + { + OSL_ENSURE( !(USER_FMT & nPoolFormatId), + "No user templates found" ); + if( POOLGRP_NOCOLLID & nPoolFormatId ) + pRefFormat = pTemplate->GetCharFormatFromPool( nPoolFormatId ); + else + pRefFormat = pTemplate->GetTextCollFromPool( nPoolFormatId, false ); + } + + return pRefFormat; +} + +const SwFormat *SwHTMLWriter::GetParentFormat( const SwFormat& rFormat, sal_uInt16 nDeep ) +{ + OSL_ENSURE( nDeep != USHRT_MAX, "Called GetParent for HTML-template!" ); + const SwFormat *pRefFormat = nullptr; + + if( nDeep > 0 ) + { + // get the pointer for the HTML-Tag template, from which the template is derived + pRefFormat = &rFormat; + for( sal_uInt16 i=nDeep; i>0; i-- ) + pRefFormat = pRefFormat->DerivedFrom(); + + if( pRefFormat && pRefFormat->IsDefault() ) + pRefFormat = nullptr; + } + + return pRefFormat; +} + +bool swhtml_css1atr_equalFontItems( const SfxPoolItem& r1, const SfxPoolItem& r2 ) +{ + return static_cast<const SvxFontItem &>(r1).GetFamilyName() == + static_cast<const SvxFontItem &>(r2).GetFamilyName() && + static_cast<const SvxFontItem &>(r1).GetFamily() == + static_cast<const SvxFontItem &>(r2).GetFamily(); +} + +void SwHTMLWriter::SubtractItemSet( SfxItemSet& rItemSet, + const SfxItemSet& rRefItemSet, + bool bSetDefaults, + bool bClearSame, + const SfxItemSet *pRefScriptItemSet ) +{ + OSL_ENSURE( bSetDefaults || bClearSame, + "SwHTMLWriter::SubtractItemSet: No action for this Flag" ); + SfxItemSet aRefItemSet( *rRefItemSet.GetPool(), rRefItemSet.GetRanges() ); + aRefItemSet.Set( rRefItemSet ); + + // compare with the Attr-Set of the template + SfxWhichIter aIter( rItemSet ); + sal_uInt16 nWhich = aIter.FirstWhich(); + while( nWhich ) + { + const SfxPoolItem *pRefItem, *pItem; + bool bItemSet = ( SfxItemState::SET == + aIter.GetItemState( false, &pItem) ); + bool bRefItemSet; + + if( pRefScriptItemSet ) + { + switch( nWhich ) + { + case RES_CHRATR_FONT: + case RES_CHRATR_FONTSIZE: + case RES_CHRATR_LANGUAGE: + case RES_CHRATR_POSTURE: + case RES_CHRATR_WEIGHT: + case RES_CHRATR_CJK_FONT: + case RES_CHRATR_CJK_FONTSIZE: + case RES_CHRATR_CJK_LANGUAGE: + case RES_CHRATR_CJK_POSTURE: + case RES_CHRATR_CJK_WEIGHT: + case RES_CHRATR_CTL_FONT: + case RES_CHRATR_CTL_FONTSIZE: + case RES_CHRATR_CTL_LANGUAGE: + case RES_CHRATR_CTL_POSTURE: + case RES_CHRATR_CTL_WEIGHT: + bRefItemSet = ( SfxItemState::SET == + pRefScriptItemSet->GetItemState( nWhich, true, &pRefItem) ); + break; + default: + bRefItemSet = ( SfxItemState::SET == + aRefItemSet.GetItemState( nWhich, false, &pRefItem) ); + break; + } + } + else + { + bRefItemSet = ( SfxItemState::SET == + aRefItemSet.GetItemState( nWhich, false, &pRefItem) ); + } + + if( bItemSet ) + { + if( (bClearSame || pRefScriptItemSet) && bRefItemSet && + ( *pItem == *pRefItem || + ((RES_CHRATR_FONT == nWhich || + RES_CHRATR_CJK_FONT == nWhich || + RES_CHRATR_CTL_FONT == nWhich) && + swhtml_css1atr_equalFontItems( *pItem, *pRefItem )) ) ) + { + // the Attribute is in both templates with the same value + // and does not need to be exported + rItemSet.ClearItem( nWhich ); + } + } + else + { + if( (bSetDefaults || pRefScriptItemSet) && bRefItemSet ) + { + // the Attribute exists only in the reference; the default + // might have to be exported + rItemSet.Put( rItemSet.GetPool()->GetDefaultItem(nWhich) ); + } + } + + nWhich = aIter.NextWhich(); + } +} + +void SwHTMLWriter::PrepareFontList( const SvxFontItem& rFontItem, + OUString& rNames, + sal_Unicode cQuote, bool bGeneric ) +{ + rNames.clear(); + const OUString& rName = rFontItem.GetFamilyName(); + bool bContainsKeyword = false; + if( !rName.isEmpty() ) + { + sal_Int32 nStrPos = 0; + while( nStrPos != -1 ) + { + OUString aName = rName.getToken( 0, ';', nStrPos ); + aName = comphelper::string::encodeForXml(comphelper::string::strip(aName, ' ')); + if( aName.isEmpty() ) + continue; + + bool bIsKeyword = false; + switch( aName[0] ) + { + case 'c': + case 'C': + bIsKeyword = aName.equalsIgnoreAsciiCaseAscii( sCSS1_PV_cursive ); + break; + + case 'f': + case 'F': + bIsKeyword = aName.equalsIgnoreAsciiCaseAscii( sCSS1_PV_fantasy ); + break; + + case 'm': + case 'M': + bIsKeyword = aName.equalsIgnoreAsciiCaseAscii( sCSS1_PV_monospace ); + break; + + case 's': + case 'S': + bIsKeyword = + aName.equalsIgnoreAsciiCaseAscii( sCSS1_PV_serif ) || + aName.equalsIgnoreAsciiCaseAscii( sCSS1_PV_sans_serif ); + break; + } + + bContainsKeyword |= bIsKeyword; + + if( !rNames.isEmpty() ) + rNames += ", "; + if( cQuote && !bIsKeyword ) + rNames += OUStringChar( cQuote ); + rNames += aName; + if( cQuote && !bIsKeyword ) + rNames += OUStringChar( cQuote ); + } + } + + if( bContainsKeyword || !bGeneric ) + return; + + std::string_view pStr; + switch( rFontItem.GetFamily() ) + { + case FAMILY_ROMAN: pStr = sCSS1_PV_serif; break; + case FAMILY_SWISS: pStr = sCSS1_PV_sans_serif; break; + case FAMILY_SCRIPT: pStr = sCSS1_PV_cursive; break; + case FAMILY_DECORATIVE: pStr = sCSS1_PV_fantasy; break; + case FAMILY_MODERN: pStr = sCSS1_PV_monospace; break; + default: + ; + } + + if( !pStr.empty() ) + { + if( !rNames.isEmpty() ) + rNames += ", "; + rNames += OStringToOUString( pStr, RTL_TEXTENCODING_ASCII_US ); + } +} + +bool SwHTMLWriter::HasScriptDependentItems( const SfxItemSet& rItemSet, + bool bCheckDropCap ) +{ + static const sal_uInt16 aWhichIds[] = + { + RES_CHRATR_FONT, RES_CHRATR_CJK_FONT, RES_CHRATR_CTL_FONT, + RES_CHRATR_FONTSIZE, RES_CHRATR_CJK_FONTSIZE, RES_CHRATR_CTL_FONTSIZE, + RES_CHRATR_LANGUAGE, RES_CHRATR_CJK_LANGUAGE, RES_CHRATR_CTL_LANGUAGE, + RES_CHRATR_POSTURE, RES_CHRATR_CJK_POSTURE, RES_CHRATR_CTL_POSTURE, + RES_CHRATR_WEIGHT, RES_CHRATR_CJK_WEIGHT, RES_CHRATR_CTL_WEIGHT, + 0, 0, 0 + }; + + for( int i=0; aWhichIds[i]; i += 3 ) + { + const SfxPoolItem *pItem = nullptr, *pItemCJK = nullptr, *pItemCTL = nullptr, *pTmp; + int nItemCount = 0; + if( SfxItemState::SET == rItemSet.GetItemState( aWhichIds[i], false, + &pTmp ) ) + { + pItem = pTmp; + nItemCount++; + } + if( SfxItemState::SET == rItemSet.GetItemState( aWhichIds[i+1], false, + &pTmp ) ) + { + pItemCJK = pTmp; + nItemCount++; + } + if( SfxItemState::SET == rItemSet.GetItemState( aWhichIds[i+2], false, + &pTmp ) ) + { + pItemCTL = pTmp; + nItemCount++; + } + + // If some of the items are set, but not all, we need script dependent + // styles + if( nItemCount > 0 && nItemCount < 3 ) + return true; + + if( 3 == nItemCount ) + { + // If all items are set, but some of them have different values, + // we need script dependent styles, too. For font items, we have + // to take care about their special HTML/CSS1 representation. + if( RES_CHRATR_FONT == aWhichIds[i] ) + { + if( !swhtml_css1atr_equalFontItems( *pItem, *pItemCJK ) || + !swhtml_css1atr_equalFontItems( *pItem, *pItemCTL ) || + !swhtml_css1atr_equalFontItems( *pItemCJK, *pItemCTL ) ) + return true; + } + else + { + if( *pItem != *pItemCJK || + *pItem != *pItemCTL || + *pItemCJK != *pItemCTL ) + return true; + } + } + } + + const SwFormatDrop *pDrop; + if( bCheckDropCap && + (pDrop = rItemSet.GetItemIfSet( RES_PARATR_DROP )) ) + { + const SwCharFormat *pDCCharFormat = pDrop->GetCharFormat(); + if( pDCCharFormat ) + { + //sequence of (start, end) property ranges we want to + //query + SfxItemSetFixed< + RES_CHRATR_FONT, RES_CHRATR_FONT, + RES_CHRATR_POSTURE, RES_CHRATR_POSTURE, + RES_CHRATR_WEIGHT, RES_CHRATR_WEIGHT, + RES_CHRATR_CJK_FONT, RES_CHRATR_CJK_FONT, + RES_CHRATR_CJK_POSTURE, RES_CHRATR_CTL_FONT, + RES_CHRATR_CTL_POSTURE, RES_CHRATR_CTL_WEIGHT> + aTstItemSet(*pDCCharFormat->GetAttrSet().GetPool()); + aTstItemSet.Set( pDCCharFormat->GetAttrSet() ); + return HasScriptDependentItems( aTstItemSet, false ); + } + } + + return false; +} + +static bool OutCSS1Rule( SwHTMLWriter& rWrt, const OUString& rSelector, + const SfxItemSet& rItemSet, bool bHasClass, + bool bCheckForPseudo ) +{ + bool bScriptDependent = false; + if( SwHTMLWriter::HasScriptDependentItems( rItemSet, bHasClass ) ) + { + bScriptDependent = true; + std::u16string_view aSelector( rSelector ); + + std::u16string_view aPseudo; + if( bCheckForPseudo ) + { + size_t nPos = aSelector.rfind( ':' ); + if( nPos != std::u16string_view::npos ) + { + aPseudo = aSelector.substr( nPos ); + aSelector =aSelector.substr( 0, nPos ); + } + } + + if( !bHasClass ) + { + // If we are exporting styles for a tag we have to export a tag + // rule for all properties that aren't style dependent and + // some class rule for the additional style dependen properties + { + SwCSS1OutMode aMode( rWrt, CSS1_OUTMODE_NO_SCRIPT|CSS1_OUTMODE_RULE|CSS1_OUTMODE_TEMPLATE, + &rSelector ); + rWrt.OutCSS1_SfxItemSet( rItemSet, false ); + } + + //sequence of (start, end) property ranges we want to + //query + SfxItemSetFixed<RES_CHRATR_FONT, RES_CHRATR_FONTSIZE, + RES_CHRATR_LANGUAGE, RES_CHRATR_POSTURE, + RES_CHRATR_WEIGHT, RES_CHRATR_WEIGHT, + RES_CHRATR_CJK_FONT, RES_CHRATR_CTL_WEIGHT> + aScriptItemSet( *rItemSet.GetPool() ); + aScriptItemSet.Put( rItemSet ); + + OUString aNewSelector = OUString::Concat(aSelector) + ".western" + aPseudo; + { + SwCSS1OutMode aMode( rWrt, CSS1_OUTMODE_WESTERN|CSS1_OUTMODE_RULE|CSS1_OUTMODE_TEMPLATE, + &aNewSelector ); + rWrt.OutCSS1_SfxItemSet( aScriptItemSet, false ); + } + + aNewSelector = OUString::Concat(aSelector) + ".cjk" + aPseudo; + { + SwCSS1OutMode aMode( rWrt, CSS1_OUTMODE_CJK|CSS1_OUTMODE_RULE|CSS1_OUTMODE_TEMPLATE, + &aNewSelector ); + rWrt.OutCSS1_SfxItemSet( aScriptItemSet, false ); + } + + aNewSelector = OUString::Concat(aSelector) + ".ctl" + aPseudo; + { + SwCSS1OutMode aMode( rWrt, CSS1_OUTMODE_CTL|CSS1_OUTMODE_RULE|CSS1_OUTMODE_TEMPLATE, + &aNewSelector ); + rWrt.OutCSS1_SfxItemSet( aScriptItemSet, false ); + } + } + else + { + // If there are script dependencies and we are derived from a tag, + // when we have to export a style dependent class for all + // scripts + OUString aNewSelector = OUString::Concat(aSelector) + "-western" + aPseudo; + { + SwCSS1OutMode aMode( rWrt, CSS1_OUTMODE_WESTERN|CSS1_OUTMODE_RULE|CSS1_OUTMODE_TEMPLATE, + &aNewSelector ); + rWrt.OutCSS1_SfxItemSet( rItemSet, false ); + } + + aNewSelector = OUString::Concat(aSelector) + "-cjk" + aPseudo; + { + SwCSS1OutMode aMode( rWrt, CSS1_OUTMODE_CJK|CSS1_OUTMODE_RULE|CSS1_OUTMODE_TEMPLATE, + &aNewSelector ); + rWrt.OutCSS1_SfxItemSet( rItemSet, false ); + } + + aNewSelector = OUString::Concat(aSelector) + "-ctl" + aPseudo; + { + SwCSS1OutMode aMode( rWrt, CSS1_OUTMODE_CTL|CSS1_OUTMODE_RULE|CSS1_OUTMODE_TEMPLATE, + &aNewSelector ); + rWrt.OutCSS1_SfxItemSet( rItemSet, false ); + } + } + } + else + { + // If there are no script dependencies, when all items are + // exported in one step. For hyperlinks only, a script information + // must be there, because these two chr formats don't support + // script dependencies by now. + SwCSS1OutMode aMode( rWrt, + rWrt.m_nCSS1Script|CSS1_OUTMODE_RULE|CSS1_OUTMODE_TEMPLATE, + &rSelector ); + rWrt.OutCSS1_SfxItemSet( rItemSet, false ); + } + + return bScriptDependent; +} + +static void OutCSS1DropCapRule( + SwHTMLWriter& rWrt, const OUString& rSelector, + const SwFormatDrop& rDrop, bool bHasClass, + bool bHasScriptDependencies ) +{ + const SwCharFormat *pDCCharFormat = rDrop.GetCharFormat(); + if( (bHasScriptDependencies && bHasClass) || + (pDCCharFormat && SwHTMLWriter::HasScriptDependentItems( pDCCharFormat->GetAttrSet(), false ) ) ) + { + std::u16string_view aSelector( rSelector ); + + std::u16string_view aPseudo; + size_t nPos = aSelector.rfind( ':' ); + if( nPos != std::u16string_view::npos ) + { + aPseudo = aSelector.substr( nPos ); + aSelector = aSelector.substr( 0, nPos ); + } + + if( !bHasClass ) + { + // If we are exporting styles for a tag we have to export a tag + // rule for all properties that aren't style dependent and + // some class rule for the additional style dependen properties + { + SwCSS1OutMode aMode( rWrt, CSS1_OUTMODE_NO_SCRIPT|CSS1_OUTMODE_RULE|CSS1_OUTMODE_DROPCAP, + &rSelector ); + OutCSS1_SwFormatDropAttrs( rWrt, rDrop ); + } + + SfxItemSetFixed<RES_CHRATR_FONT, RES_CHRATR_FONTSIZE, + RES_CHRATR_LANGUAGE, RES_CHRATR_POSTURE, + RES_CHRATR_WEIGHT, RES_CHRATR_WEIGHT, + RES_CHRATR_CJK_FONT, RES_CHRATR_CTL_WEIGHT> + aScriptItemSet( rWrt.m_pDoc->GetAttrPool() ); + if( pDCCharFormat ) + aScriptItemSet.Set( pDCCharFormat->GetAttrSet() ); + + OUString aNewSelector = OUString::Concat(aSelector) + ".western" + aPseudo; + { + SwCSS1OutMode aMode( rWrt, CSS1_OUTMODE_WESTERN|CSS1_OUTMODE_RULE|CSS1_OUTMODE_DROPCAP, + &aNewSelector ); + OutCSS1_SwFormatDropAttrs( rWrt, rDrop, &aScriptItemSet ); + } + + aNewSelector = OUString::Concat(aSelector) + ".cjk" + aPseudo; + { + SwCSS1OutMode aMode( rWrt, CSS1_OUTMODE_CJK|CSS1_OUTMODE_RULE|CSS1_OUTMODE_DROPCAP, + &aNewSelector ); + OutCSS1_SwFormatDropAttrs( rWrt, rDrop, &aScriptItemSet ); + } + + aNewSelector = OUString::Concat(aSelector) + ".ctl" + aPseudo; + { + SwCSS1OutMode aMode( rWrt, CSS1_OUTMODE_CTL|CSS1_OUTMODE_RULE|CSS1_OUTMODE_DROPCAP, + &aNewSelector ); + OutCSS1_SwFormatDropAttrs( rWrt, rDrop, &aScriptItemSet ); + } + } + else + { + // If there are script dependencies and we are derived from a tag, + // when we have to export a style dependent class for all + // scripts + OUString aNewSelector = OUString::Concat(aSelector) + "-western" + aPseudo; + { + SwCSS1OutMode aMode( rWrt, CSS1_OUTMODE_WESTERN|CSS1_OUTMODE_RULE|CSS1_OUTMODE_DROPCAP, + &aNewSelector ); + OutCSS1_SwFormatDropAttrs( rWrt, rDrop ); + } + + aNewSelector = OUString::Concat(aSelector) + "-cjk" + aPseudo; + { + SwCSS1OutMode aMode( rWrt, CSS1_OUTMODE_CJK|CSS1_OUTMODE_RULE|CSS1_OUTMODE_DROPCAP, + &aNewSelector ); + OutCSS1_SwFormatDropAttrs( rWrt, rDrop ); + } + + aNewSelector = OUString::Concat(aSelector) + "-ctl" + aPseudo; + { + SwCSS1OutMode aMode( rWrt, CSS1_OUTMODE_CTL|CSS1_OUTMODE_RULE|CSS1_OUTMODE_DROPCAP, + &aNewSelector ); + OutCSS1_SwFormatDropAttrs( rWrt, rDrop ); + } + } + } + else + { + // If there are no script dependencies, when all items are + // exported in one step. For hyperlinks only, a script information + // must be there, because these two chr formats don't support + // script dependencies by now. + SwCSS1OutMode aMode( rWrt, + rWrt.m_nCSS1Script|CSS1_OUTMODE_RULE|CSS1_OUTMODE_DROPCAP, + &rSelector ); + OutCSS1_SwFormatDropAttrs( rWrt, rDrop ); + } +} + +static SwHTMLWriter& OutCSS1_SwFormat( SwHTMLWriter& rWrt, const SwFormat& rFormat, + IDocumentStylePoolAccess/*SwDoc*/ *pDoc, SwDoc *pTemplate ) +{ + bool bCharFormat = false; + switch( rFormat.Which() ) + { + case RES_CHRFMT: + bCharFormat = true; + break; + + case RES_TXTFMTCOLL: + case RES_CONDTXTFMTCOLL: + // these template-types can be exported + break; + + default: + // but not these + return rWrt; + } + + // determine Selector and the to-be-exported Attr-Set-depth + OUString aSelector; + sal_uInt16 nRefPoolId = 0; + sal_uInt16 nDeep = GetCSS1Selector( &rFormat, aSelector, nRefPoolId ); + if( !nDeep ) + return rWrt; // not derived from a HTML-template + + sal_uInt16 nPoolFormatId = rFormat.GetPoolFormatId(); + + // Determine the to-be-exported Attr-Set. We have to distinguish 3 cases: + // - HTML-Tag templates (nDeep==USHRT_MAX): + // Export Attrs... + // - that are set in the template, but not in the original of the HTML template + // - the Default-Attrs for the Attrs, that are set in the Original of the + // HTML template, but not in the current template. + // - templates directly derived from HTML templates (nDeep==1): + // Export only Attributes of the template Item-Set w/o its parents. + // - templates in-directly derived from HTML templates (nDeep>1): + // Export Attributes of the template Item-Set incl. its Parents, + // but w/o Attributes that are set in the HTML-Tag template. + + // create Item-Set with all Attributes from the template + // (all but for nDeep==1) + const SfxItemSet& rFormatItemSet = rFormat.GetAttrSet(); + SfxItemSet aItemSet( *rFormatItemSet.GetPool(), rFormatItemSet.GetRanges() ); + aItemSet.Set( rFormatItemSet ); // Was nDeep!=1 that is not working + // for script dependent items but should + // not make a difference for any other + + bool bSetDefaults = true, bClearSame = true; + const SwFormat *pRefFormat = nullptr; + const SwFormat *pRefFormatScript = nullptr; + switch( nDeep ) + { + case CSS1_FMT_ISTAG: + pRefFormat = SwHTMLWriter::GetTemplateFormat( nRefPoolId, pTemplate == nullptr ? nullptr : &pTemplate->getIDocumentStylePoolAccess() ); + break; + case CSS1_FMT_CMPREF: + pRefFormat = SwHTMLWriter::GetTemplateFormat( nRefPoolId, pDoc ); + pRefFormatScript = SwHTMLWriter::GetTemplateFormat( nRefPoolId, pTemplate == nullptr ? nullptr : &pTemplate->getIDocumentStylePoolAccess() ); + bClearSame = false; + break; + default: + pRefFormat = SwHTMLWriter::GetParentFormat( rFormat, nDeep ); + pRefFormatScript = SwHTMLWriter::GetTemplateFormat( nRefPoolId, pTemplate == nullptr ? nullptr : &pTemplate->getIDocumentStylePoolAccess() ); + bSetDefaults = false; + break; + } + + if( pRefFormat ) + { + // subtract Item-Set of the Reference template (incl. its Parents) + SwHTMLWriter::SubtractItemSet( aItemSet, pRefFormat->GetAttrSet(), + bSetDefaults, bClearSame, + pRefFormatScript + ? &pRefFormatScript->GetAttrSet() + : nullptr ); + + if( !bCharFormat ) + { + const SvxULSpaceItem& rULItem = pRefFormat->GetULSpace(); + rWrt.m_nDfltTopMargin = rULItem.GetUpper(); + rWrt.m_nDfltBottomMargin = rULItem.GetLower(); + } + } + else if( CSS1_FMT_ISTAG==nDeep && !bCharFormat ) + { + // set Default-distance above and below (for the + // case that there is no reference template) + rWrt.m_nDfltTopMargin = 0; + rWrt.m_nDfltBottomMargin = HTML_PARSPACE; + if( USER_FMT & nPoolFormatId ) + { + // user templates + const OUString& aNm(rFormat.GetName()); + + if (aNm == "DD 1" || aNm == "DT 1") + rWrt.m_nDfltBottomMargin = 0; + else if (aNm == OOO_STRING_SVTOOLS_HTML_listing) + rWrt.m_nDfltBottomMargin = 0; + else if (aNm == OOO_STRING_SVTOOLS_HTML_preformtxt) + rWrt.m_nDfltBottomMargin = 0; + else if (aNm == OOO_STRING_SVTOOLS_HTML_xmp) + rWrt.m_nDfltBottomMargin = 0; + } + else + { + // Pool templates + switch( nPoolFormatId ) + { + case RES_POOLCOLL_HEADLINE1: + case RES_POOLCOLL_HEADLINE2: + case RES_POOLCOLL_HEADLINE3: + case RES_POOLCOLL_HEADLINE4: + case RES_POOLCOLL_HEADLINE5: + case RES_POOLCOLL_HEADLINE6: + rWrt.m_nDfltTopMargin = HTML_HEADSPACE; + break; + case RES_POOLCOLL_SEND_ADDRESS: + case RES_POOLCOLL_HTML_DT: + case RES_POOLCOLL_HTML_DD: + case RES_POOLCOLL_HTML_PRE: + rWrt.m_nDfltBottomMargin = 0; + break; + } + } + } + + // if nothing is to be exported ... + if( !aItemSet.Count() ) + return rWrt; + + // There is no support for script dependent hyperlinks by now. + bool bCheckForPseudo = false; + if( bCharFormat && + (RES_POOLCHR_INET_NORMAL==nRefPoolId || + RES_POOLCHR_INET_VISIT==nRefPoolId) ) + bCheckForPseudo = true; + + // export now the Attributes (incl. selector) + bool bHasScriptDependencies = false; + if( OutCSS1Rule( rWrt, aSelector, aItemSet, CSS1_FMT_ISTAG != nDeep, + bCheckForPseudo ) ) + { + if( bCharFormat ) + rWrt.m_aScriptTextStyles.insert( rFormat.GetName() ); + else + { + if( nPoolFormatId==RES_POOLCOLL_TEXT ) + rWrt.m_aScriptParaStyles.insert( pDoc->GetTextCollFromPool( RES_POOLCOLL_STANDARD, false )->GetName() ); + rWrt.m_aScriptParaStyles.insert( rFormat.GetName() ); + } + bHasScriptDependencies = true; + } + + // export Drop-Caps + if( const SwFormatDrop *pDrop = aItemSet.GetItemIfSet( RES_PARATR_DROP, false ) ) + { + OUString sOut = aSelector + + ":" + OStringToOUString( sCSS1_first_letter, RTL_TEXTENCODING_ASCII_US ); + OutCSS1DropCapRule( rWrt, sOut, *pDrop, CSS1_FMT_ISTAG != nDeep, bHasScriptDependencies ); + } + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SwPageDesc( SwHTMLWriter& rWrt, const SwPageDesc& rPageDesc, + IDocumentStylePoolAccess/*SwDoc*/ *pDoc, SwDoc *pTemplate, + sal_uInt16 nRefPoolId, bool bExtRef, + bool bPseudo ) +{ + const SwPageDesc* pRefPageDesc = nullptr; + if( !bExtRef ) + pRefPageDesc = pDoc->GetPageDescFromPool( nRefPoolId, false ); + else if( pTemplate ) + pRefPageDesc = pTemplate->getIDocumentStylePoolAccess().GetPageDescFromPool( nRefPoolId, false ); + + OUString aSelector = "@" + OStringToOUString( sCSS1_page, RTL_TEXTENCODING_ASCII_US ); + + if( bPseudo ) + { + std::string_view pPseudo; + switch( rPageDesc.GetPoolFormatId() ) + { + case RES_POOLPAGE_FIRST: pPseudo = sCSS1_first; break; + case RES_POOLPAGE_LEFT: pPseudo = sCSS1_left; break; + case RES_POOLPAGE_RIGHT: pPseudo = sCSS1_right; break; + } + if( !pPseudo.empty() ) + aSelector += ":" + OStringToOUString( pPseudo, RTL_TEXTENCODING_ASCII_US ); + } + + SwCSS1OutMode aMode( rWrt, CSS1_OUTMODE_RULE_ON|CSS1_OUTMODE_TEMPLATE, + &aSelector ); + + // Size: If the only difference is the Landscape-Flag, + // only export Portrait or Landscape. Otherwise export size. + bool bRefLandscape = pRefPageDesc && pRefPageDesc->GetLandscape(); + Size aRefSz; + const Size& rSz = rPageDesc.GetMaster().GetFrameSize().GetSize(); + if( pRefPageDesc ) + { + aRefSz = pRefPageDesc->GetMaster().GetFrameSize().GetSize(); + if( bRefLandscape != rPageDesc.GetLandscape() ) + { + tools::Long nTmp = aRefSz.Height(); + aRefSz.setHeight( aRefSz.Width() ); + aRefSz.setWidth( nTmp ); + } + } + + // TODO: Bad Hack: On the Page-Tabpage there are small rounding errors + // for the page size. Partially because of bug 25535, we stupidly still + // use the Size-Item from Dialog, even if nothing changed. + // Thus: once one visited the Page-Dialog and left it with OK, we get a + // new page size that then gets exported here. To avoid that, we allow + // here small deviations. + if( std::abs( rSz.Width() - aRefSz.Width() ) <= 2 && + std::abs( rSz.Height() - aRefSz.Height() ) <= 2 ) + { + if( bRefLandscape != rPageDesc.GetLandscape() ) + { + rWrt.OutCSS1_PropertyAscii( sCSS1_P_size, + rPageDesc.GetLandscape() ? sCSS1_PV_landscape + : sCSS1_PV_portrait ); + } + } + else + { + OStringBuffer sVal; + AddUnitPropertyValue(sVal, rSz.Width(), rWrt.GetCSS1Unit()); + sVal.append(' '); + AddUnitPropertyValue(sVal, rSz.Height(), rWrt.GetCSS1Unit()); + rWrt.OutCSS1_PropertyAscii(sCSS1_P_size, sVal); + } + + // Export the distance-Attributes as normally + const SwFrameFormat &rMaster = rPageDesc.GetMaster(); + SfxItemSetFixed<RES_LR_SPACE, RES_UL_SPACE> aItemSet( *rMaster.GetAttrSet().GetPool() ); + aItemSet.Set( rMaster.GetAttrSet() ); + + if( pRefPageDesc ) + { + SwHTMLWriter::SubtractItemSet( aItemSet, + pRefPageDesc->GetMaster().GetAttrSet(), + true ); + } + + OutCSS1_SvxULSpace_SvxLRSpace( rWrt, aItemSet ); + + // If for a Pseudo-Selector no Property had been set, we still + // have to export something, so that the corresponding template is + // created on the next import. + if( rWrt.m_bFirstCSS1Property && bPseudo ) + { + rWrt.OutNewLine(); + OString sTmp(OUStringToOString(aSelector, RTL_TEXTENCODING_UTF8)); + rWrt.Strm().WriteOString( sTmp ).WriteOString( " {" ); + rWrt.m_bFirstCSS1Property = false; + } + + if( !rWrt.m_bFirstCSS1Property ) + rWrt.Strm().WriteOString( sCSS1_rule_end ); + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SwFootnoteInfo( SwHTMLWriter& rWrt, const SwEndNoteInfo& rInfo, + SwDoc *pDoc, bool bHasNotes, bool bEndNote ) +{ + OUString aSelector; + + if( bHasNotes ) + { + aSelector = OUString::Concat(OOO_STRING_SVTOOLS_HTML_anchor ".") + + ( bEndNote ? std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_sdendnote_anc) + : std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_sdfootnote_anc) ); + SwCSS1OutMode aMode( rWrt, CSS1_OUTMODE_RULE|CSS1_OUTMODE_TEMPLATE, + &aSelector ); + rWrt.OutCSS1_PropertyAscii( sCSS1_P_font_size, + sHTML_FTN_fontheight ); + rWrt.Strm().WriteOString( sCSS1_rule_end ); + } + + const SwCharFormat *pSymCharFormat = rInfo.GetCharFormat( *pDoc ); + if( pSymCharFormat ) + { + const SfxItemSet& rFormatItemSet = pSymCharFormat->GetAttrSet(); + SfxItemSet aItemSet( *rFormatItemSet.GetPool(), rFormatItemSet.GetRanges() ); + aItemSet.Set( rFormatItemSet ); + + // If there are footnotes or endnotes, then all Attributes have to be + // exported, so that Netscape displays the document correctly. + // Otherwise it is sufficient, to export the differences to the + // footnote and endnote template. + if( !bHasNotes && rWrt.m_xTemplate.is() ) + { + SwFormat *pRefFormat = rWrt.m_xTemplate->getIDocumentStylePoolAccess().GetCharFormatFromPool( + static_cast< sal_uInt16 >(bEndNote ? RES_POOLCHR_ENDNOTE : RES_POOLCHR_FOOTNOTE) ); + if( pRefFormat ) + SwHTMLWriter::SubtractItemSet( aItemSet, pRefFormat->GetAttrSet(), + true ); + } + if( aItemSet.Count() ) + { + aSelector = OUString::Concat(OOO_STRING_SVTOOLS_HTML_anchor ".") + + ( bEndNote ? std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_sdendnote_sym) + : std::u16string_view( + u"" OOO_STRING_SVTOOLS_HTML_sdfootnote_sym)); + if( OutCSS1Rule( rWrt, aSelector, aItemSet, true, false )) + rWrt.m_aScriptTextStyles.insert( pSymCharFormat->GetName() ); + } + } + + return rWrt; +} + +SwHTMLWriter& OutCSS1_BodyTagStyleOpt( SwHTMLWriter& rWrt, const SfxItemSet& rItemSet ) +{ + SwCSS1OutMode aMode( rWrt, CSS1_OUTMODE_STYLE_OPT_ON | + CSS1_OUTMODE_ENCODE|CSS1_OUTMODE_BODY, nullptr ); + + // Only export the attributes of the page template. + // The attributes of the default paragraph template were + // considered already when exporting the paragraph template. + + const SfxPoolItem *pItem; + if( SfxItemState::SET == rItemSet.GetItemState( RES_BACKGROUND, false, + &pItem ) ) + { + OUString rEmbeddedGraphicName; + OutCSS1_SvxBrush( rWrt, *pItem, sw::Css1Background::Page, &rEmbeddedGraphicName ); + } + + if( SfxItemState::SET == rItemSet.GetItemState( RES_BOX, false, + &pItem )) + { + OutCSS1_SvxBox( rWrt, *pItem ); + } + + if( !rWrt.m_bFirstCSS1Property ) + { + // if a Property was exported as part of a Style-Option, + // the Option still needs to be finished + rWrt.Strm().WriteChar( '\"' ); + } + + return rWrt; +} + +SwHTMLWriter& OutCSS1_ParaTagStyleOpt( SwHTMLWriter& rWrt, const SfxItemSet& rItemSet, std::string_view rAdd ) +{ + SwCSS1OutMode aMode( rWrt, rWrt.m_nCSS1Script|CSS1_OUTMODE_STYLE_OPT | + CSS1_OUTMODE_ENCODE|CSS1_OUTMODE_PARA, nullptr ); + rWrt.OutCSS1_SfxItemSet( rItemSet, false, rAdd ); + + return rWrt; +} + +SwHTMLWriter& OutCSS1_TableBGStyleOpt( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + SwCSS1OutMode aMode( rWrt, CSS1_OUTMODE_STYLE_OPT_ON | + CSS1_OUTMODE_ENCODE| + CSS1_OUTMODE_TABLEBOX, nullptr ); + OutCSS1_SvxBrush( rWrt, rHt, sw::Css1Background::TableRow, nullptr ); + + if (!rWrt.m_bFirstCSS1Property) + rWrt.Strm().WriteChar(cCSS1_style_opt_end); + + return rWrt; +} + +SwHTMLWriter& OutCSS1_NumberBulletListStyleOpt( SwHTMLWriter& rWrt, const SwNumRule& rNumRule, + sal_uInt8 nLevel ) +{ + SwCSS1OutMode aMode( rWrt, CSS1_OUTMODE_STYLE_OPT | + CSS1_OUTMODE_ENCODE|CSS1_OUTMODE_PARA, nullptr ); + + const SwNumFormat& rNumFormat = rNumRule.Get( nLevel ); + + tools::Long nLSpace = rNumFormat.GetAbsLSpace(); + tools::Long nFirstLineOffset = rNumFormat.GetFirstLineOffset(); + tools::Long nDfltFirstLineOffset = HTML_NUMBER_BULLET_INDENT; + if( nLevel > 0 ) + { + const SwNumFormat& rPrevNumFormat = rNumRule.Get( nLevel-1 ); + nLSpace -= rPrevNumFormat.GetAbsLSpace(); + nDfltFirstLineOffset = rPrevNumFormat.GetFirstLineOffset(); + } + + if( rWrt.IsHTMLMode(HTMLMODE_LSPACE_IN_NUMBER_BULLET) && + nLSpace != HTML_NUMBER_BULLET_MARGINLEFT ) + rWrt.OutCSS1_UnitProperty( sCSS1_P_margin_left, nLSpace ); + + if( rWrt.IsHTMLMode(HTMLMODE_FRSTLINE_IN_NUMBER_BULLET) && + nFirstLineOffset != nDfltFirstLineOffset ) + rWrt.OutCSS1_UnitProperty( sCSS1_P_text_indent, nFirstLineOffset ); + + if( !rWrt.m_bFirstCSS1Property ) + rWrt.Strm().WriteChar( '\"' ); + + return rWrt; +} + +void SwHTMLWriter::OutCSS1_FrameFormatOptions( const SwFrameFormat& rFrameFormat, + HtmlFrmOpts nFrameOpts, + const SdrObject *pSdrObj, + const SfxItemSet *pItemSet ) +{ + SwCSS1OutMode aMode( *this, CSS1_OUTMODE_STYLE_OPT_ON | + CSS1_OUTMODE_ENCODE| + CSS1_OUTMODE_FRAME, nullptr ); + + const SwFormatHoriOrient& rHoriOri = rFrameFormat.GetHoriOrient(); + SvxLRSpaceItem aLRItem( rFrameFormat.GetLRSpace() ); + SvxULSpaceItem aULItem( rFrameFormat.GetULSpace() ); + if( nFrameOpts & HtmlFrmOpts::SAlign ) + { + const SwFormatAnchor& rAnchor = rFrameFormat.GetAnchor(); + switch( rAnchor.GetAnchorId() ) + { + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_CHAR: + if( text::RelOrientation::FRAME == rHoriOri.GetRelationOrient() || + text::RelOrientation::PRINT_AREA == rHoriOri.GetRelationOrient() ) + { + if( !(nFrameOpts & HtmlFrmOpts::Align) ) + { + // float + std::string_view pStr = text::HoriOrientation::RIGHT==rHoriOri.GetHoriOrient() + ? sCSS1_PV_right + : sCSS1_PV_left; + OutCSS1_PropertyAscii( sCSS1_P_float, pStr ); + } + break; + } + [[fallthrough]]; + + case RndStdIds::FLY_AT_PAGE: + case RndStdIds::FLY_AT_FLY: + { + // position + OutCSS1_PropertyAscii( sCSS1_P_position, sCSS1_PV_absolute ); + + // For top/left we need to subtract the distance to the frame + // from the position, as in CSS1 it is added to the position. + // This works also for automatically aligned frames, even that + // in this case Writer also adds the distance; because in this + // case the Orient-Attribute contains the correct position. + + // top + tools::Long nXPos=0, nYPos=0; + bool bOutXPos = false, bOutYPos = false; + if( RES_DRAWFRMFMT == rFrameFormat.Which() ) + { + OSL_ENSURE( pSdrObj, "Do not pass a SdrObject. Inefficient" ); + if( !pSdrObj ) + pSdrObj = rFrameFormat.FindSdrObject(); + OSL_ENSURE( pSdrObj, "Where is the SdrObject" ); + if( pSdrObj ) + { + Point aPos( pSdrObj->GetRelativePos() ); + nXPos = aPos.X(); + nYPos = aPos.Y(); + } + bOutXPos = bOutYPos = true; + } + else + { + bOutXPos = text::RelOrientation::CHAR != rHoriOri.GetRelationOrient(); + nXPos = text::HoriOrientation::NONE == rHoriOri.GetHoriOrient() + ? rHoriOri.GetPos() : 0; + + const SwFormatVertOrient& rVertOri = rFrameFormat.GetVertOrient(); + bOutYPos = text::RelOrientation::CHAR != rVertOri.GetRelationOrient(); + nYPos = text::VertOrientation::NONE == rVertOri.GetVertOrient() + ? rVertOri.GetPos() : 0; + } + + if( bOutYPos ) + { + if( IsHTMLMode( HTMLMODE_FLY_MARGINS) ) + { + nYPos -= aULItem.GetUpper(); + if( nYPos < 0 ) + { + aULItem.SetUpper( o3tl::narrowing<sal_uInt16>(aULItem.GetUpper() + nYPos) ); + nYPos = 0; + } + } + + OutCSS1_UnitProperty( sCSS1_P_top, nYPos ); + } + + if( bOutXPos ) + { + // left + if( IsHTMLMode( HTMLMODE_FLY_MARGINS) ) + { + nXPos -= aLRItem.GetLeft(); + if( nXPos < 0 ) + { + aLRItem.SetLeft( o3tl::narrowing<sal_uInt16>(aLRItem.GetLeft() + nXPos) ); + nXPos = 0; + } + } + + OutCSS1_UnitProperty( sCSS1_P_left, nXPos ); + } + } + break; + + default: + ; + } + } + + // width/height + if( nFrameOpts & HtmlFrmOpts::SSize ) + { + if( RES_DRAWFRMFMT == rFrameFormat.Which() ) + { + OSL_ENSURE( pSdrObj, "Do not pass a SdrObject. Inefficient" ); + if( !pSdrObj ) + pSdrObj = rFrameFormat.FindSdrObject(); + OSL_ENSURE( pSdrObj, "Where is the SdrObject" ); + if( pSdrObj ) + { + Size aTwipSz( pSdrObj->GetLogicRect().GetSize() ); + if( nFrameOpts & HtmlFrmOpts::SWidth ) + { + if( nFrameOpts & HtmlFrmOpts::SPixSize ) + OutCSS1_PixelProperty( sCSS1_P_width, aTwipSz.Width() ); + else + OutCSS1_UnitProperty( sCSS1_P_width, aTwipSz.Width() ); + } + if( nFrameOpts & HtmlFrmOpts::SHeight ) + { + if( nFrameOpts & HtmlFrmOpts::SPixSize ) + OutCSS1_PixelProperty( sCSS1_P_height, aTwipSz.Height() ); + else + OutCSS1_UnitProperty( sCSS1_P_height, aTwipSz.Height() ); + } + } + } + else + { + OSL_ENSURE( HtmlFrmOpts::AbsSize & nFrameOpts, + "Export absolute size" ); + OSL_ENSURE( HtmlFrmOpts::AnySize & nFrameOpts, + "Export every size" ); + Css1FrameSize nMode = Css1FrameSize::NONE; + if( nFrameOpts & HtmlFrmOpts::SWidth ) + nMode |= Css1FrameSize::Width; + if( nFrameOpts & HtmlFrmOpts::SHeight ) + nMode |= Css1FrameSize::MinHeight|Css1FrameSize::FixHeight; + if( nFrameOpts & HtmlFrmOpts::SPixSize ) + nMode |= Css1FrameSize::Pixel; + + OutCSS1_SwFormatFrameSize( *this, rFrameFormat.GetFrameSize(), nMode ); + } + } + + const SfxItemSet& rItemSet = rFrameFormat.GetAttrSet(); + // margin-* + if( (nFrameOpts & HtmlFrmOpts::SSpace) && + IsHTMLMode( HTMLMODE_FLY_MARGINS) ) + { + const SvxLRSpaceItem *pLRItem = nullptr; + const SvxULSpaceItem *pULItem = nullptr; + if( SfxItemState::SET == rItemSet.GetItemState( RES_LR_SPACE ) ) + pLRItem = &aLRItem; + if( SfxItemState::SET == rItemSet.GetItemState( RES_UL_SPACE ) ) + pULItem = &aULItem; + if( pLRItem || pULItem ) + OutCSS1_SvxULSpace_SvxLRSpace( *this, pULItem, pLRItem ); + } + + // border + if( nFrameOpts & HtmlFrmOpts::SBorder ) + { + const SfxPoolItem* pItem; + if( nFrameOpts & HtmlFrmOpts::SNoBorder ) + OutCSS1_SvxBox( *this, rFrameFormat.GetBox() ); + else if( SfxItemState::SET==rItemSet.GetItemState( RES_BOX, true, &pItem ) ) + OutCSS1_SvxBox( *this, *pItem ); + } + + // background (if, then the color must be set also) + if( nFrameOpts & HtmlFrmOpts::SBackground ) + OutCSS1_FrameFormatBackground( rFrameFormat ); + + if( pItemSet ) + OutCSS1_SfxItemSet( *pItemSet, false ); + + if( !m_bFirstCSS1Property ) + Strm().WriteChar( '\"' ); +} + +void SwHTMLWriter::OutCSS1_TableFrameFormatOptions( const SwFrameFormat& rFrameFormat ) +{ + SwCSS1OutMode aMode( *this, CSS1_OUTMODE_STYLE_OPT_ON | + CSS1_OUTMODE_ENCODE| + CSS1_OUTMODE_TABLE, nullptr ); + + const SfxPoolItem *pItem; + const SfxItemSet& rItemSet = rFrameFormat.GetAttrSet(); + if( SfxItemState::SET==rItemSet.GetItemState( RES_BACKGROUND, false, &pItem ) ) + OutCSS1_SvxBrush( *this, *pItem, sw::Css1Background::Table, nullptr ); + + if( IsHTMLMode( HTMLMODE_PRINT_EXT ) ) + OutCSS1_SvxFormatBreak_SwFormatPDesc_SvxFormatKeep( *this, rItemSet, false ); + + if( SfxItemState::SET==rItemSet.GetItemState( RES_LAYOUT_SPLIT, false, &pItem ) ) + OutCSS1_SwFormatLayoutSplit( *this, *pItem ); + + if (mbXHTML) + { + sal_Int16 eTabHoriOri = rFrameFormat.GetHoriOrient().GetHoriOrient(); + if (eTabHoriOri == text::HoriOrientation::CENTER) + { + // Emit XHTML's center using inline CSS. + OutCSS1_Property(sCSS1_P_margin_left, "auto", nullptr, sw::Css1Background::Table); + OutCSS1_Property(sCSS1_P_margin_right, "auto", nullptr, sw::Css1Background::Table); + } + } + + if( !m_bFirstCSS1Property ) + Strm().WriteChar( '\"' ); +} + +void SwHTMLWriter::OutCSS1_TableCellBordersAndBG(SwFrameFormat const& rFrameFormat, const SvxBrushItem *pBrushItem) +{ + SwCSS1OutMode const aMode( *this, + CSS1_OUTMODE_STYLE_OPT_ON|CSS1_OUTMODE_ENCODE|CSS1_OUTMODE_TABLEBOX, nullptr ); + if (pBrushItem) + OutCSS1_SvxBrush(*this, *pBrushItem, sw::Css1Background::TableCell, nullptr); + OutCSS1_SvxBox(*this, rFrameFormat.GetBox()); + if (!m_bFirstCSS1Property) + Strm().WriteChar(cCSS1_style_opt_end); +} + +void SwHTMLWriter::OutCSS1_SectionFormatOptions( const SwFrameFormat& rFrameFormat, const SwFormatCol *pCol ) +{ + SwCSS1OutMode aMode( *this, CSS1_OUTMODE_STYLE_OPT_ON | + CSS1_OUTMODE_ENCODE| + CSS1_OUTMODE_SECTION, nullptr ); + + const SfxPoolItem *pItem; + const SfxItemSet& rItemSet = rFrameFormat.GetAttrSet(); + if( SfxItemState::SET==rItemSet.GetItemState( RES_BACKGROUND, false, &pItem ) ) + OutCSS1_SvxBrush( *this, *pItem, sw::Css1Background::Section, nullptr ); + + if (mbXHTML) + { + SvxFrameDirection nDir = GetHTMLDirection(rFrameFormat.GetAttrSet()); + OString sConvertedDirection = convertDirection(nDir); + if (!sConvertedDirection.isEmpty()) + { + OutCSS1_Property(sCSS1_P_dir, sConvertedDirection, nullptr, + sw::Css1Background::Section); + } + } + + if (pCol) + { + OString sColumnCount(OString::number(static_cast<sal_Int32>(pCol->GetNumCols()))); + OutCSS1_PropertyAscii(sCSS1_P_column_count, sColumnCount); + } + + if( !m_bFirstCSS1Property ) + Strm().WriteChar( '\"' ); +} + +static bool OutCSS1_FrameFormatBrush( SwHTMLWriter& rWrt, + const SvxBrushItem& rBrushItem ) +{ + bool bWritten = false; + /// output brush of frame format, if its background color is not "no fill"/"auto fill" + /// or it has a background graphic. + if( rBrushItem.GetColor() != COL_TRANSPARENT || + !rBrushItem.GetGraphicLink().isEmpty() || + 0 != rBrushItem.GetGraphicPos() ) + { + OutCSS1_SvxBrush( rWrt, rBrushItem, sw::Css1Background::Fly, nullptr ); + bWritten = true; + } + return bWritten; +} + +void SwHTMLWriter::OutCSS1_FrameFormatBackground( const SwFrameFormat& rFrameFormat ) +{ + // If the frame itself has a background, then export. + if( OutCSS1_FrameFormatBrush( *this, *rFrameFormat.makeBackgroundBrushItem() ) ) + return; + + // If the frame is not linked to a page, we use the background of the anchor. + const SwFormatAnchor& rAnchor = rFrameFormat.GetAnchor(); + RndStdIds eAnchorId = rAnchor.GetAnchorId(); + const SwNode *pAnchorNode = rAnchor.GetAnchorNode(); + if (RndStdIds::FLY_AT_PAGE != eAnchorId && pAnchorNode) + { + if( pAnchorNode->IsContentNode() ) + { + // If the frame is linked to a content-node, + // we take the background of the content-node, if it has one. + if( OutCSS1_FrameFormatBrush( *this, + pAnchorNode->GetContentNode()->GetSwAttrSet().GetBackground()) ) + return; + + // Otherwise we also could be in a table + const SwTableNode *pTableNd = pAnchorNode->FindTableNode(); + if( pTableNd ) + { + const SwStartNode *pBoxSttNd = pAnchorNode->FindTableBoxStartNode(); + const SwTableBox *pBox = + pTableNd->GetTable().GetTableBox( pBoxSttNd->GetIndex() ); + + // If the box has a background, we take it. + if( OutCSS1_FrameFormatBrush( *this, + *pBox->GetFrameFormat()->makeBackgroundBrushItem() ) ) + return; + + // Otherwise we use that of the lines + const SwTableLine *pLine = pBox->GetUpper(); + while( pLine ) + { + if( OutCSS1_FrameFormatBrush( *this, + *pLine->GetFrameFormat()->makeBackgroundBrushItem() ) ) + return; + pBox = pLine->GetUpper(); + pLine = pBox ? pBox->GetUpper() : nullptr; + } + + // If there was none either, we use the background of the table. + if( OutCSS1_FrameFormatBrush( *this, + *pTableNd->GetTable().GetFrameFormat()->makeBackgroundBrushItem() ) ) + return; + } + + } + + // If the anchor is again in a Fly-Frame, use the background of the Fly-Frame. + const SwFrameFormat *pFrameFormat = pAnchorNode->GetFlyFormat(); + if( pFrameFormat ) + { + OutCSS1_FrameFormatBackground( *pFrameFormat ); + return; + } + } + + // At last there is the background of the page, and as the final rescue + // the value of the Config. + OSL_ENSURE( m_pCurrPageDesc, "no page template found" ); + if( OutCSS1_FrameFormatBrush( *this, + *m_pCurrPageDesc->GetMaster().makeBackgroundBrushItem() ) ) + return; + + Color aColor( COL_WHITE ); + + // The background color is normally only used in Browse-Mode. + // We always use it for a HTML document, but for a text document + // only if viewed in Browse-Mode. + if( m_pDoc->getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE) || + m_pDoc->getIDocumentSettingAccess().get(DocumentSettingId::BROWSE_MODE)) + { + SwViewShell *pVSh = m_pDoc->getIDocumentLayoutAccess().GetCurrentViewShell(); + if ( pVSh && + COL_TRANSPARENT != pVSh->GetViewOptions()->GetRetoucheColor()) + aColor = pVSh->GetViewOptions()->GetRetoucheColor(); + } + + OutCSS1_PropertyAscii(sCSS1_P_background, GetCSS1_Color(aColor)); +} + +static SwHTMLWriter& OutCSS1_SvxTextLn_SvxCrOut_SvxBlink( SwHTMLWriter& rWrt, + const SvxUnderlineItem *pUItem, + const SvxOverlineItem *pOItem, + const SvxCrossedOutItem *pCOItem, + const SvxBlinkItem *pBItem ) +{ + bool bNone = false; + OStringBuffer sOut; + + if( pUItem ) + { + switch( pUItem->GetLineStyle() ) + { + case LINESTYLE_NONE: + bNone = true; + break; + case LINESTYLE_DONTKNOW: + break; + default: + if( !rWrt.IsCSS1Source( CSS1_OUTMODE_PARA ) ) + { + // this also works in HTML does not need to be written as + // a STYLE-Options, and must not be written as Hint + OSL_ENSURE( !rWrt.IsCSS1Source(CSS1_OUTMODE_HINT) || rWrt.mbReqIF, + "write underline as Hint?" ); + sOut.append(sCSS1_PV_underline); + } + break; + } + } + + if( pOItem ) + { + switch( pOItem->GetLineStyle() ) + { + case LINESTYLE_NONE: + bNone = true; + break; + case LINESTYLE_DONTKNOW: + break; + default: + if( !rWrt.IsCSS1Source( CSS1_OUTMODE_PARA ) ) + { + // this also works in HTML does not need to be written as + // a STYLE-Options, and must not be written as Hint + OSL_ENSURE( !rWrt.IsCSS1Source(CSS1_OUTMODE_HINT), + "write overline as Hint?" ); + if (!sOut.isEmpty()) + sOut.append(' '); + sOut.append(sCSS1_PV_overline); + } + break; + } + } + + if( pCOItem ) + { + switch( pCOItem->GetStrikeout() ) + { + case STRIKEOUT_NONE: + bNone = true; + break; + case STRIKEOUT_DONTKNOW: + break; + default: + if( !rWrt.IsCSS1Source( CSS1_OUTMODE_PARA ) ) + { + // this also works in HTML does not need to be written as + // a STYLE-Options, and must not be written as Hint + OSL_ENSURE( !rWrt.IsCSS1Source(CSS1_OUTMODE_HINT) || rWrt.mbReqIF, + "write crossedOut as Hint?" ); + if (!sOut.isEmpty()) + sOut.append(' '); + sOut.append(sCSS1_PV_line_through); + } + break; + } + } + + if( pBItem ) + { + if( !pBItem->GetValue() ) + { + bNone = true; + } + else if( !rWrt.IsCSS1Source( CSS1_OUTMODE_PARA ) ) + { + // this also works in HTML does not need to be written as + // a STYLE-Options, and must not be written as Hint + OSL_ENSURE( !rWrt.IsCSS1Source(CSS1_OUTMODE_HINT), + "write blink as Hint?" ); + if (!sOut.isEmpty()) + sOut.append(' '); + sOut.append(sCSS1_PV_blink); + } + } + + if (!sOut.isEmpty()) + rWrt.OutCSS1_PropertyAscii( sCSS1_P_text_decoration, sOut ); + else if( bNone ) + rWrt.OutCSS1_PropertyAscii( sCSS1_P_text_decoration, sCSS1_PV_none ); + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxCaseMap( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + switch( static_cast<const SvxCaseMapItem&>(rHt).GetCaseMap() ) + { + case SvxCaseMap::NotMapped: + rWrt.OutCSS1_PropertyAscii( sCSS1_P_font_variant, sCSS1_PV_normal ); + break; + case SvxCaseMap::SmallCaps: + rWrt.OutCSS1_PropertyAscii( sCSS1_P_font_variant, sCSS1_PV_small_caps ); + break; + case SvxCaseMap::Uppercase: + rWrt.OutCSS1_PropertyAscii( sCSS1_P_text_transform, sCSS1_PV_uppercase ); + break; + case SvxCaseMap::Lowercase: + rWrt.OutCSS1_PropertyAscii( sCSS1_P_text_transform, sCSS1_PV_lowercase ); + break; + case SvxCaseMap::Capitalize: + rWrt.OutCSS1_PropertyAscii( sCSS1_P_text_transform, sCSS1_PV_capitalize ); + break; + default: + ; + } + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxColor( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + // Colors do not need to be exported for Style-Option. + if( rWrt.IsCSS1Source( CSS1_OUTMODE_PARA ) && + !rWrt.m_bCfgPreferStyles ) + return rWrt; + OSL_ENSURE( !rWrt.IsCSS1Source(CSS1_OUTMODE_HINT), + "write color as Hint?" ); + + Color aColor( static_cast<const SvxColorItem&>(rHt).GetValue() ); + if( COL_AUTO == aColor ) + aColor = COL_BLACK; + + rWrt.OutCSS1_PropertyAscii(sCSS1_P_color, GetCSS1_Color(aColor)); + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxCrossedOut( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + // This function only exports Hints! + // Otherwise OutCSS1_SvxTextLn_SvxCrOut_SvxBlink() is called directly. + + if( rWrt.IsCSS1Source(CSS1_OUTMODE_HINT) ) + OutCSS1_SvxTextLn_SvxCrOut_SvxBlink( rWrt, + nullptr, nullptr, static_cast<const SvxCrossedOutItem *>(&rHt), nullptr ); + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxFont( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + // No need to export Fonts for the Style-Option. + if( rWrt.IsCSS1Source( CSS1_OUTMODE_PARA ) ) + return rWrt; + + sal_uInt16 nScript = CSS1_OUTMODE_WESTERN; + switch( rHt.Which() ) + { + case RES_CHRATR_CJK_FONT: nScript = CSS1_OUTMODE_CJK; break; + case RES_CHRATR_CTL_FONT: nScript = CSS1_OUTMODE_CTL; break; + } + if( !rWrt.IsCSS1Script( nScript ) ) + return rWrt; + + OSL_ENSURE( !rWrt.IsCSS1Source(CSS1_OUTMODE_HINT), + "write Font as Hint?" ); + + OUString sOut; + // MS IE3b1 has problems with single quotes + sal_uInt16 nMode = rWrt.m_nCSS1OutMode & CSS1_OUTMODE_ANY_ON; + sal_Unicode cQuote = nMode == CSS1_OUTMODE_RULE_ON ? '\"' : '\''; + SwHTMLWriter::PrepareFontList( static_cast<const SvxFontItem&>(rHt), sOut, cQuote, + true ); + + rWrt.OutCSS1_Property( sCSS1_P_font_family, sOut ); + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxFontHeight( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + // Font-Height need not be exported in the Style-Option. + // For Drop-Caps another Font-Size is exported. + if( rWrt.IsCSS1Source( CSS1_OUTMODE_PARA ) || + rWrt.IsCSS1Source( CSS1_OUTMODE_DROPCAP ) ) + return rWrt; + + sal_uInt16 nScript = CSS1_OUTMODE_WESTERN; + switch( rHt.Which() ) + { + case RES_CHRATR_CJK_FONTSIZE: nScript = CSS1_OUTMODE_CJK; break; + case RES_CHRATR_CTL_FONTSIZE: nScript = CSS1_OUTMODE_CTL; break; + } + if( !rWrt.IsCSS1Script( nScript ) ) + return rWrt; + + sal_uInt32 nHeight = static_cast<const SvxFontHeightItem&>(rHt).GetHeight(); + OString sHeight(OString::number(nHeight/20) + sCSS1_UNIT_pt); + rWrt.OutCSS1_PropertyAscii(sCSS1_P_font_size, sHeight); + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxPosture( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + sal_uInt16 nScript = CSS1_OUTMODE_WESTERN; + switch( rHt.Which() ) + { + case RES_CHRATR_CJK_POSTURE: nScript = CSS1_OUTMODE_CJK; break; + case RES_CHRATR_CTL_POSTURE: nScript = CSS1_OUTMODE_CTL; break; + } + if( !rWrt.IsCSS1Script( nScript ) ) + return rWrt; + + std::string_view pStr; + switch( static_cast<const SvxPostureItem&>(rHt).GetPosture() ) + { + case ITALIC_NONE: pStr = sCSS1_PV_normal; break; + case ITALIC_OBLIQUE: pStr = sCSS1_PV_oblique; break; + case ITALIC_NORMAL: + if( !rWrt.IsCSS1Source( CSS1_OUTMODE_PARA ) ) + { + // this also works in HTML does not need to be written as + // a STYLE-Options, and must not be written as Hint + OSL_ENSURE( !rWrt.IsCSS1Source(CSS1_OUTMODE_HINT), + "write italic as Hint?" ); + pStr = sCSS1_PV_italic; + } + break; + default: + ; + } + + if( !pStr.empty() ) + rWrt.OutCSS1_PropertyAscii( sCSS1_P_font_style, pStr ); + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxKerning( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + sal_Int16 nValue = static_cast<const SvxKerningItem&>(rHt).GetValue(); + if( nValue ) + { + OStringBuffer sOut; + if( nValue < 0 ) + { + sOut.append('-'); + nValue = -nValue; + } + + // Width as n.n pt + nValue = (nValue + 1) / 2; // 1/10pt + sOut.append(OString::number(nValue / 10) + "." + OString::number(nValue % 10) + + sCSS1_UNIT_pt); + + rWrt.OutCSS1_PropertyAscii(sCSS1_P_letter_spacing, sOut); + sOut.setLength(0); + } + else + { + rWrt.OutCSS1_PropertyAscii( sCSS1_P_letter_spacing, + sCSS1_PV_normal ); + } + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxLanguage( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + // Only export Language rules + if( rWrt.IsCSS1Source( CSS1_OUTMODE_PARA ) ) + return rWrt; + + sal_uInt16 nScript = CSS1_OUTMODE_WESTERN; + switch( rHt.Which() ) + { + case RES_CHRATR_CJK_LANGUAGE: nScript = CSS1_OUTMODE_CJK; break; + case RES_CHRATR_CTL_LANGUAGE: nScript = CSS1_OUTMODE_CTL; break; + } + if( !rWrt.IsCSS1Script( nScript ) ) + return rWrt; + + OSL_ENSURE( !rWrt.IsCSS1Source(CSS1_OUTMODE_HINT), + "write Language as Hint?" ); + + LanguageType eLang = static_cast<const SvxLanguageItem &>(rHt).GetLanguage(); + if( LANGUAGE_DONTKNOW == eLang ) + return rWrt; + + OUString sOut = LanguageTag::convertToBcp47( eLang ); + + rWrt.OutCSS1_Property( sCSS1_P_so_language, sOut ); + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxUnderline( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + // This function only exports Hints! + // Otherwise OutCSS1_SvxTextLn_SvxCrOut_SvxBlink() is called directly. + + if( rWrt.IsCSS1Source(CSS1_OUTMODE_HINT) ) + OutCSS1_SvxTextLn_SvxCrOut_SvxBlink( rWrt, + static_cast<const SvxUnderlineItem *>(&rHt), nullptr, nullptr, nullptr ); + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxOverline( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + // This function only exports Hints! + // Otherwise OutCSS1_SvxTextLn_SvxCrOut_SvxBlink() is called directly. + + if( rWrt.IsCSS1Source(CSS1_OUTMODE_HINT) ) + OutCSS1_SvxTextLn_SvxCrOut_SvxBlink( rWrt, + nullptr, static_cast<const SvxOverlineItem *>(&rHt), nullptr, nullptr ); + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxHidden( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + if ( static_cast<const SvxCharHiddenItem&>(rHt).GetValue() ) + rWrt.OutCSS1_PropertyAscii( sCSS1_P_display, sCSS1_PV_none ); + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxFontWeight( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + sal_uInt16 nScript = CSS1_OUTMODE_WESTERN; + switch( rHt.Which() ) + { + case RES_CHRATR_CJK_WEIGHT: nScript = CSS1_OUTMODE_CJK; break; + case RES_CHRATR_CTL_WEIGHT: nScript = CSS1_OUTMODE_CTL; break; + } + if( !rWrt.IsCSS1Script( nScript ) ) + return rWrt; + + std::string_view pStr; + switch( static_cast<const SvxWeightItem&>(rHt).GetWeight() ) + { + case WEIGHT_ULTRALIGHT: pStr = sCSS1_PV_extra_light; break; + case WEIGHT_LIGHT: pStr = sCSS1_PV_light; break; + case WEIGHT_SEMILIGHT: pStr = sCSS1_PV_demi_light; break; + case WEIGHT_NORMAL: pStr = sCSS1_PV_normal; break; + case WEIGHT_SEMIBOLD: pStr = sCSS1_PV_demi_bold; break; + case WEIGHT_BOLD: + if( !rWrt.IsCSS1Source( CSS1_OUTMODE_PARA ) ) + { + // this also works in HTML does not need to be written as + // a STYLE-Options, and must not be written as Hint + OSL_ENSURE( !rWrt.IsCSS1Source(CSS1_OUTMODE_HINT), + "write bold as Hint?" ); + pStr = sCSS1_PV_bold; + } + break; + case WEIGHT_ULTRABOLD: pStr = sCSS1_PV_extra_bold; break; + default: + pStr = sCSS1_PV_normal; + } + + if( !pStr.empty() ) + rWrt.OutCSS1_PropertyAscii( sCSS1_P_font_weight, pStr ); + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxBlink( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + // This function only exports Hints! + // Otherwise OutCSS1_SvxTextLn_SvxCrOut_SvxBlink() is called directly. + + if( rWrt.IsCSS1Source(CSS1_OUTMODE_HINT) ) + OutCSS1_SvxTextLn_SvxCrOut_SvxBlink( rWrt, + nullptr, nullptr, nullptr, static_cast<const SvxBlinkItem *>(&rHt) ); + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxLineSpacing( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + // Netscape4 has big problems with cell heights if the line spacing is + // changed within a table and the width of the table is not calculated + // automatically (== if there is a WIDTH-Option) + if( rWrt.m_bOutTable && rWrt.m_bCfgNetscape4 ) + return rWrt; + + const SvxLineSpacingItem& rLSItem = static_cast<const SvxLineSpacingItem&>(rHt); + + sal_uInt16 nHeight = 0; + sal_uInt16 nPercentHeight = 0; + SvxLineSpaceRule eLineSpace = rLSItem.GetLineSpaceRule(); + switch( rLSItem.GetInterLineSpaceRule() ) + { + case SvxInterLineSpaceRule::Off: + case SvxInterLineSpaceRule::Fix: + { + switch( eLineSpace ) + { + case SvxLineSpaceRule::Min: + case SvxLineSpaceRule::Fix: + nHeight = rLSItem.GetLineHeight(); + break; + case SvxLineSpaceRule::Auto: + nPercentHeight = 100; + break; + default: + ; + } + } + break; + case SvxInterLineSpaceRule::Prop: + nPercentHeight = rLSItem.GetPropLineSpace(); + break; + + default: + ; + } + + if( nHeight ) + rWrt.OutCSS1_UnitProperty( sCSS1_P_line_height, static_cast<tools::Long>(nHeight) ); + else if( nPercentHeight && + !(nPercentHeight < 115 && rWrt.m_bParaDotLeaders )) // avoid HTML scrollbars and missing descenders + { + OString sHeight(OString::number(nPercentHeight) + "%"); + rWrt.OutCSS1_PropertyAscii(sCSS1_P_line_height, sHeight); + } + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxAdjust( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + // Export Alignment in Style-Option only if the Tag does not allow ALIGN=xxx + if( rWrt.IsCSS1Source( CSS1_OUTMODE_PARA ) && + !rWrt.m_bNoAlign) + return rWrt; + + std::string_view pStr; + switch( static_cast<const SvxAdjustItem&>(rHt).GetAdjust() ) + { + case SvxAdjust::Left: pStr = sCSS1_PV_left; break; + case SvxAdjust::Right: pStr = sCSS1_PV_right; break; + case SvxAdjust::Block: pStr = sCSS1_PV_justify; break; + case SvxAdjust::Center: pStr = sCSS1_PV_center; break; + default: + ; + } + + if( !pStr.empty() ) + rWrt.OutCSS1_PropertyAscii( sCSS1_P_text_align, pStr ); + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxFormatSplit( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + std::string_view pStr = static_cast<const SvxFormatSplitItem&>(rHt).GetValue() + ? sCSS1_PV_auto + : sCSS1_PV_avoid; + rWrt.OutCSS1_PropertyAscii( sCSS1_P_page_break_inside, pStr ); + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SwFormatLayoutSplit( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + std::string_view pStr = static_cast<const SwFormatLayoutSplit&>(rHt).GetValue() + ? sCSS1_PV_auto + : sCSS1_PV_avoid; + rWrt.OutCSS1_PropertyAscii( sCSS1_P_page_break_inside, pStr ); + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxWidows( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + OString aStr(OString::number(static_cast<const SvxWidowsItem&>(rHt).GetValue())); + rWrt.OutCSS1_PropertyAscii( sCSS1_P_widows, aStr ); + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxOrphans( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + OString aStr(OString::number(static_cast<const SvxOrphansItem&>(rHt).GetValue())); + rWrt.OutCSS1_PropertyAscii( sCSS1_P_orphans, aStr ); + + return rWrt; +} + +static void OutCSS1_SwFormatDropAttrs( SwHTMLWriter& rHWrt, + const SwFormatDrop& rDrop, + const SfxItemSet *pCharFormatItemSet ) +{ + // Text flows around on right side + rHWrt.OutCSS1_PropertyAscii( sCSS1_P_float, sCSS1_PV_left ); + + // number of lines -> use % for Font-Height! + OString sOut(OString::number(rDrop.GetLines()*100) + "%"); + rHWrt.OutCSS1_PropertyAscii(sCSS1_P_font_size, sOut); + + // distance to Text = right margin + sal_uInt16 nDistance = rDrop.GetDistance(); + if( nDistance > 0 ) + rHWrt.OutCSS1_UnitProperty( sCSS1_P_margin_right, nDistance ); + + const SwCharFormat *pDCCharFormat = rDrop.GetCharFormat(); + if( pCharFormatItemSet ) + rHWrt.OutCSS1_SfxItemSet( *pCharFormatItemSet ); + else if( pDCCharFormat ) + rHWrt.OutCSS1_SfxItemSet( pDCCharFormat->GetAttrSet() ); + else if( (rHWrt.m_nCSS1OutMode & CSS1_OUTMODE_ANY_OFF) == CSS1_OUTMODE_RULE_OFF ) + rHWrt.Strm().WriteOString( sCSS1_rule_end ); + +} + +static SwHTMLWriter& OutCSS1_SwFormatDrop( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + // never export as an Option of a paragraph, but only as Hints + if( !rWrt.IsCSS1Source(CSS1_OUTMODE_HINT) ) + return rWrt; + + if( rWrt.m_bTagOn ) + { + SwCSS1OutMode aMode( rWrt, + rWrt.m_nCSS1Script|CSS1_OUTMODE_SPAN_TAG1_ON|CSS1_OUTMODE_ENCODE| + CSS1_OUTMODE_DROPCAP, nullptr ); + + OutCSS1_SwFormatDropAttrs( rWrt, static_cast<const SwFormatDrop&>(rHt) ); + // A "> is already printed by the calling OutCSS1_HintAsSpanTag. + } + else + { + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_span), false ); + } + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SwFormatFrameSize( SwHTMLWriter& rWrt, const SfxPoolItem& rHt, + Css1FrameSize nMode ) +{ + const SwFormatFrameSize& rFSItem = static_cast<const SwFormatFrameSize&>(rHt); + + if( nMode & Css1FrameSize::Width ) + { + sal_uInt8 nPercentWidth = rFSItem.GetWidthPercent(); + if( nPercentWidth ) + { + OString sOut(OString::number(nPercentWidth) + "%"); + rWrt.OutCSS1_PropertyAscii(sCSS1_P_width, sOut); + } + else if( nMode & Css1FrameSize::Pixel ) + { + rWrt.OutCSS1_PixelProperty( sCSS1_P_width, + rFSItem.GetSize().Width() ); + } + else + { + rWrt.OutCSS1_UnitProperty( sCSS1_P_width, + rFSItem.GetSize().Width() ); + } + } + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxFirstLineIndent(SwHTMLWriter & rWrt, SfxPoolItem const& rHt) +{ + const SvxFirstLineIndentItem & rFirstLine(static_cast<const SvxFirstLineIndentItem&>(rHt)); + + // No Export of a firm attribute is needed if the new values + // match that of the current template + + // The LineIndent of the first line might contain the room for numbering + tools::Long nFirstLineIndent = static_cast<tools::Long>(rFirstLine.GetTextFirstLineOffset()) + - rWrt.m_nFirstLineIndent; + if (rWrt.m_nDfltFirstLineIndent != nFirstLineIndent) + { + rWrt.OutCSS1_UnitProperty(sCSS1_P_text_indent, nFirstLineIndent); + } + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxTextLeftMargin(SwHTMLWriter & rWrt, SfxPoolItem const& rHt) +{ + const SvxTextLeftMarginItem& rLeftMargin(static_cast<const SvxTextLeftMarginItem&>(rHt)); + + // No Export of a firm attribute is needed if the new values + // match that of the current template + + // A left margin can exist because of a list nearby + tools::Long nLeftMargin = rLeftMargin.GetTextLeft() - rWrt.m_nLeftMargin; + if (rWrt.m_nDfltLeftMargin != nLeftMargin) + { + rWrt.OutCSS1_UnitProperty(sCSS1_P_margin_left, nLeftMargin); + + // max-width = max-width - margin-left for TOC paragraphs with dot leaders + if (rWrt.m_bParaDotLeaders) + rWrt.OutCSS1_UnitProperty(sCSS1_P_max_width, tools::Long(DOT_LEADERS_MAX_WIDTH/2.54*72*20) - nLeftMargin); + + } + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxRightMargin(SwHTMLWriter & rWrt, SfxPoolItem const& rHt) +{ + const SvxRightMarginItem& rRightMargin(static_cast<const SvxRightMarginItem&>(rHt)); + + // No Export of a firm attribute is needed if the new values + // match that of the current template + + if (rWrt.m_nDfltRightMargin != rRightMargin.GetRight()) + { + rWrt.OutCSS1_UnitProperty(sCSS1_P_margin_right, rRightMargin.GetRight()); + } + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxLRSpace( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + const SvxLRSpaceItem& rLRItem = static_cast<const SvxLRSpaceItem&>(rHt); + + // No Export of a firm attribute is needed if the new values + // match that of the current template + + // A left margin can exist because of a list nearby + tools::Long nLeftMargin = rLRItem.GetTextLeft() - rWrt.m_nLeftMargin; + if( rWrt.m_nDfltLeftMargin != nLeftMargin ) + { + rWrt.OutCSS1_UnitProperty( sCSS1_P_margin_left, nLeftMargin ); + + // max-width = max-width - margin-left for TOC paragraphs with dot leaders + if( rWrt.m_bParaDotLeaders ) + rWrt.OutCSS1_UnitProperty( sCSS1_P_max_width, tools::Long(DOT_LEADERS_MAX_WIDTH/2.54*72*20) - nLeftMargin ); + + } + + if( rWrt.m_nDfltRightMargin != rLRItem.GetRight() ) + { + rWrt.OutCSS1_UnitProperty( sCSS1_P_margin_right, rLRItem.GetRight() ); + } + + // The LineIndent of the first line might contain the room for numbering + tools::Long nFirstLineIndent = static_cast<tools::Long>(rLRItem.GetTextFirstLineOffset()) - + rWrt.m_nFirstLineIndent; + if( rWrt.m_nDfltFirstLineIndent != nFirstLineIndent ) + { + rWrt.OutCSS1_UnitProperty( sCSS1_P_text_indent, + nFirstLineIndent ); + } + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxULSpace( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + const SvxULSpaceItem& rULItem = static_cast<const SvxULSpaceItem&>(rHt); + + if( rWrt.m_nDfltTopMargin != rULItem.GetUpper() ) + { + rWrt.OutCSS1_UnitProperty( sCSS1_P_margin_top, + static_cast<tools::Long>(rULItem.GetUpper()) ); + } + + if( rWrt.m_nDfltBottomMargin != rULItem.GetLower() ) + { + rWrt.OutCSS1_UnitProperty( sCSS1_P_margin_bottom, + static_cast<tools::Long>(rULItem.GetLower()) ); + } + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxULSpace_SvxLRSpace( SwHTMLWriter& rWrt, + const SvxULSpaceItem *pULItem, + const SvxLRSpaceItem *pLRItem ) +{ + if( pLRItem && pULItem && + pLRItem->GetLeft() == pLRItem->GetRight() && + pLRItem->GetLeft() == pULItem->GetUpper() && + pLRItem->GetLeft() == pULItem->GetLower() && + pLRItem->GetLeft() != rWrt.m_nDfltLeftMargin && + pLRItem->GetRight() != rWrt.m_nDfltRightMargin && + pULItem->GetUpper() != rWrt.m_nDfltTopMargin && + pULItem->GetLower() != rWrt.m_nDfltBottomMargin ) + { + rWrt.OutCSS1_UnitProperty( sCSS1_P_margin, pLRItem->GetLeft() ); + } + else + { + if( pLRItem ) + OutCSS1_SvxLRSpace( rWrt, *pLRItem ); + if( pULItem ) + OutCSS1_SvxULSpace( rWrt, *pULItem ); + } + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxULSpace_SvxLRSpace( SwHTMLWriter& rWrt, + const SfxItemSet& rItemSet ) +{ + const SvxLRSpaceItem *pLRSpace = rItemSet.GetItemIfSet( RES_LR_SPACE, false/*bDeep*/ ); + const SvxULSpaceItem *pULSpace = rItemSet.GetItemIfSet( RES_UL_SPACE, false/*bDeep*/ ); + + if( pLRSpace || pULSpace ) + OutCSS1_SvxULSpace_SvxLRSpace( rWrt, pULSpace, pLRSpace ); + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxFormatBreak_SwFormatPDesc_SvxFormatKeep( SwHTMLWriter& rWrt, + const SvxFormatBreakItem *pBreakItem, + const SwFormatPageDesc *pPDescItem, + const SvxFormatKeepItem *pKeepItem ) +{ + if( !rWrt.IsHTMLMode(HTMLMODE_PRINT_EXT) ) + return rWrt; + + std::string_view pBreakBefore; + std::string_view pBreakAfter; + + if( pKeepItem ) + { + pBreakAfter = pKeepItem->GetValue() ? sCSS1_PV_avoid : sCSS1_PV_auto; + } + if( pBreakItem ) + { + switch( pBreakItem->GetBreak() ) + { + case SvxBreak::NONE: + pBreakBefore = sCSS1_PV_auto; + if( pBreakAfter.empty() ) + pBreakAfter = sCSS1_PV_auto; + break; + + case SvxBreak::PageBefore: + pBreakBefore = sCSS1_PV_always; + break; + + case SvxBreak::PageAfter: + pBreakAfter= sCSS1_PV_always; + break; + + default: + ; + } + } + if( pPDescItem ) + { + const SwPageDesc *pPDesc = pPDescItem->GetPageDesc(); + if( pPDesc ) + { + switch( pPDesc->GetPoolFormatId() ) + { + case RES_POOLPAGE_LEFT: pBreakBefore = sCSS1_PV_left; break; + case RES_POOLPAGE_RIGHT: pBreakBefore = sCSS1_PV_right; break; + default: pBreakBefore = sCSS1_PV_always; break; + } + } + else if( pBreakBefore.empty() ) + { + pBreakBefore = sCSS1_PV_auto; + } + } + + if (rWrt.mbSkipHeaderFooter) + // No page break when writing only a fragment. + return rWrt; + + if( !pBreakBefore.empty() ) + rWrt.OutCSS1_PropertyAscii( sCSS1_P_page_break_before, + pBreakBefore ); + if( !pBreakAfter.empty() ) + rWrt.OutCSS1_PropertyAscii( sCSS1_P_page_break_after, + pBreakAfter ); + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxFormatBreak_SwFormatPDesc_SvxFormatKeep( SwHTMLWriter& rWrt, + const SfxItemSet& rItemSet, + bool bDeep ) +{ + const SvxFormatBreakItem *pBreakItem = rItemSet.GetItemIfSet( RES_BREAK, bDeep ); + + const SwFormatPageDesc *pPDescItem = nullptr; + if( !rWrt.IsCSS1Source( CSS1_OUTMODE_PARA ) || + !rWrt.m_bCSS1IgnoreFirstPageDesc || + rWrt.m_pStartNdIdx->GetIndex() != + rWrt.m_pCurrentPam->GetPoint()->GetNodeIndex() ) + pPDescItem = rItemSet.GetItemIfSet( RES_PAGEDESC, bDeep ); + + const SvxFormatKeepItem *pKeepItem = rItemSet.GetItemIfSet( RES_KEEP, bDeep ); + + if( pBreakItem || pPDescItem || pKeepItem ) + OutCSS1_SvxFormatBreak_SwFormatPDesc_SvxFormatKeep( rWrt, pBreakItem, + pPDescItem, pKeepItem ); + + return rWrt; +} + +// Wrapper for OutCSS1_SfxItemSet etc. +static SwHTMLWriter& OutCSS1_SvxBrush( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + OutCSS1_SvxBrush( rWrt, rHt, sw::Css1Background::Attr, nullptr ); + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxBrush( SwHTMLWriter& rWrt, const SfxPoolItem& rHt, + sw::Css1Background nMode, + const OUString* pGraphicName) +{ + // The Character-Attribute is skipped, if we are about to + // exporting options + if( rHt.Which() < RES_CHRATR_END && + rWrt.IsCSS1Source( CSS1_OUTMODE_PARA ) ) + return rWrt; + + // start getting a few values +// const Brush &rBrush = static_cast<const SvxBrushItem &>(rHt).GetBrush(); + const Color & rColor = static_cast<const SvxBrushItem &>(rHt).GetColor(); + OUString aLink = pGraphicName ? *pGraphicName + : static_cast<const SvxBrushItem &>(rHt).GetGraphicLink(); + SvxGraphicPosition ePos = static_cast<const SvxBrushItem &>(rHt).GetGraphicPos(); + if( sw::Css1Background::Page == nMode && !rWrt.mbEmbedImages ) + { + // page style images are exported if not tiled + if( aLink.isEmpty() || GPOS_TILED==ePos ) + return rWrt; + } + + // get the color + bool bColor = false; + /// set <bTransparent> to true, if color is "no fill"/"auto fill" + bool bTransparent = (rColor == COL_TRANSPARENT); + Color aColor; + if( !bTransparent ) + { + aColor = rColor; + bColor = true; + } + + // and now the Graphic + OUString aGraphicInBase64; + + // Embedded Graphic -> export WriteEmbedded + const Graphic* pGrf = nullptr; + if( rWrt.mbEmbedImages || aLink.isEmpty()) + { + pGrf = static_cast<const SvxBrushItem &>(rHt).GetGraphic(); + if( pGrf ) + { + if( !XOutBitmap::GraphicToBase64(*pGrf, aGraphicInBase64) ) + { + rWrt.m_nWarn = WARN_SWG_POOR_LOAD; + } + } + aLink.clear(); + } + else if( !pGraphicName && rWrt.m_bCfgCpyLinkedGrfs ) + { + OUString aGraphicAsLink = aLink; + rWrt.CopyLocalFileToINet( aGraphicAsLink ); + aLink = aGraphicAsLink; + } + // In tables we only export something if there is a Graphic + if( (nMode == sw::Css1Background::Table || nMode == sw::Css1Background::TableRow) && !pGrf && !aLink.isEmpty()) + return rWrt; + + // if necessary, add the orientation of the Graphic + std::string_view pRepeat, pHori, pVert; + if( pGrf || !aLink.isEmpty() ) + { + if( GPOS_TILED==ePos ) + { + pRepeat = sCSS1_PV_repeat; + } + else + { + switch( ePos ) + { + case GPOS_LT: + case GPOS_MT: + case GPOS_RT: + pHori = sCSS1_PV_top; + break; + + case GPOS_LM: + case GPOS_MM: + case GPOS_RM: + pHori = sCSS1_PV_middle; + break; + + case GPOS_LB: + case GPOS_MB: + case GPOS_RB: + pHori = sCSS1_PV_bottom; + break; + + default: + ; + } + + switch( ePos ) + { + case GPOS_LT: + case GPOS_LM: + case GPOS_LB: + pVert = sCSS1_PV_left; + break; + + case GPOS_MT: + case GPOS_MM: + case GPOS_MB: + pVert = sCSS1_PV_center; + break; + + case GPOS_RT: + case GPOS_RM: + case GPOS_RB: + pVert = sCSS1_PV_right; + break; + + default: + ; + } + + if( !pHori.empty() || !pVert.empty() ) + pRepeat = sCSS1_PV_no_repeat; + } + } + + // now build the string + OUString sOut; + if( !pGrf && aLink.isEmpty() && !bColor ) + { + // no color and no Link, but a transparent Brush + if( bTransparent && sw::Css1Background::Fly != nMode ) + sOut += OStringToOUString(sCSS1_PV_transparent, RTL_TEXTENCODING_ASCII_US); + } + else + { + if( bColor ) + { + OString sTmp(GetCSS1_Color(aColor)); + sOut += OStringToOUString(sTmp, RTL_TEXTENCODING_ASCII_US); + } + + if( pGrf || !aLink.isEmpty() ) + { + if( bColor ) + sOut += " "; + + if(pGrf) + { + sOut += OStringToOUString(sCSS1_url, RTL_TEXTENCODING_ASCII_US) + + "(\'" OOO_STRING_SVTOOLS_HTML_O_data ":" + aGraphicInBase64 + "\')"; + } + else + { + sOut += OStringToOUString(sCSS1_url, RTL_TEXTENCODING_ASCII_US)+ + "(" + URIHelper::simpleNormalizedMakeRelative(rWrt.GetBaseURL(), + aLink) + ")"; + } + + if( !pRepeat.empty() ) + { + sOut += " " + OStringToOUString(pRepeat, RTL_TEXTENCODING_ASCII_US); + } + + if( !pHori.empty() ) + { + sOut += " " + OStringToOUString(pHori, RTL_TEXTENCODING_ASCII_US); + } + if( !pVert.empty() ) + { + sOut += " " + OStringToOUString(pVert, RTL_TEXTENCODING_ASCII_US); + } + + sOut += " " + OStringToOUString(sCSS1_PV_scroll, RTL_TEXTENCODING_ASCII_US) + " "; + } + } + + if( !sOut.isEmpty() ) + { + rWrt.OutCSS1_Property(sCSS1_P_background, std::string_view(), &sOut, + nMode); + } + + return rWrt; +} + +static void OutCSS1_SvxBorderLine( SwHTMLWriter& rWrt, + std::string_view pProperty, + const SvxBorderLine *pLine ) +{ + if( !pLine || pLine->isEmpty() ) + { + rWrt.OutCSS1_PropertyAscii( pProperty, sCSS1_PV_none ); + return; + } + + sal_Int32 nWidth = pLine->GetWidth(); + + OStringBuffer sOut; + if( nWidth <= o3tl::convert(1, o3tl::Length::px, o3tl::Length::twip) ) + { + // If the width is smaller than one pixel, then export as 1px + // so that Netscape and IE show the line. + sOut.append("1px"); + } + else + { + nWidth *= 5; // 1/100pt + + // width in n.nn pt + sOut.append(OString::number(nWidth / 100) + "." + OString::number((nWidth/10) % 10) + + OString::number(nWidth % 10) + sCSS1_UNIT_pt); + } + + // Line-Style: solid or double + sOut.append(' '); + switch (pLine->GetBorderLineStyle()) + { + case SvxBorderLineStyle::SOLID: + sOut.append(sCSS1_PV_solid); + break; + case SvxBorderLineStyle::DOTTED: + sOut.append(sCSS1_PV_dotted); + break; + case SvxBorderLineStyle::DASHED: + sOut.append(sCSS1_PV_dashed); + break; + case SvxBorderLineStyle::DOUBLE: + case SvxBorderLineStyle::THINTHICK_SMALLGAP: + case SvxBorderLineStyle::THINTHICK_MEDIUMGAP: + case SvxBorderLineStyle::THINTHICK_LARGEGAP: + case SvxBorderLineStyle::THICKTHIN_SMALLGAP: + case SvxBorderLineStyle::THICKTHIN_MEDIUMGAP: + case SvxBorderLineStyle::THICKTHIN_LARGEGAP: + sOut.append(sCSS1_PV_double); + break; + case SvxBorderLineStyle::EMBOSSED: + sOut.append(sCSS1_PV_ridge); + break; + case SvxBorderLineStyle::ENGRAVED: + sOut.append(sCSS1_PV_groove); + break; + case SvxBorderLineStyle::INSET: + sOut.append(sCSS1_PV_inset); + break; + case SvxBorderLineStyle::OUTSET: + sOut.append(sCSS1_PV_outset); + break; + default: + sOut.append(sCSS1_PV_none); + } + sOut.append(' '); + + // and also the color + sOut.append(GetCSS1_Color(pLine->GetColor())); + + rWrt.OutCSS1_PropertyAscii(pProperty, sOut); +} + +SwHTMLWriter& OutCSS1_SvxBox( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + // Avoid interference between character and paragraph attributes + if( rHt.Which() < RES_CHRATR_END && + rWrt.IsCSS1Source( CSS1_OUTMODE_PARA ) ) + return rWrt; + + if( rHt.Which() == RES_CHRATR_BOX ) + { + if( rWrt.m_bTagOn ) + { + // Inline-block to make the line height changing correspond to the character border + rWrt.OutCSS1_PropertyAscii(sCSS1_P_display, "inline-block"); + } + else + { + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_span), false ); + return rWrt; + } + } + + const SvxBoxItem& rBoxItem = static_cast<const SvxBoxItem&>(rHt); + const SvxBorderLine *pTop = rBoxItem.GetTop(); + const SvxBorderLine *pBottom = rBoxItem.GetBottom(); + const SvxBorderLine *pLeft = rBoxItem.GetLeft(); + const SvxBorderLine *pRight = rBoxItem.GetRight(); + + if( (pTop && pBottom && pLeft && pRight && + *pTop == *pBottom && *pTop == *pLeft && *pTop == *pRight) || + (!pTop && !pBottom && !pLeft && !pRight) ) + { + // all Lines are set and equal, or all Lines are not set + // => border : ... + OutCSS1_SvxBorderLine( rWrt, sCSS1_P_border, pTop ); + } + else + { + // otherwise export all Lines separately + OutCSS1_SvxBorderLine( rWrt, sCSS1_P_border_top, pTop ); + OutCSS1_SvxBorderLine( rWrt, sCSS1_P_border_bottom, pBottom ); + OutCSS1_SvxBorderLine( rWrt, sCSS1_P_border_left, pLeft ); + OutCSS1_SvxBorderLine( rWrt, sCSS1_P_border_right, pRight ); + } + + tools::Long nTopDist = pTop ? rBoxItem.GetDistance( SvxBoxItemLine::TOP ) : 0; + tools::Long nBottomDist = pBottom ? rBoxItem.GetDistance( SvxBoxItemLine::BOTTOM ) : 0; + tools::Long nLeftDist = pLeft ? rBoxItem.GetDistance( SvxBoxItemLine::LEFT ) : 0; + tools::Long nRightDist = pRight ? rBoxItem.GetDistance( SvxBoxItemLine::RIGHT ) : 0; + + if( nTopDist == nBottomDist && nLeftDist == nRightDist ) + { + OStringBuffer sVal; + AddUnitPropertyValue(sVal, nTopDist, rWrt.GetCSS1Unit()); + if( nTopDist != nLeftDist ) + { + sVal.append(' '); + AddUnitPropertyValue(sVal, nLeftDist, rWrt.GetCSS1Unit()); + } + rWrt.OutCSS1_PropertyAscii(sCSS1_P_padding, sVal); + } + else + { + rWrt.OutCSS1_UnitProperty( sCSS1_P_padding_top, nTopDist ); + rWrt.OutCSS1_UnitProperty( sCSS1_P_padding_bottom, nBottomDist ); + rWrt.OutCSS1_UnitProperty( sCSS1_P_padding_left, nLeftDist ); + rWrt.OutCSS1_UnitProperty( sCSS1_P_padding_right, nRightDist ); + } + + return rWrt; +} + +static SwHTMLWriter& OutCSS1_SvxFrameDirection( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + // Language will be exported rules only + if( !rWrt.IsCSS1Source( CSS1_OUTMODE_TEMPLATE ) ) + return rWrt; + + SvxFrameDirection nDir = + static_cast< const SvxFrameDirectionItem& >( rHt ).GetValue(); + std::string_view pStr; + switch( nDir ) + { + case SvxFrameDirection::Horizontal_LR_TB: + case SvxFrameDirection::Vertical_LR_TB: + pStr = sCSS1_PV_ltr; + break; + case SvxFrameDirection::Horizontal_RL_TB: + case SvxFrameDirection::Vertical_RL_TB: + pStr = sCSS1_PV_rtl; + break; + case SvxFrameDirection::Environment: + pStr = sCSS1_PV_inherit; + break; + default: break; + } + + if( !pStr.empty() ) + rWrt.OutCSS1_PropertyAscii( sCSS1_P_direction, pStr ); + + return rWrt; +} + +/* + * Place here the table for the HTML-Function-Pointer to the + * Export-Functions. + * They are local structures, only needed within the HTML-DLL. + */ + +SwAttrFnTab const aCSS1AttrFnTab = { +/* RES_CHRATR_CASEMAP */ OutCSS1_SvxCaseMap, +/* RES_CHRATR_CHARSETCOLOR */ nullptr, +/* RES_CHRATR_COLOR */ OutCSS1_SvxColor, +/* RES_CHRATR_CONTOUR */ nullptr, +/* RES_CHRATR_CROSSEDOUT */ OutCSS1_SvxCrossedOut, +/* RES_CHRATR_ESCAPEMENT */ nullptr, +/* RES_CHRATR_FONT */ OutCSS1_SvxFont, +/* RES_CHRATR_FONTSIZE */ OutCSS1_SvxFontHeight, +/* RES_CHRATR_KERNING */ OutCSS1_SvxKerning, +/* RES_CHRATR_LANGUAGE */ OutCSS1_SvxLanguage, +/* RES_CHRATR_POSTURE */ OutCSS1_SvxPosture, +/* RES_CHRATR_UNUSED1*/ nullptr, +/* RES_CHRATR_SHADOWED */ nullptr, +/* RES_CHRATR_UNDERLINE */ OutCSS1_SvxUnderline, +/* RES_CHRATR_WEIGHT */ OutCSS1_SvxFontWeight, +/* RES_CHRATR_WORDLINEMODE */ nullptr, +/* RES_CHRATR_AUTOKERN */ nullptr, +/* RES_CHRATR_BLINK */ OutCSS1_SvxBlink, +/* RES_CHRATR_NOHYPHEN */ nullptr, // new: don't separate +/* RES_CHRATR_UNUSED2 */ nullptr, +/* RES_CHRATR_BACKGROUND */ OutCSS1_SvxBrush, // new: character background +/* RES_CHRATR_CJK_FONT */ OutCSS1_SvxFont, +/* RES_CHRATR_CJK_FONTSIZE */ OutCSS1_SvxFontHeight, +/* RES_CHRATR_CJK_LANGUAGE */ OutCSS1_SvxLanguage, +/* RES_CHRATR_CJK_POSTURE */ OutCSS1_SvxPosture, +/* RES_CHRATR_CJK_WEIGHT */ OutCSS1_SvxFontWeight, +/* RES_CHRATR_CTL_FONT */ OutCSS1_SvxFont, +/* RES_CHRATR_CTL_FONTSIZE */ OutCSS1_SvxFontHeight, +/* RES_CHRATR_CTL_LANGUAGE */ OutCSS1_SvxLanguage, +/* RES_CHRATR_CTL_POSTURE */ OutCSS1_SvxPosture, +/* RES_CHRATR_CTL_WEIGHT */ OutCSS1_SvxFontWeight, +/* RES_CHRATR_ROTATE */ nullptr, +/* RES_CHRATR_EMPHASIS_MARK */ nullptr, +/* RES_CHRATR_TWO_LINES */ nullptr, +/* RES_CHRATR_SCALEW */ nullptr, +/* RES_CHRATR_RELIEF */ nullptr, +/* RES_CHRATR_HIDDEN */ OutCSS1_SvxHidden, +/* RES_CHRATR_OVERLINE */ OutCSS1_SvxOverline, +/* RES_CHRATR_RSID */ nullptr, +/* RES_CHRATR_BOX */ OutCSS1_SvxBox, +/* RES_CHRATR_SHADOW */ nullptr, +/* RES_CHRATR_HIGHLIGHT */ nullptr, +/* RES_CHRATR_GRABBAG */ nullptr, +/* RES_CHRATR_BIDIRTL */ nullptr, +/* RES_CHRATR_IDCTHINT */ nullptr, + +/* RES_TXTATR_REFMARK */ nullptr, +/* RES_TXTATR_TOXMARK */ nullptr, +/* RES_TXTATR_META */ nullptr, +/* RES_TXTATR_METAFIELD */ nullptr, +/* RES_TXTATR_AUTOFMT */ nullptr, +/* RES_TXTATR_INETFMT */ nullptr, +/* RES_TXTATR_CHARFMT */ nullptr, +/* RES_TXTATR_CJK_RUBY */ nullptr, +/* RES_TXTATR_UNKNOWN_CONTAINER */ nullptr, +/* RES_TXTATR_INPUTFIELD */ nullptr, +/* RES_TXTATR_CONTENTCONTROL */ nullptr, + +/* RES_TXTATR_FIELD */ nullptr, +/* RES_TXTATR_FLYCNT */ nullptr, +/* RES_TXTATR_FTN */ nullptr, +/* RES_TXTATR_ANNOTATION */ nullptr, +/* RES_TXTATR_LINEBREAK */ nullptr, +/* RES_TXTATR_DUMMY1 */ nullptr, // Dummy: + +/* RES_PARATR_LINESPACING */ OutCSS1_SvxLineSpacing, +/* RES_PARATR_ADJUST */ OutCSS1_SvxAdjust, +/* RES_PARATR_SPLIT */ OutCSS1_SvxFormatSplit, +/* RES_PARATR_ORPHANS */ OutCSS1_SvxOrphans, +/* RES_PARATR_WIDOWS */ OutCSS1_SvxWidows, +/* RES_PARATR_TABSTOP */ nullptr, +/* RES_PARATR_HYPHENZONE*/ nullptr, +/* RES_PARATR_DROP */ OutCSS1_SwFormatDrop, +/* RES_PARATR_REGISTER */ nullptr, // new: register-true +/* RES_PARATR_NUMRULE */ nullptr, +/* RES_PARATR_SCRIPTSPACE */ nullptr, +/* RES_PARATR_HANGINGPUNCTUATION */ nullptr, +/* RES_PARATR_FORBIDDEN_RULES */ nullptr, // new +/* RES_PARATR_VERTALIGN */ nullptr, // new +/* RES_PARATR_SNAPTOGRID*/ nullptr, // new +/* RES_PARATR_CONNECT_TO_BORDER */ nullptr, // new +/* RES_PARATR_OUTLINELEVEL */ nullptr, // new since cws outlinelevel +/* RES_PARATR_RSID */ nullptr, // new +/* RES_PARATR_GRABBAG */ nullptr, + +/* RES_PARATR_LIST_ID */ nullptr, // new +/* RES_PARATR_LIST_LEVEL */ nullptr, // new +/* RES_PARATR_LIST_ISRESTART */ nullptr, // new +/* RES_PARATR_LIST_RESTARTVALUE */ nullptr, // new +/* RES_PARATR_LIST_ISCOUNTED */ nullptr, // new +/* RES_PARATR_LIST_AUTOFMT */ nullptr, // new + +/* RES_FILL_ORDER */ nullptr, +/* RES_FRM_SIZE */ nullptr, +/* RES_PAPER_BIN */ nullptr, +/* RES_MARGIN_FIRSTLINE */ OutCSS1_SvxFirstLineIndent, +/* RES_MARGIN_TEXTLEFT */ OutCSS1_SvxTextLeftMargin, +/* RES_MARGIN_RIGHT */ OutCSS1_SvxRightMargin, +/* RES_MARGIN_LEFT */ nullptr, +/* RES_MARGIN_GUTTER */ nullptr, +/* RES_MARGIN_GUTTER_RIGHT */ nullptr, +/* RES_LR_SPACE */ OutCSS1_SvxLRSpace, +/* RES_UL_SPACE */ OutCSS1_SvxULSpace, +/* RES_PAGEDESC */ nullptr, +/* RES_BREAK */ nullptr, +/* RES_CNTNT */ nullptr, +/* RES_HEADER */ nullptr, +/* RES_FOOTER */ nullptr, +/* RES_PRINT */ nullptr, +/* RES_OPAQUE */ nullptr, +/* RES_PROTECT */ nullptr, +/* RES_SURROUND */ nullptr, +/* RES_VERT_ORIENT */ nullptr, +/* RES_HORI_ORIENT */ nullptr, +/* RES_ANCHOR */ nullptr, +/* RES_BACKGROUND */ OutCSS1_SvxBrush, +/* RES_BOX */ OutCSS1_SvxBox, +/* RES_SHADOW */ nullptr, +/* RES_FRMMACRO */ nullptr, +/* RES_COL */ nullptr, +/* RES_KEEP */ nullptr, +/* RES_URL */ nullptr, +/* RES_EDIT_IN_READONLY */ nullptr, +/* RES_LAYOUT_SPLIT */ nullptr, +/* RES_CHAIN */ nullptr, +/* RES_TEXTGRID */ nullptr, +/* RES_LINENUMBER */ nullptr, +/* RES_FTN_AT_TXTEND */ nullptr, +/* RES_END_AT_TXTEND */ nullptr, +/* RES_COLUMNBALANCE */ nullptr, +/* RES_FRAMEDIR */ OutCSS1_SvxFrameDirection, +/* RES_HEADER_FOOTER_EAT_SPACING */ nullptr, +/* RES_ROW_SPLIT */ nullptr, +/* RES_FLY_SPLIT */ nullptr, +/* RES_FOLLOW_TEXT_FLOW */ nullptr, +/* RES_COLLAPSING_BORDERS */ nullptr, +/* RES_WRAP_INFLUENCE_ON_OBJPOS */ nullptr, +/* RES_AUTO_STYLE */ nullptr, +/* RES_FRMATR_STYLE_NAME */ nullptr, +/* RES_FRMATR_CONDITIONAL_STYLE_NAME */ nullptr, +/* RES_FRMATR_GRABBAG */ nullptr, +/* RES_TEXT_VERT_ADJUST */ nullptr, +/* RES_BACKGROUND_FULL_SIZE */ nullptr, +/* RES_RTL_GUTTER */ nullptr, +/* RES_DECORATIVE */ nullptr, + +/* RES_GRFATR_MIRRORGRF */ nullptr, +/* RES_GRFATR_CROPGRF */ nullptr, +/* RES_GRFATR_ROTATION */ nullptr, +/* RES_GRFATR_LUMINANCE */ nullptr, +/* RES_GRFATR_CONTRAST */ nullptr, +/* RES_GRFATR_CHANNELR */ nullptr, +/* RES_GRFATR_CHANNELG */ nullptr, +/* RES_GRFATR_CHANNELB */ nullptr, +/* RES_GRFATR_GAMMA */ nullptr, +/* RES_GRFATR_INVERT */ nullptr, +/* RES_GRFATR_TRANSPARENCY */ nullptr, +/* RES_GRFATR_DRWAMODE */ nullptr, +/* RES_GRFATR_DUMMY3 */ nullptr, +/* RES_GRFATR_DUMMY4 */ nullptr, +/* RES_GRFATR_DUMMY5 */ nullptr, + +/* RES_BOXATR_FORMAT */ nullptr, +/* RES_BOXATR_FORMULA */ nullptr, +/* RES_BOXATR_VALUE */ nullptr +}; + +static_assert(SAL_N_ELEMENTS(aCSS1AttrFnTab) == RES_BOXATR_END); + +void SwHTMLWriter::OutCSS1_SfxItemSet( const SfxItemSet& rItemSet, + bool bDeep, std::string_view rAdd ) +{ + // print ItemSet, including all attributes + Out_SfxItemSet( aCSS1AttrFnTab, *this, rItemSet, bDeep ); + + // some Attributes require special treatment + + // Underline, Overline, CrossedOut and Blink form together a CSS1-Property + // (doesn't work of course for Hints) + if( !IsCSS1Source(CSS1_OUTMODE_HINT) ) + { + const SvxUnderlineItem *pUnderlineItem = + rItemSet.GetItemIfSet( RES_CHRATR_UNDERLINE, bDeep ); + + const SvxOverlineItem *pOverlineItem = + rItemSet.GetItemIfSet( RES_CHRATR_OVERLINE, bDeep ); + + const SvxCrossedOutItem *pCrossedOutItem = + rItemSet.GetItemIfSet( RES_CHRATR_CROSSEDOUT, bDeep ); + + const SvxBlinkItem *pBlinkItem = + rItemSet.GetItemIfSet( RES_CHRATR_BLINK, bDeep ); + + if( pUnderlineItem || pOverlineItem || pCrossedOutItem || pBlinkItem ) + OutCSS1_SvxTextLn_SvxCrOut_SvxBlink( *this, pUnderlineItem, + pOverlineItem, + pCrossedOutItem, + pBlinkItem ); + + OutCSS1_SvxFormatBreak_SwFormatPDesc_SvxFormatKeep( *this, rItemSet, bDeep ); + } + + if (!rAdd.empty()) + { + for (std::size_t index = 0; index != std::string_view::npos;) + { + std::string_view attr = o3tl::trim(o3tl::getToken(rAdd, ':', index)); + assert(!attr.empty()); + assert(index != std::string_view::npos); + + std::string_view val = o3tl::trim(o3tl::getToken(rAdd, ':', index)); + assert(!val.empty()); + OutCSS1_PropertyAscii(attr, val); + } + } + + if( m_bFirstCSS1Property ) + return; + + // if a Property was exported as part of a Style-Option, + // the Option still needs to be finished + OStringBuffer sOut; + switch( m_nCSS1OutMode & CSS1_OUTMODE_ANY_OFF ) + { + case CSS1_OUTMODE_SPAN_TAG_OFF: + sOut.append(sCSS1_span_tag_end); + break; + + case CSS1_OUTMODE_STYLE_OPT_OFF: + sOut.append(cCSS1_style_opt_end); + break; + + case CSS1_OUTMODE_RULE_OFF: + sOut.append(sCSS1_rule_end); + break; + } + if (!sOut.isEmpty()) + Strm().WriteOString( sOut ); +} + +SwHTMLWriter& OutCSS1_HintSpanTag( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + SwCSS1OutMode aMode( rWrt, CSS1_OUTMODE_SPAN_TAG | + CSS1_OUTMODE_ENCODE|CSS1_OUTMODE_HINT, nullptr ); + + Out( aCSS1AttrFnTab, rHt, rWrt ); + + if( !rWrt.m_bFirstCSS1Property && rWrt.m_bTagOn ) + rWrt.Strm().WriteOString( sCSS1_span_tag_end ); + + return rWrt; +} + +SwHTMLWriter& OutCSS1_HintStyleOpt( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + SwCSS1OutMode aMode( rWrt, CSS1_OUTMODE_STYLE_OPT_ON | + CSS1_OUTMODE_ENCODE| + CSS1_OUTMODE_HINT, nullptr ); + + Out( aCSS1AttrFnTab, rHt, rWrt ); + + if( !rWrt.m_bFirstCSS1Property ) + rWrt.Strm().WriteChar( '\"' ); + + return rWrt; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/css1atr.hxx b/sw/source/filter/html/css1atr.hxx new file mode 100644 index 0000000000..42b925587f --- /dev/null +++ b/sw/source/filter/html/css1atr.hxx @@ -0,0 +1,29 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_FILTER_HTML_CSS1ATR_HXX +#define INCLUDED_SW_SOURCE_FILTER_HTML_CSS1ATR_HXX + +class SfxPoolItem; + +bool swhtml_css1atr_equalFontItems(const SfxPoolItem& r1, const SfxPoolItem& r2); + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/css1kywd.hxx b/sw/source/filter/html/css1kywd.hxx new file mode 100644 index 0000000000..42ad28da92 --- /dev/null +++ b/sw/source/filter/html/css1kywd.hxx @@ -0,0 +1,224 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_FILTER_HTML_CSS1KYWD_HXX +#define INCLUDED_SW_SOURCE_FILTER_HTML_CSS1KYWD_HXX + +#include <sal/config.h> + +#include <string_view> + +constexpr inline std::string_view sCSS_mimetype = "text/css"; + +constexpr inline std::string_view sCSS1_page = "page"; +//constexpr inline std::string_view sCSS1_media = "media"; + +constexpr inline std::string_view sCSS1_link = "link"; +constexpr inline std::string_view sCSS1_visited = "visited"; +constexpr inline std::string_view sCSS1_first_letter = "first-letter"; + +constexpr inline std::string_view sCSS1_left = "left"; +constexpr inline std::string_view sCSS1_right = "right"; +constexpr inline std::string_view sCSS1_first = "first"; + +constexpr inline std::string_view sCSS1_url = "url"; + +constexpr inline std::string_view sCSS1_UNIT_pt = "pt"; +constexpr inline std::string_view sCSS1_UNIT_mm = "mm"; +constexpr inline std::string_view sCSS1_UNIT_cm = "cm"; +constexpr inline std::string_view sCSS1_UNIT_pc = "pc"; +constexpr inline std::string_view sCSS1_UNIT_inch = "in"; +constexpr inline std::string_view sCSS1_UNIT_px = "px"; + +// Strings for font properties + +constexpr inline std::string_view sCSS1_P_font_family = "font-family"; + +constexpr inline std::string_view sCSS1_PV_serif = "serif"; +constexpr inline std::string_view sCSS1_PV_sans_serif = "sans-serif"; +constexpr inline std::string_view sCSS1_PV_cursive = "cursive"; +constexpr inline std::string_view sCSS1_PV_fantasy = "fantasy"; +constexpr inline std::string_view sCSS1_PV_monospace = "monospace"; + +constexpr inline std::string_view sCSS1_P_font_style = "font-style"; + +constexpr inline std::string_view sCSS1_PV_normal = "normal"; +constexpr inline std::string_view sCSS1_PV_italic = "italic"; +constexpr inline std::string_view sCSS1_PV_oblique = "oblique"; + +constexpr inline std::string_view sCSS1_P_font_variant = "font-variant"; + +//constexpr inline std::string_view sCSS1_PV_normal = "normal"; +constexpr inline std::string_view sCSS1_PV_small_caps = "small-caps"; + +constexpr inline std::string_view sCSS1_P_text_transform = "text-transform"; + +constexpr inline std::string_view sCSS1_PV_capitalize = "capitalize"; +constexpr inline std::string_view sCSS1_PV_uppercase = "uppercase"; +constexpr inline std::string_view sCSS1_PV_lowercase = "lowercase"; + +constexpr inline std::string_view sCSS1_P_font_weight = "font-weight"; + +constexpr inline std::string_view sCSS1_PV_extra_light = "extra-light"; +constexpr inline std::string_view sCSS1_PV_light = "light"; +constexpr inline std::string_view sCSS1_PV_demi_light = "demi-light"; +//constexpr inline std::string_view sCSS1_PV_medium = "medium"; +constexpr inline std::string_view sCSS1_PV_demi_bold = "demi-bold"; +constexpr inline std::string_view sCSS1_PV_bold = "bold"; +constexpr inline std::string_view sCSS1_PV_extra_bold = "extra-bold"; + +constexpr inline std::string_view sCSS1_P_font_size = "font-size"; + +constexpr inline std::string_view sCSS1_P_font = "font"; + +// Strings for color and background properties + +constexpr inline std::string_view sCSS1_P_color = "color"; + +constexpr inline std::string_view sCSS1_P_background = "background"; +constexpr inline std::string_view sCSS1_P_background_color = "background-color"; + +constexpr inline std::string_view sCSS1_PV_transparent = "transparent"; + +constexpr inline std::string_view sCSS1_PV_repeat = "repeat"; +constexpr inline std::string_view sCSS1_PV_no_repeat = "no-repeat"; + +constexpr inline std::string_view sCSS1_PV_top = "top"; +constexpr inline std::string_view sCSS1_PV_middle = "middle"; +constexpr inline std::string_view sCSS1_PV_bottom = "bottom"; + +constexpr inline std::string_view sCSS1_PV_scroll = "scroll"; + +// Strings for text properties + +constexpr inline std::string_view sCSS1_P_letter_spacing = "letter-spacing"; + +constexpr inline std::string_view sCSS1_P_text_decoration = "text-decoration"; + +constexpr inline std::string_view sCSS1_PV_none = "none"; +constexpr inline std::string_view sCSS1_PV_underline = "underline"; +constexpr inline std::string_view sCSS1_PV_overline = "overline"; +constexpr inline std::string_view sCSS1_PV_line_through = "line-through"; +constexpr inline std::string_view sCSS1_PV_blink = "blink"; + +constexpr inline std::string_view sCSS1_P_text_align = "text-align"; + +constexpr inline std::string_view sCSS1_PV_left = "left"; +constexpr inline std::string_view sCSS1_PV_center = "center"; +constexpr inline std::string_view sCSS1_PV_right = "right"; +constexpr inline std::string_view sCSS1_PV_justify = "justify"; + +constexpr inline std::string_view sCSS1_P_text_indent = "text-indent"; + +constexpr inline std::string_view sCSS1_P_line_height = "line-height"; + +constexpr inline std::string_view sCSS1_P_list_style_type = "list-style-type"; + +// Strings for box properties + +constexpr inline std::string_view sCSS1_P_margin_left = "margin-left"; +constexpr inline std::string_view sCSS1_P_margin_right = "margin-right"; +constexpr inline std::string_view sCSS1_P_margin_top = "margin-top"; +constexpr inline std::string_view sCSS1_P_margin_bottom = "margin-bottom"; +constexpr inline std::string_view sCSS1_P_margin = "margin"; + +constexpr inline std::string_view sCSS1_P_padding_top = "padding-top"; +constexpr inline std::string_view sCSS1_P_padding_bottom = "padding-bottom"; +constexpr inline std::string_view sCSS1_P_padding_left = "padding-left"; +constexpr inline std::string_view sCSS1_P_padding_right = "padding-right"; +constexpr inline std::string_view sCSS1_P_padding = "padding"; + +constexpr inline std::string_view sCSS1_PV_auto = "auto"; + +constexpr inline std::string_view sCSS1_P_border_left_width = "border-left-width"; +constexpr inline std::string_view sCSS1_P_border_right_width = "border-right-width"; +constexpr inline std::string_view sCSS1_P_border_top_width = "border-top-width"; +constexpr inline std::string_view sCSS1_P_border_bottom_width = "border-bottom-width"; +constexpr inline std::string_view sCSS1_P_border_width = "border-width"; +constexpr inline std::string_view sCSS1_P_border_color = "border-color"; +constexpr inline std::string_view sCSS1_P_border_style = "border-style"; +constexpr inline std::string_view sCSS1_P_border_left = "border-left"; +constexpr inline std::string_view sCSS1_P_border_right = "border-right"; +constexpr inline std::string_view sCSS1_P_border_top = "border-top"; +constexpr inline std::string_view sCSS1_P_border_bottom = "border-bottom"; +constexpr inline std::string_view sCSS1_P_border = "border"; + +//constexpr inline std::string_view sCSS1_PV_none = "none"; +constexpr inline std::string_view sCSS1_PV_dotted = "dotted"; +constexpr inline std::string_view sCSS1_PV_dashed = "dashed"; +constexpr inline std::string_view sCSS1_PV_solid = "solid"; +constexpr inline std::string_view sCSS1_PV_double = "double"; +constexpr inline std::string_view sCSS1_PV_groove = "groove"; +constexpr inline std::string_view sCSS1_PV_ridge = "ridge"; +constexpr inline std::string_view sCSS1_PV_inset = "inset"; +constexpr inline std::string_view sCSS1_PV_outset = "outset"; + +constexpr inline std::string_view sCSS1_P_width = "width"; +constexpr inline std::string_view sCSS1_P_max_width = "max-width"; + +constexpr inline std::string_view sCSS1_P_height = "height"; + +constexpr inline std::string_view sCSS1_P_float = "float"; + +constexpr inline std::string_view sCSS1_P_column_count = "column-count"; +constexpr inline std::string_view sCSS1_P_dir = "dir"; + +// Strings for positioning + +constexpr inline std::string_view sCSS1_P_position = "position"; + +constexpr inline std::string_view sCSS1_PV_absolute = "absolute"; + +constexpr inline std::string_view sCSS1_P_left = "left"; + +constexpr inline std::string_view sCSS1_P_top = "top"; + +// Strings for printing extensions + +constexpr inline std::string_view sCSS1_P_page_break_before = "page-break-before"; +constexpr inline std::string_view sCSS1_P_page_break_after = "page-break-after"; +constexpr inline std::string_view sCSS1_P_page_break_inside = "page-break-inside"; +constexpr inline std::string_view sCSS1_P_size = "size"; +constexpr inline std::string_view sCSS1_P_widows = "widows"; +constexpr inline std::string_view sCSS1_P_visibility = "visibility"; +constexpr inline std::string_view sCSS1_P_orphans = "orphans"; +//constexpr inline std::string_view sCSS1_P_marks = "marks"; + +constexpr inline std::string_view sCSS1_PV_always = "always"; +constexpr inline std::string_view sCSS1_PV_avoid = "avoid"; + +constexpr inline std::string_view sCSS1_PV_portrait = "portrait"; +constexpr inline std::string_view sCSS1_PV_landscape = "landscape"; + +//constexpr inline std::string_view sCSS1_PV_crop = "crop"; +//constexpr inline std::string_view sCSS1_PV_cross = "cross"; + +constexpr inline std::string_view sCSS1_P_so_language = "so-language"; +constexpr inline std::string_view sCSS1_P_direction = "direction"; +constexpr inline std::string_view sCSS1_PV_ltr = "ltr"; +constexpr inline std::string_view sCSS1_PV_rtl = "rtl"; +constexpr inline std::string_view sCSS1_PV_inherit = "inherit"; + +constexpr inline std::string_view sCSS1_P_display = "display"; + +constexpr inline std::string_view sCSS1_white_space = "white-space"; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlatr.cxx b/sw/source/filter/html/htmlatr.cxx new file mode 100644 index 0000000000..9f67d1ee03 --- /dev/null +++ b/sw/source/filter/html/htmlatr.cxx @@ -0,0 +1,3467 @@ +/* -*- 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 <hintids.hxx> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <comphelper/string.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <svtools/htmlout.hxx> +#include <svtools/htmlkywd.hxx> +#include <svtools/htmltokn.h> +#include <svl/whiter.hxx> +#include <sfx2/event.hxx> +#include <sfx2/htmlmode.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/formatbreakitem.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/blinkitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <fchrfmt.hxx> +#include <fmtautofmt.hxx> +#include <fmtfsize.hxx> +#include <fmtclds.hxx> +#include <fmtpdsc.hxx> +#include <fmtflcnt.hxx> +#include <fmtinfmt.hxx> +#include <txatbase.hxx> +#include <frmatr.hxx> +#include <charfmt.hxx> +#include <fmtfld.hxx> +#include <doc.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <paratr.hxx> +#include <poolfmt.hxx> +#include <pagedesc.hxx> +#include <swtable.hxx> +#include <fldbas.hxx> +#include <breakit.hxx> +#include "htmlatr.hxx" +#include "htmlnum.hxx" +#include "wrthtml.hxx" +#include "htmlfly.hxx" +#include <numrule.hxx> +#include <rtl/character.hxx> +#include <osl/diagnose.h> +#include <deque> + +#include <svtools/HtmlWriter.hxx> +#include <o3tl/string_view.hxx> + +#include <memory> +#include <algorithm> + +using namespace css; + +HTMLOutEvent const aAnchorEventTable[] = +{ + { OOO_STRING_SVTOOLS_HTML_O_SDonclick, OOO_STRING_SVTOOLS_HTML_O_onclick, SvMacroItemId::OnClick }, + { OOO_STRING_SVTOOLS_HTML_O_SDonmouseover, OOO_STRING_SVTOOLS_HTML_O_onmouseover, SvMacroItemId::OnMouseOver }, + { OOO_STRING_SVTOOLS_HTML_O_SDonmouseout, OOO_STRING_SVTOOLS_HTML_O_onmouseout, SvMacroItemId::OnMouseOut }, + { nullptr, nullptr, SvMacroItemId::NONE } +}; + +static SwHTMLWriter& OutHTML_SvxAdjust( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ); + +sal_uInt16 SwHTMLWriter::GetDefListLvl( std::u16string_view rNm, sal_uInt16 nPoolId ) +{ + if( nPoolId == RES_POOLCOLL_HTML_DD ) + { + return 1 | HTML_DLCOLL_DD; + } + else if( nPoolId == RES_POOLCOLL_HTML_DT ) + { + return 1 | HTML_DLCOLL_DT; + } + + OUString sDTDD = OOO_STRING_SVTOOLS_HTML_dt " "; + if( o3tl::starts_with(rNm, sDTDD) ) + // DefinitionList - term + return o3tl::narrowing<sal_uInt16>(o3tl::toInt32(rNm.substr( sDTDD.getLength() ))) | HTML_DLCOLL_DT; + + sDTDD = OOO_STRING_SVTOOLS_HTML_dd " "; + if( o3tl::starts_with(rNm, sDTDD) ) + // DefinitionList - definition + return o3tl::narrowing<sal_uInt16>(o3tl::toInt32(rNm.substr( sDTDD.getLength() ))) | HTML_DLCOLL_DD; + + return 0; +} + +void SwHTMLWriter::OutAndSetDefList( sal_uInt16 nNewLvl ) +{ + // possibly, we first need to start a new list + if( m_nDefListLvl < nNewLvl ) + { + // output </pre> for the previous(!) paragraph, if required. + // Preferable, the <pre> is exported by OutHTML_SwFormatOff for the + // previous paragraph already, but that's not possible, because a very + // deep look at the next paragraph (this one) is required to figure + // out that a def list starts here. + + ChangeParaToken( HtmlTokenId::NONE ); + + // write according to the level difference + for( sal_uInt16 i=m_nDefListLvl; i<nNewLvl; i++ ) + { + if (IsLFPossible()) + OutNewLine(); + HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + OOO_STRING_SVTOOLS_HTML_deflist) ); + HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + OOO_STRING_SVTOOLS_HTML_dd) ); + IncIndentLevel(); + SetLFPossible(true); + } + } + else if( m_nDefListLvl > nNewLvl ) + { + for( sal_uInt16 i=nNewLvl ; i < m_nDefListLvl; i++ ) + { + DecIndentLevel(); + if (IsLFPossible()) + OutNewLine(); + HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + OOO_STRING_SVTOOLS_HTML_dd), false ); + HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + OOO_STRING_SVTOOLS_HTML_deflist), false ); + SetLFPossible(true); + } + } + + m_nDefListLvl = nNewLvl; +} + +void SwHTMLWriter::ChangeParaToken( HtmlTokenId nNew ) +{ + if( nNew != m_nLastParaToken && HtmlTokenId::PREFORMTXT_ON == m_nLastParaToken ) + { + HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + OOO_STRING_SVTOOLS_HTML_preformtxt), false ); + SetLFPossible(true); + } + m_nLastParaToken = nNew; +} + +sal_uInt16 SwHTMLWriter::GetCSS1ScriptForScriptType( sal_uInt16 nScriptType ) +{ + sal_uInt16 nRet = CSS1_OUTMODE_ANY_SCRIPT; + + switch( nScriptType ) + { + case i18n::ScriptType::LATIN: + nRet = CSS1_OUTMODE_WESTERN; + break; + case i18n::ScriptType::ASIAN: + nRet = CSS1_OUTMODE_CJK; + break; + case i18n::ScriptType::COMPLEX: + nRet = CSS1_OUTMODE_CTL; + break; + } + + return nRet; +} + +// a single output function should be enough for all formats +/* + * Output the formats as follows + * - output the tag for formats for which a corresponding HTML tag exist + * - for all the other formats, output a paragraph tag <P> and set bUserFormat + * - if a paragraph alignment is set for the supplied ItemSet of the node or + * for the ItemSet of the format, output an ALIGN=xxx if HTML allows it + * - In all cases, hard attribute is written as STYLE option. + * If bUserFormat is not set, only the supplied ItemSet is considered. + * Otherwise, attributes of the format are output as well. + */ + +namespace { + +struct SwHTMLTextCollOutputInfo +{ + OString aToken; // End token to be output + std::optional<SfxItemSet> moItemSet; // hard attribute + + bool bInNumberBulletList; // in an enumerated list; + bool bParaPossible; // a </P> may be output additionally + bool bOutPara; // a </P> is supposed to be output + bool bOutDiv; // write a </DIV> + + SwHTMLTextCollOutputInfo() : + bInNumberBulletList( false ), + bParaPossible( false ), + bOutPara( false ), + bOutDiv( false ) + {} + + bool HasParaToken() const { return aToken.getLength()==1 && aToken[0]=='P'; } + bool ShouldOutputToken() const { return bOutPara || !HasParaToken(); } +}; + +} + +SwHTMLFormatInfo::SwHTMLFormatInfo( const SwFormat *pF, SwDoc *pDoc, SwDoc *pTemplate, + bool bOutStyles, + LanguageType eDfltLang, + sal_uInt16 nCSS1Script ) + : pFormat(pF) + , nLeftMargin(0) + , nRightMargin(0) + , nFirstLineIndent(0) + , nTopMargin(0) + , nBottomMargin(0) + , bScriptDependent( false ) +{ + sal_uInt16 nRefPoolId = 0; + // Get the selector of the format + sal_uInt16 nDeep = SwHTMLWriter::GetCSS1Selector( pFormat, aToken, aClass, + nRefPoolId ); + OSL_ENSURE( nDeep ? !aToken.isEmpty() : aToken.isEmpty(), + "Something seems to be wrong with this token!" ); + OSL_ENSURE( nDeep ? nRefPoolId != 0 : nRefPoolId == 0, + "Something seems to be wrong with the comparison style!" ); + + bool bTextColl = pFormat->Which() == RES_TXTFMTCOLL || + pFormat->Which() == RES_CONDTXTFMTCOLL; + + const SwFormat *pReferenceFormat = nullptr; // Comparison format + if( nDeep != 0 ) + { + // It's an HTML-tag style or this style is derived from such + // a style. + if( !bOutStyles ) + { + // if no styles are exported, it may be necessary to additionally + // write hard attribute + switch( nDeep ) + { + case CSS1_FMT_ISTAG: + case CSS1_FMT_CMPREF: + // for HTML-tag styles the differences to the original + // (if available) + pReferenceFormat = SwHTMLWriter::GetTemplateFormat( nRefPoolId, + &pTemplate->getIDocumentStylePoolAccess() ); + break; + + default: + // otherwise, the differences to the HTML-tag style of the + // original or the ones to the current document, if it the + // HTML-tag style is not available + if( pTemplate ) + pReferenceFormat = SwHTMLWriter::GetTemplateFormat( nRefPoolId, + &pTemplate->getIDocumentStylePoolAccess() ); + else + pReferenceFormat = SwHTMLWriter::GetParentFormat( *pFormat, nDeep ); + break; + } + } + } + else if( bTextColl ) + { + // HTML-tag styles that are not derived from a paragraph style + // must be exported as hard attribute relative to the text-body + // style. For a 'not-styles' export, the one of the HTML style + // should be used as a reference + if( !bOutStyles && pTemplate ) + pReferenceFormat = pTemplate->getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT, false ); + else + pReferenceFormat = pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_TEXT, false ); + } + + if( pReferenceFormat || nDeep==0 ) + { + moItemSet.emplace( *pFormat->GetAttrSet().GetPool(), + pFormat->GetAttrSet().GetRanges() ); + // if the differences to a different style are supposed to be + // written, hard attribute is necessary. This is always true + // for styles that are not derived from HTML-tag styles. + + moItemSet->Set( pFormat->GetAttrSet() ); + + if( pReferenceFormat ) + SwHTMLWriter::SubtractItemSet( *moItemSet, pReferenceFormat->GetAttrSet(), true ); + + // delete ItemSet that is empty straight away. This will save work + // later on + if( !moItemSet->Count() ) + { + moItemSet.reset(); + } + } + + if( !bTextColl ) + return; + + if( bOutStyles ) + { + // We have to add hard attributes for any script dependent + // item that is not accessed by the style + static const sal_uInt16 aWhichIds[3][4] = + { + { RES_CHRATR_FONT, RES_CHRATR_FONTSIZE, + RES_CHRATR_POSTURE, RES_CHRATR_WEIGHT }, + { RES_CHRATR_CJK_FONT, RES_CHRATR_CJK_FONTSIZE, + RES_CHRATR_CJK_POSTURE, RES_CHRATR_CJK_WEIGHT }, + { RES_CHRATR_CTL_FONT, RES_CHRATR_CTL_FONTSIZE, + RES_CHRATR_CTL_POSTURE, RES_CHRATR_CTL_WEIGHT } + }; + + sal_uInt16 nRef = 0; + sal_uInt16 aSets[2] = {0,0}; + switch( nCSS1Script ) + { + case CSS1_OUTMODE_WESTERN: + nRef = 0; + aSets[0] = 1; + aSets[1] = 2; + break; + case CSS1_OUTMODE_CJK: + nRef = 1; + aSets[0] = 0; + aSets[1] = 2; + break; + case CSS1_OUTMODE_CTL: + nRef = 2; + aSets[0] = 0; + aSets[1] = 1; + break; + } + for( int i=0; i<4; ++i ) + { + const SfxPoolItem& rRef = pFormat->GetFormatAttr( aWhichIds[nRef][i] ); + for(sal_uInt16 nSet : aSets) + { + const SfxPoolItem& rSet = pFormat->GetFormatAttr( aWhichIds[nSet][i] ); + if( rSet != rRef ) + { + if( !moItemSet ) + moItemSet.emplace( *pFormat->GetAttrSet().GetPool(), + pFormat->GetAttrSet().GetRanges() ); + moItemSet->Put( rSet ); + } + } + } + } + + // remember all the different default spacings from the style or + // the comparison style. + SvxFirstLineIndentItem const& rFirstLine( + (pReferenceFormat ? pReferenceFormat : pFormat)->GetFirstLineIndent()); + SvxTextLeftMarginItem const& rTextLeftMargin( + (pReferenceFormat ? pReferenceFormat : pFormat)->GetTextLeftMargin()); + SvxRightMarginItem const& rRightMargin( + (pReferenceFormat ? pReferenceFormat : pFormat)->GetRightMargin()); + nLeftMargin = rTextLeftMargin.GetTextLeft(); + nRightMargin = rRightMargin.GetRight(); + nFirstLineIndent = rFirstLine.GetTextFirstLineOffset(); + + const SvxULSpaceItem &rULSpace = + (pReferenceFormat ? pReferenceFormat : pFormat)->GetULSpace(); + nTopMargin = rULSpace.GetUpper(); + nBottomMargin = rULSpace.GetLower(); + + // export language if it differs from the default language + TypedWhichId<SvxLanguageItem> nWhichId = + SwHTMLWriter::GetLangWhichIdFromScript( nCSS1Script ); + const SvxLanguageItem& rLang = pFormat->GetFormatAttr( nWhichId ); + LanguageType eLang = rLang.GetLanguage(); + if( eLang != eDfltLang ) + { + if( !moItemSet ) + moItemSet.emplace( *pFormat->GetAttrSet().GetPool(), + pFormat->GetAttrSet().GetRanges() ); + moItemSet->Put( rLang ); + } + + static const TypedWhichId<SvxLanguageItem> aWhichIds[3] = + { RES_CHRATR_LANGUAGE, RES_CHRATR_CJK_LANGUAGE, + RES_CHRATR_CTL_LANGUAGE }; + for(const TypedWhichId<SvxLanguageItem>& i : aWhichIds) + { + if( i != nWhichId ) + { + const SvxLanguageItem& rTmpLang = pFormat->GetFormatAttr(i); + if( rTmpLang.GetLanguage() != eLang ) + { + if( !moItemSet ) + moItemSet.emplace( *pFormat->GetAttrSet().GetPool(), + pFormat->GetAttrSet().GetRanges() ); + moItemSet->Put( rTmpLang ); + } + } + } + +} + +SwHTMLFormatInfo::~SwHTMLFormatInfo() +{ +} + +static void OutHTML_SwFormat( SwHTMLWriter& rWrt, const SwFormat& rFormat, + const SfxItemSet *pNodeItemSet, + SwHTMLTextCollOutputInfo& rInfo ) +{ + OSL_ENSURE( RES_CONDTXTFMTCOLL==rFormat.Which() || RES_TXTFMTCOLL==rFormat.Which(), + "not a paragraph style" ); + + // First, some flags + sal_uInt16 nNewDefListLvl = 0; + sal_uInt16 nNumStart = USHRT_MAX; + bool bForceDL = false; + bool bDT = false; + rInfo.bInNumberBulletList = false; // Are we in a list? + bool bNumbered = false; // The current paragraph is numbered + bool bPara = false; // the current token is <P> + rInfo.bParaPossible = false; // a <P> may be additionally output + bool bNoEndTag = false; // don't output an end tag + + rWrt.m_bNoAlign = false; // no ALIGN=... possible + + if (rWrt.mbXHTML) + { + rWrt.m_bNoAlign = true; + } + + sal_uInt8 nBulletGrfLvl = 255; // The bullet graphic we want to output + + // Are we in a bulleted or numbered list? + const SwTextNode* pTextNd = rWrt.m_pCurrentPam->GetPointNode().GetTextNode(); + + SwHTMLNumRuleInfo aNumInfo; + if( rWrt.GetNextNumInfo() ) + { + aNumInfo = *rWrt.GetNextNumInfo(); + rWrt.ClearNextNumInfo(); + } + else + { + aNumInfo.Set( *pTextNd ); + } + + if( aNumInfo.GetNumRule() ) + { + rInfo.bInNumberBulletList = true; + nNewDefListLvl = 0; + + // is the current paragraph numbered? + bNumbered = aNumInfo.IsNumbered(); + sal_uInt8 nLvl = aNumInfo.GetLevel(); + + OSL_ENSURE( pTextNd->GetActualListLevel() == nLvl, + "Remembered Num level is wrong" ); + OSL_ENSURE( bNumbered == pTextNd->IsCountedInList(), + "Remembered numbering state is wrong" ); + + if( bNumbered ) + { + nBulletGrfLvl = nLvl; // only temporarily!!! + // #i57919# + // correction of re-factoring done by cws swnumtree: + // - <nNumStart> has to contain the restart value, if the + // numbering is restarted at this text node. Value <USHRT_MAX> + // indicates, that no additional restart value has to be written. + if ( pTextNd->IsListRestart() ) + { + nNumStart = static_cast< sal_uInt16 >(pTextNd->GetActualListStartValue()); + } + OSL_ENSURE( rWrt.m_nLastParaToken == HtmlTokenId::NONE, + "<PRE> was not closed before <LI>." ); + } + } + + // Now, we're getting the token and, if necessary, the class + std::unique_ptr<SwHTMLFormatInfo> pTmpInfo(new SwHTMLFormatInfo(&rFormat)); + SwHTMLFormatInfo *pFormatInfo; + SwHTMLFormatInfos::iterator it = rWrt.m_TextCollInfos.find( pTmpInfo ); + if (it != rWrt.m_TextCollInfos.end()) + { + pFormatInfo = it->get(); + } + else + { + pFormatInfo = new SwHTMLFormatInfo( &rFormat, rWrt.m_pDoc, rWrt.m_xTemplate.get(), + rWrt.m_bCfgOutStyles, rWrt.m_eLang, + rWrt.m_nCSS1Script ); + rWrt.m_TextCollInfos.insert(std::unique_ptr<SwHTMLFormatInfo>(pFormatInfo)); + if( rWrt.m_aScriptParaStyles.count( rFormat.GetName() ) ) + pFormatInfo->bScriptDependent = true; + } + + // Now, we define what is possible due to the token + HtmlTokenId nToken = HtmlTokenId::NONE; // token for tag change + bool bOutNewLine = false; // only output a single LF? + if( !pFormatInfo->aToken.isEmpty() ) + { + // It is an HTML-tag style or the style is derived from such a + // style. + rInfo.aToken = pFormatInfo->aToken; + + if (rInfo.aToken == OOO_STRING_SVTOOLS_HTML_address) + { + rInfo.bParaPossible = true; + rWrt.m_bNoAlign = true; + } + else if (rInfo.aToken == OOO_STRING_SVTOOLS_HTML_blockquote) + { + rInfo.bParaPossible = true; + rWrt.m_bNoAlign = true; + } + else if (rInfo.aToken == OOO_STRING_SVTOOLS_HTML_parabreak) + { + bPara = true; + } + else if (rInfo.aToken == OOO_STRING_SVTOOLS_HTML_preformtxt) + { + if (HtmlTokenId::PREFORMTXT_ON == rWrt.m_nLastParaToken) + { + bOutNewLine = true; + } + else + { + nToken = HtmlTokenId::PREFORMTXT_ON; + rWrt.m_bNoAlign = true; + bNoEndTag = true; + } + } + else if (rInfo.aToken == OOO_STRING_SVTOOLS_HTML_dt || rInfo.aToken == OOO_STRING_SVTOOLS_HTML_dd) + { + bDT = rInfo.aToken == OOO_STRING_SVTOOLS_HTML_dt; + rInfo.bParaPossible = !bDT; + rWrt.m_bNoAlign = true; + bForceDL = true; + } + } + else + { + // all styles that do not correspond to an HTML tag, or that are + // not derived from it, are exported as <P> + + rInfo.aToken = OOO_STRING_SVTOOLS_HTML_parabreak ""_ostr; + bPara = true; + } + + // If necessary, take the hard attribute from the style + if( pFormatInfo->moItemSet ) + { + OSL_ENSURE(!rInfo.moItemSet, "Where does this ItemSet come from?"); + rInfo.moItemSet.emplace( *pFormatInfo->moItemSet ); + } + + // additionally, add the hard attribute from the paragraph + if( pNodeItemSet ) + { + if (rInfo.moItemSet) + rInfo.moItemSet->Put( *pNodeItemSet ); + else + rInfo.moItemSet.emplace( *pNodeItemSet ); + } + + // we will need the lower spacing of the paragraph later on + const SvxULSpaceItem& rULSpace = + pNodeItemSet ? pNodeItemSet->Get(RES_UL_SPACE) + : rFormat.GetULSpace(); + + if( (rWrt.m_bOutHeader && + rWrt.m_pCurrentPam->GetPoint()->GetNodeIndex() == + rWrt.m_pCurrentPam->GetMark()->GetNodeIndex()) || + rWrt.m_bOutFooter ) + { + if( rWrt.m_bCfgOutStyles ) + { + SvxULSpaceItem aULSpaceItem( rULSpace ); + if( rWrt.m_bOutHeader ) + aULSpaceItem.SetLower( rWrt.m_nHeaderFooterSpace ); + else + aULSpaceItem.SetUpper( rWrt.m_nHeaderFooterSpace ); + + if (!rInfo.moItemSet) + { + rInfo.moItemSet.emplace(*rFormat.GetAttrSet().GetPool(), svl::Items<RES_UL_SPACE, RES_UL_SPACE>); + } + rInfo.moItemSet->Put( aULSpaceItem ); + } + rWrt.m_bOutHeader = false; + rWrt.m_bOutFooter = false; + } + + if( bOutNewLine ) + { + // output a line break (without indentation) at the beginning of the + // paragraph, only + rInfo.aToken.clear(); // don't output an end tag + rWrt.Strm().WriteOString( SAL_NEWLINE_STRING ); + + return; + } + + // should an ALIGN=... be written? + const SvxAdjustItem* pAdjItem = nullptr; + + if( rInfo.moItemSet ) + pAdjItem = rInfo.moItemSet->GetItemIfSet( RES_PARATR_ADJUST, false ); + + // Consider the lower spacing of the paragraph? (never in the last + // paragraph of tables) + bool bUseParSpace = !rWrt.m_bOutTable || + (rWrt.m_pCurrentPam->GetPoint()->GetNodeIndex() != + rWrt.m_pCurrentPam->GetMark()->GetNodeIndex()); + // If styles are exported, indented paragraphs become definition lists + SvxFirstLineIndentItem const& rFirstLine( + pNodeItemSet ? pNodeItemSet->Get(RES_MARGIN_FIRSTLINE) + : rFormat.GetFirstLineIndent()); + SvxTextLeftMarginItem const& rTextLeftMargin( + pNodeItemSet ? pNodeItemSet->Get(RES_MARGIN_TEXTLEFT) + : rFormat.GetTextLeftMargin()); + if( (!rWrt.m_bCfgOutStyles || bForceDL) && !rInfo.bInNumberBulletList ) + { + sal_Int32 nLeftMargin; + if( bForceDL ) + nLeftMargin = rTextLeftMargin.GetTextLeft(); + else + nLeftMargin = rTextLeftMargin.GetTextLeft() > pFormatInfo->nLeftMargin + ? rTextLeftMargin.GetTextLeft() - pFormatInfo->nLeftMargin + : 0; + + if( nLeftMargin > 0 && rWrt.m_nDefListMargin > 0 ) + { + nNewDefListLvl = static_cast< sal_uInt16 >((nLeftMargin + (rWrt.m_nDefListMargin/2)) / + rWrt.m_nDefListMargin); + if( nNewDefListLvl == 0 && bForceDL && !bDT ) + nNewDefListLvl = 1; + } + else + { + // If the left margin is 0 or negative, emulating indent + // with <dd> does not work. We then set a def list only if + // the dd style is used. + nNewDefListLvl = (bForceDL&& !bDT) ? 1 : 0; + } + + bool bIsNextTextNode = + rWrt.m_pDoc->GetNodes()[rWrt.m_pCurrentPam->GetPoint()->GetNodeIndex()+1] + ->IsTextNode(); + + if( bForceDL && bDT ) + { + // Instead of a DD we must use a DT from the level above this one. + nNewDefListLvl++; + } + else if( !nNewDefListLvl && !rWrt.m_bCfgOutStyles && bPara && + rULSpace.GetLower()==0 && + ((bUseParSpace && bIsNextTextNode) || rWrt.m_nDefListLvl==1) && + (!pAdjItem || SvxAdjust::Left==pAdjItem->GetAdjust()) ) + { + // Export paragraphs without a lower spacing as DT + nNewDefListLvl = 1; + bDT = true; + rInfo.bParaPossible = false; + rWrt.m_bNoAlign = true; + } + } + + if( nNewDefListLvl != rWrt.m_nDefListLvl ) + rWrt.OutAndSetDefList( nNewDefListLvl ); + + // if necessary, start a bulleted or numbered list + if( rInfo.bInNumberBulletList ) + { + OSL_ENSURE( !rWrt.m_nDefListLvl, "DL cannot be inside OL!" ); + OutHTML_NumberBulletListStart( rWrt, aNumInfo ); + + if( bNumbered ) + { + if( !rWrt.m_aBulletGrfs[nBulletGrfLvl].isEmpty() ) + bNumbered = false; + else + nBulletGrfLvl = 255; + } + } + + // Take the defaults of the style, because they don't need to be + // exported + rWrt.m_nDfltLeftMargin = pFormatInfo->nLeftMargin; + rWrt.m_nDfltRightMargin = pFormatInfo->nRightMargin; + rWrt.m_nDfltFirstLineIndent = pFormatInfo->nFirstLineIndent; + + if( rInfo.bInNumberBulletList ) + { + if( !rWrt.IsHTMLMode( HTMLMODE_LSPACE_IN_NUMBER_BULLET ) ) + rWrt.m_nDfltLeftMargin = rTextLeftMargin.GetTextLeft(); + + // In numbered lists, don't output a first line indent. + rWrt.m_nFirstLineIndent = rFirstLine.GetTextFirstLineOffset(); + } + + if( rInfo.bInNumberBulletList && bNumbered && bPara && !rWrt.m_bCfgOutStyles ) + { + // a single LI doesn't have spacing + rWrt.m_nDfltTopMargin = 0; + rWrt.m_nDfltBottomMargin = 0; + } + else if( rWrt.m_nDefListLvl && bPara ) + { + // a single DD doesn't have spacing, as well + rWrt.m_nDfltTopMargin = 0; + rWrt.m_nDfltBottomMargin = 0; + } + else + { + rWrt.m_nDfltTopMargin = pFormatInfo->nTopMargin; + // if in the last paragraph of a table the lower paragraph spacing + // is changed, Netscape doesn't get it. That's why we don't + // export anything here for now, by setting this spacing to the + // default value. + if( rWrt.m_bCfgNetscape4 && !bUseParSpace ) + rWrt.m_nDfltBottomMargin = rULSpace.GetLower(); + else + rWrt.m_nDfltBottomMargin = pFormatInfo->nBottomMargin; + } + + if( rWrt.m_nDefListLvl ) + { + rWrt.m_nLeftMargin = + (rWrt.m_nDefListLvl-1) * rWrt.m_nDefListMargin; + } + + if (rWrt.IsLFPossible() && !rWrt.m_bFirstLine) + rWrt.OutNewLine(); // paragraph tag on a new line + rInfo.bOutPara = false; + + // this is now our new token + rWrt.ChangeParaToken( nToken ); + + bool bHasParSpace = bUseParSpace && rULSpace.GetLower() > 0; + // XHTML doesn't allow character children for <blockquote>. + bool bXhtmlBlockQuote = rWrt.mbXHTML && rInfo.aToken == OOO_STRING_SVTOOLS_HTML_blockquote; + + // if necessary, start a new list item + bool bNumberedForListItem = bNumbered; + if (!bNumberedForListItem) + { + // Open a list also for the leading unnumbered nodes (= list headers in ODF terminology); + // to do that, detect if this unnumbered node is the first in this list + const auto& rPrevListInfo = rWrt.GetNumInfo(); + if (rPrevListInfo.GetNumRule() != aNumInfo.GetNumRule() || aNumInfo.IsRestart(rPrevListInfo) + || rPrevListInfo.GetDepth() < aNumInfo.GetDepth()) + bNumberedForListItem = true; + } + if( rInfo.bInNumberBulletList && bNumberedForListItem ) + { + OStringBuffer sOut(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_li); + if (!bNumbered) + { + // Handles list headers (<text:list-header> ODF element) + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_style "=\"display: block\""); + } + else if (USHRT_MAX != nNumStart) + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_value "=\"" + OString::number(nNumStart) + + "\""); + HTMLOutFuncs::Out_AsciiTag(rWrt.Strm(), sOut); + } + + if( rWrt.m_nDefListLvl > 0 && !bForceDL ) + { + OString aTag = bDT ? OOO_STRING_SVTOOLS_HTML_dt : OOO_STRING_SVTOOLS_HTML_dd; + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + aTag) ); + } + + if( pAdjItem && + rWrt.IsHTMLMode( HTMLMODE_NO_CONTROL_CENTERING ) && + rWrt.HasControls() ) + { + // The align=... attribute does behave strange in netscape + // if there are controls in a paragraph, because the control and + // all text behind the control does not recognize this attribute. + OString sOut = "<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_division; + rWrt.Strm().WriteOString( sOut ); + + rWrt.m_bTextAttr = false; + rWrt.m_bOutOpts = true; + OutHTML_SvxAdjust( rWrt, *pAdjItem ); + rWrt.Strm().WriteChar( '>' ); + pAdjItem = nullptr; + rWrt.m_bNoAlign = false; + rInfo.bOutDiv = true; + rWrt.IncIndentLevel(); + rWrt.SetLFPossible(true); + rWrt.OutNewLine(); + } + + // for BLOCKQUOTE, ADDRESS and DD we output another paragraph token, if + // - no styles are written and + // - a lower spacing or a paragraph alignment exists + // Also, XHTML does not allow character children in this context. + OString aToken = rInfo.aToken; + if( (!rWrt.m_bCfgOutStyles || rWrt.mbXHTML) && rInfo.bParaPossible && !bPara && + (bHasParSpace || bXhtmlBlockQuote || pAdjItem) ) + { + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + rInfo.aToken) ); + aToken = OOO_STRING_SVTOOLS_HTML_parabreak ""_ostr; + bPara = true; + rWrt.m_bNoAlign = false; + } + + LanguageType eLang; + if (rInfo.moItemSet) + { + const SvxLanguageItem& rLangItem = rInfo.moItemSet->Get(SwHTMLWriter::GetLangWhichIdFromScript(rWrt.m_nCSS1Script)); + eLang = rLangItem.GetLanguage(); + } + else + eLang = rWrt.m_eLang; + + if( rInfo.moItemSet ) + { + static const TypedWhichId<SvxLanguageItem> aWhichIds[3] = { RES_CHRATR_LANGUAGE, RES_CHRATR_CJK_LANGUAGE, RES_CHRATR_CTL_LANGUAGE }; + + for(auto const & i : aWhichIds) + { + // export language if it differs from the default language only. + const SvxLanguageItem* pTmpItem = rInfo.moItemSet->GetItemIfSet( i ); + if( pTmpItem && pTmpItem->GetLanguage() == eLang ) + rInfo.moItemSet->ClearItem( i ); + } + } + + // and the text direction + SvxFrameDirection nDir = rWrt.GetHTMLDirection( + (pNodeItemSet ? pNodeItemSet->Get( RES_FRAMEDIR ) + : rFormat.GetFrameDir() ).GetValue() ); + + // We only write a <P>, if + // - we are not inside OL/UL/DL, or + // - the paragraph of an OL/UL is not numbered or + // - styles are not exported and + // - a lower spacing, or + // - a paragraph alignment exists, or + // - styles are exported and + // - the text body style was changed, or + // - a user format is exported, or + // - a paragraph attribute exists + if( !bPara || + (!rInfo.bInNumberBulletList && !rWrt.m_nDefListLvl) || + (rInfo.bInNumberBulletList && !bNumbered) || + (!rWrt.m_bCfgOutStyles && + (bHasParSpace || bXhtmlBlockQuote || pAdjItem || + (eLang != LANGUAGE_DONTKNOW && eLang != rWrt.m_eLang))) || + nDir != rWrt.m_nDirection || + rWrt.m_bCfgOutStyles ) + { + // now, options are output + rWrt.m_bTextAttr = false; + rWrt.m_bOutOpts = true; + + OString sOut = "<" + rWrt.GetNamespace() + aToken; + + if( eLang != LANGUAGE_DONTKNOW && eLang != rWrt.m_eLang ) + { + rWrt.Strm().WriteOString( sOut ); + sOut = ""_ostr; + rWrt.OutLanguage( eLang ); + } + + if( nDir != rWrt.m_nDirection ) + { + if( !sOut.isEmpty() ) + { + rWrt.Strm().WriteOString( sOut ); + sOut = ""_ostr; + } + rWrt.OutDirection( nDir ); + } + + if( rWrt.m_bCfgOutStyles && + (!pFormatInfo->aClass.isEmpty() || pFormatInfo->bScriptDependent) ) + { + sOut += " " OOO_STRING_SVTOOLS_HTML_O_class "=\""; + rWrt.Strm().WriteOString( sOut ); + sOut = ""_ostr; + OUString aClass( pFormatInfo->aClass ); + if( pFormatInfo->bScriptDependent ) + { + if( !aClass.isEmpty() ) + aClass += "-"; + switch( rWrt.m_nCSS1Script ) + { + case CSS1_OUTMODE_WESTERN: + aClass += "western"; + break; + case CSS1_OUTMODE_CJK: + aClass += "cjk"; + break; + case CSS1_OUTMODE_CTL: + aClass += "ctl"; + break; + } + } + HTMLOutFuncs::Out_String( rWrt.Strm(), aClass ); + sOut += "\""; + } + rWrt.Strm().WriteOString( sOut ); + sOut = ""_ostr; + + std::string_view sStyle; + if (rWrt.IsSpacePreserve()) + { + if (rWrt.mbXHTML) + rWrt.Strm().WriteOString(" xml:space=\"preserve\""); + else + sStyle = "white-space: pre-wrap"; + } + + // if necessary, output alignment + if( !rWrt.m_bNoAlign && pAdjItem ) + OutHTML_SvxAdjust( rWrt, *pAdjItem ); + + rWrt.m_bParaDotLeaders = bPara && rWrt.m_bCfgPrintLayout && rWrt.indexOfDotLeaders( + pTextNd->GetAnyFormatColl().GetPoolFormatId(), pTextNd->GetText()) > -1; + + // and now, if necessary, the STYLE options + if (rWrt.m_bCfgOutStyles && rInfo.moItemSet) + { + OutCSS1_ParaTagStyleOpt(rWrt, *rInfo.moItemSet, sStyle); + } + + if (rWrt.m_bParaDotLeaders) { + sOut += " " OOO_STRING_SVTOOLS_HTML_O_class "=\"" + sCSS2_P_CLASS_leaders "\"><" + OOO_STRING_SVTOOLS_HTML_O_span; + rWrt.Strm().WriteOString( sOut ); + sOut = ""_ostr; + } + + rWrt.Strm().WriteChar( '>' ); + + // is a </P> supposed to be written? + rInfo.bOutPara = + bPara && + ( rWrt.m_bCfgOutStyles || bHasParSpace ); + + // if no end tag is supposed to be written, delete it + if( bNoEndTag ) + rInfo.aToken.clear(); + } + + if( nBulletGrfLvl != 255 ) + { + OSL_ENSURE( aNumInfo.GetNumRule(), "Where is the numbering gone???" ); + OSL_ENSURE( nBulletGrfLvl < MAXLEVEL, "There are not this many layers." ); + const SwNumFormat& rNumFormat = aNumInfo.GetNumRule()->Get(nBulletGrfLvl); + OutHTML_BulletImage( rWrt, OOO_STRING_SVTOOLS_HTML_image, rNumFormat.GetBrush(), + rWrt.m_aBulletGrfs[nBulletGrfLvl]); + } + + rWrt.GetNumInfo() = aNumInfo; + + // reset the defaults + rWrt.m_nDfltLeftMargin = 0; + rWrt.m_nDfltRightMargin = 0; + rWrt.m_nDfltFirstLineIndent = 0; + rWrt.m_nDfltTopMargin = 0; + rWrt.m_nDfltBottomMargin = 0; + rWrt.m_nLeftMargin = 0; + rWrt.m_nFirstLineIndent = 0; +} + +static void OutHTML_SwFormatOff( SwHTMLWriter& rWrt, const SwHTMLTextCollOutputInfo& rInfo ) +{ + // if there is no token, we don't need to output anything + if( rInfo.aToken.isEmpty() ) + { + rWrt.FillNextNumInfo(); + const SwHTMLNumRuleInfo& rNextInfo = *rWrt.GetNextNumInfo(); + // a bulleted list must be closed in PRE as well + if( rInfo.bInNumberBulletList ) + { + + const SwHTMLNumRuleInfo& rNRInfo = rWrt.GetNumInfo(); + if( rNextInfo.GetNumRule() != rNRInfo.GetNumRule() || + rNextInfo.GetDepth() != rNRInfo.GetDepth() || + rNextInfo.IsNumbered() || rNextInfo.IsRestart(rNRInfo) ) + rWrt.ChangeParaToken( HtmlTokenId::NONE ); + OutHTML_NumberBulletListEnd( rWrt, rNextInfo ); + } + else if( rNextInfo.GetNumRule() != nullptr ) + rWrt.ChangeParaToken( HtmlTokenId::NONE ); + + return; + } + + if( rInfo.ShouldOutputToken() ) + { + if (rWrt.IsPrettyPrint() && rWrt.IsLFPossible()) + rWrt.OutNewLine( true ); + + // if necessary, for BLOCKQUOTE, ADDRESS and DD another paragraph token + // is output, if + // - no styles are written and + // - a lower spacing exists + if( rInfo.bParaPossible && rInfo.bOutPara ) + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_parabreak), false ); + + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + rInfo.aToken), false ); + rWrt.SetLFPossible( + rInfo.aToken != OOO_STRING_SVTOOLS_HTML_dt && + rInfo.aToken != OOO_STRING_SVTOOLS_HTML_dd && + rInfo.aToken != OOO_STRING_SVTOOLS_HTML_li); + } + if( rInfo.bOutDiv ) + { + rWrt.DecIndentLevel(); + if (rWrt.IsLFPossible()) + rWrt.OutNewLine(); + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_division), false ); + rWrt.SetLFPossible(true); + } + + // if necessary, close the list item, then close a bulleted or numbered list + if( rInfo.bInNumberBulletList ) + { + rWrt.FillNextNumInfo(); + OutHTML_NumberBulletListEnd( rWrt, *rWrt.GetNextNumInfo() ); + } +} + +namespace { + +class HTMLStartEndPos +{ + sal_Int32 m_nStart; + sal_Int32 m_nEnd; + std::unique_ptr<SfxPoolItem> m_pItem; + +public: + + HTMLStartEndPos( const SfxPoolItem& rItem, sal_Int32 nStt, sal_Int32 nE ); + + const SfxPoolItem* GetItem() const { return m_pItem.get(); } + + void SetStart(sal_Int32 nStt) { m_nStart = nStt; } + sal_Int32 GetStart() const { return m_nStart; } + + sal_Int32 GetEnd() const { return m_nEnd; } + void SetEnd(sal_Int32 nE) { m_nEnd = nE; } +}; + +} + +HTMLStartEndPos::HTMLStartEndPos(const SfxPoolItem& rItem, sal_Int32 nStt, sal_Int32 nE) + : m_nStart(nStt) + , m_nEnd(nE) + , m_pItem(rItem.Clone()) +{} + +typedef std::vector<HTMLStartEndPos *> HTMLStartEndPositions; + +namespace { + +enum HTMLOnOffState { HTML_NOT_SUPPORTED, // unsupported Attribute + HTML_REAL_VALUE, // Attribute with value + HTML_ON_VALUE, // Attribute is On-Tag + HTML_OFF_VALUE, // Attribute is Off-Tag + HTML_CHRFMT_VALUE, // Attribute for character format + HTML_COLOR_VALUE, // Attribute for foreground color + HTML_STYLE_VALUE, // Attribute must be exported as style + HTML_DROPCAP_VALUE, // DropCap-Attribute + HTML_AUTOFMT_VALUE }; // Attribute for automatic character styles + +class HTMLEndPosLst +{ + HTMLStartEndPositions m_aStartLst; // list, sorted for start positions + HTMLStartEndPositions m_aEndLst; // list, sorted for end positions + std::deque<sal_Int32> m_aScriptChgLst; // positions where script changes + // 0 is not contained in this list, + // but the text length + // the script that is valid up to the position + // contained in aScriptChgList at the same index + std::vector<sal_uInt16> m_aScriptLst; + + SwDoc* m_pDoc; // the current document + SwDoc* m_pTemplate; // the HTML template (or 0) + std::optional<Color> m_xDefaultColor; // the default foreground colors + std::set<OUString>& m_rScriptTextStyles; + + sal_uLong m_nHTMLMode; + bool m_bOutStyles : 1; // are styles exported + + // Insert/remove a SttEndPos in/from the Start and End lists. + // The end position is known. + void InsertItem_( HTMLStartEndPos *pPos, HTMLStartEndPositions::size_type nEndPos ); + void RemoveItem_( HTMLStartEndPositions::size_type nEndPos ); + + // determine the 'type' of the attribute + HTMLOnOffState GetHTMLItemState( const SfxPoolItem& rItem ); + + // does a specific OnTag item exist + bool ExistsOnTagItem( sal_uInt16 nWhich, sal_Int32 nPos ); + + // does an item exist that can be used to disable an attribute that + // is exported the same way as the supplied item in the same range? + bool ExistsOffTagItem( sal_uInt16 nWhich, sal_Int32 nStartPos, + sal_Int32 nEndPos ); + + // adapt the end of a split item + void FixSplittedItem( HTMLStartEndPos *pPos, sal_Int32 nNewEnd, + HTMLStartEndPositions::size_type nStartPos ); + + // insert an attribute in the lists and, if necessary, split it + void InsertItem( const SfxPoolItem& rItem, sal_Int32 nStart, + sal_Int32 nEnd ); + + // split an already existing attribute + void SplitItem( const SfxPoolItem& rItem, sal_Int32 nStart, + sal_Int32 nEnd ); + + // Insert without taking care of script + void InsertNoScript( const SfxPoolItem& rItem, sal_Int32 nStart, + sal_Int32 nEnd, SwHTMLFormatInfos& rFormatInfos, + bool bParaAttrs ); + + const SwHTMLFormatInfo *GetFormatInfo( const SwFormat& rFormat, + SwHTMLFormatInfos& rFormatInfos ); + +public: + + HTMLEndPosLst( SwDoc *pDoc, SwDoc* pTemplate, std::optional<Color> xDfltColor, + bool bOutStyles, sal_uLong nHTMLMode, + const OUString& rText, std::set<OUString>& rStyles ); + ~HTMLEndPosLst(); + + // insert an attribute + void Insert( const SfxPoolItem& rItem, sal_Int32 nStart, sal_Int32 nEnd, + SwHTMLFormatInfos& rFormatInfos, bool bParaAttrs=false ); + void Insert( const SfxItemSet& rItemSet, sal_Int32 nStart, sal_Int32 nEnd, + SwHTMLFormatInfos& rFormatInfos, bool bDeep, + bool bParaAttrs=false ); + void Insert( const SwDrawFrameFormat& rFormat, sal_Int32 nPos, + SwHTMLFormatInfos& rFormatInfos ); + + sal_uInt16 GetScriptAtPos( sal_Int32 nPos, + sal_uInt16 nWeak ); + + void OutStartAttrs( SwHTMLWriter& rWrt, sal_Int32 nPos ); + void OutEndAttrs( SwHTMLWriter& rWrt, sal_Int32 nPos ); + + bool IsHTMLMode(sal_uLong nMode) const { return (m_nHTMLMode & nMode) != 0; } +}; + +} + +void HTMLEndPosLst::InsertItem_( HTMLStartEndPos *pPos, HTMLStartEndPositions::size_type nEndPos ) +{ + // Insert the attribute in the Start list behind all attributes that + // were started before, or at the same position. + sal_Int32 nStart = pPos->GetStart(); + HTMLStartEndPositions::size_type i {0}; + + while (i < m_aStartLst.size() && m_aStartLst[i]->GetStart() <= nStart) + ++i; + m_aStartLst.insert(m_aStartLst.begin() + i, pPos); + + // the position in the End list was supplied + m_aEndLst.insert(m_aEndLst.begin() + nEndPos, pPos); +} + +void HTMLEndPosLst::RemoveItem_( HTMLStartEndPositions::size_type nEndPos ) +{ + HTMLStartEndPos* pPos = m_aEndLst[nEndPos]; + + // now, we are looking for it in the Start list + HTMLStartEndPositions::iterator it = std::find(m_aStartLst.begin(), m_aStartLst.end(), pPos); + OSL_ENSURE(it != m_aStartLst.end(), "Item not found in Start List!"); + if (it != m_aStartLst.end()) + m_aStartLst.erase(it); + + m_aEndLst.erase(m_aEndLst.begin() + nEndPos); + + delete pPos; +} + +HTMLOnOffState HTMLEndPosLst::GetHTMLItemState( const SfxPoolItem& rItem ) +{ + HTMLOnOffState eState = HTML_NOT_SUPPORTED; + switch( rItem.Which() ) + { + case RES_CHRATR_POSTURE: + case RES_CHRATR_CJK_POSTURE: + case RES_CHRATR_CTL_POSTURE: + switch( static_cast<const SvxPostureItem&>(rItem).GetPosture() ) + { + case ITALIC_NORMAL: + eState = HTML_ON_VALUE; + break; + case ITALIC_NONE: + eState = HTML_OFF_VALUE; + break; + default: + if( IsHTMLMode(HTMLMODE_SOME_STYLES) ) + eState = HTML_STYLE_VALUE; + break; + } + break; + + case RES_CHRATR_CROSSEDOUT: + switch( rItem.StaticWhichCast(RES_CHRATR_CROSSEDOUT).GetStrikeout() ) + { + case STRIKEOUT_SINGLE: + case STRIKEOUT_DOUBLE: + eState = HTML_ON_VALUE; + break; + case STRIKEOUT_NONE: + eState = HTML_OFF_VALUE; + break; + default: + ; + } + break; + + case RES_CHRATR_ESCAPEMENT: + switch( static_cast<SvxEscapement>(rItem.StaticWhichCast(RES_CHRATR_ESCAPEMENT).GetEnumValue()) ) + { + case SvxEscapement::Superscript: + case SvxEscapement::Subscript: + eState = HTML_ON_VALUE; + break; + case SvxEscapement::Off: + eState = HTML_OFF_VALUE; + break; + default: + ; + } + break; + + case RES_CHRATR_UNDERLINE: + switch( rItem.StaticWhichCast(RES_CHRATR_UNDERLINE).GetLineStyle() ) + { + case LINESTYLE_SINGLE: + eState = HTML_ON_VALUE; + break; + case LINESTYLE_NONE: + eState = HTML_OFF_VALUE; + break; + default: + if( IsHTMLMode(HTMLMODE_SOME_STYLES) ) + eState = HTML_STYLE_VALUE; + break; + } + break; + + case RES_CHRATR_OVERLINE: + case RES_CHRATR_HIDDEN: + if( IsHTMLMode(HTMLMODE_SOME_STYLES) ) + eState = HTML_STYLE_VALUE; + break; + + case RES_CHRATR_WEIGHT: + case RES_CHRATR_CJK_WEIGHT: + case RES_CHRATR_CTL_WEIGHT: + switch( static_cast<const SvxWeightItem&>(rItem).GetWeight() ) + { + case WEIGHT_BOLD: + eState = HTML_ON_VALUE; + break; + case WEIGHT_NORMAL: + eState = HTML_OFF_VALUE; + break; + default: + if( IsHTMLMode(HTMLMODE_SOME_STYLES) ) + eState = HTML_STYLE_VALUE; + break; + } + break; + + case RES_CHRATR_BLINK: + eState = rItem.StaticWhichCast(RES_CHRATR_BLINK).GetValue() ? HTML_ON_VALUE + : HTML_OFF_VALUE; + break; + + case RES_CHRATR_COLOR: + eState = HTML_COLOR_VALUE; + break; + + case RES_CHRATR_FONT: + case RES_CHRATR_FONTSIZE: + case RES_CHRATR_LANGUAGE: + case RES_CHRATR_CJK_FONT: + case RES_CHRATR_CJK_FONTSIZE: + case RES_CHRATR_CJK_LANGUAGE: + case RES_CHRATR_CTL_FONT: + case RES_CHRATR_CTL_FONTSIZE: + case RES_CHRATR_CTL_LANGUAGE: + case RES_TXTATR_INETFMT: + eState = HTML_REAL_VALUE; + break; + + case RES_TXTATR_CHARFMT: + eState = HTML_CHRFMT_VALUE; + break; + + case RES_TXTATR_AUTOFMT: + eState = HTML_AUTOFMT_VALUE; + break; + + case RES_CHRATR_CASEMAP: + eState = HTML_STYLE_VALUE; + break; + + case RES_CHRATR_KERNING: + eState = HTML_STYLE_VALUE; + break; + + case RES_CHRATR_BACKGROUND: + if( IsHTMLMode(HTMLMODE_SOME_STYLES) ) + eState = HTML_STYLE_VALUE; + break; + + case RES_PARATR_DROP: + eState = HTML_DROPCAP_VALUE; + break; + + case RES_CHRATR_BOX: + if( IsHTMLMode(HTMLMODE_SOME_STYLES) ) + eState = HTML_STYLE_VALUE; + break; + } + + return eState; +} + +bool HTMLEndPosLst::ExistsOnTagItem( sal_uInt16 nWhich, sal_Int32 nPos ) +{ + for (auto pTest : m_aStartLst) + { + if( pTest->GetStart() > nPos ) + { + // this attribute, and all attributes that follow, start later + break; + } + else if( pTest->GetEnd() > nPos ) + { + // the attribute starts before, or at, the current position and + // ends after it + const SfxPoolItem *pItem = pTest->GetItem(); + if( pItem->Which() == nWhich && + HTML_ON_VALUE == GetHTMLItemState(*pItem) ) + { + // an OnTag attribute was found + return true; + } + } + } + + return false; +} + +bool HTMLEndPosLst::ExistsOffTagItem( sal_uInt16 nWhich, sal_Int32 nStartPos, + sal_Int32 nEndPos ) +{ + if( nWhich != RES_CHRATR_CROSSEDOUT && + nWhich != RES_CHRATR_UNDERLINE && + nWhich != RES_CHRATR_BLINK ) + { + return false; + } + + for (auto pTest : m_aStartLst) + { + if( pTest->GetStart() > nStartPos ) + { + // this attribute, and all attributes that follow, start later + break; + } + else if( pTest->GetStart()==nStartPos && + pTest->GetEnd()==nEndPos ) + { + // the attribute starts before or at the current position and + // ends after it + const SfxPoolItem *pItem = pTest->GetItem(); + sal_uInt16 nTstWhich = pItem->Which(); + if( (nTstWhich == RES_CHRATR_CROSSEDOUT || + nTstWhich == RES_CHRATR_UNDERLINE || + nTstWhich == RES_CHRATR_BLINK) && + HTML_OFF_VALUE == GetHTMLItemState(*pItem) ) + { + // an OffTag attribute was found that is exported the same + // way as the current item + return true; + } + } + } + + return false; +} + +void HTMLEndPosLst::FixSplittedItem( HTMLStartEndPos *pPos, sal_Int32 nNewEnd, + HTMLStartEndPositions::size_type nStartPos ) +{ + // fix the end position accordingly + pPos->SetEnd( nNewEnd ); + + // remove the item from the End list + HTMLStartEndPositions::iterator it = std::find(m_aEndLst.begin(), m_aEndLst.end(), pPos); + OSL_ENSURE(it != m_aEndLst.end(), "Item not found in End List!"); + if (it != m_aEndLst.end()) + m_aEndLst.erase(it); + + // from now on, it is closed as the last one at the corresponding position + HTMLStartEndPositions::size_type nEndPos {0}; + while (nEndPos < m_aEndLst.size() && m_aEndLst[nEndPos]->GetEnd() <= nNewEnd) + ++nEndPos; + m_aEndLst.insert(m_aEndLst.begin() + nEndPos, pPos); + + // now, adjust the attributes that got started afterwards + for (HTMLStartEndPositions::size_type i = nStartPos + 1; i < m_aStartLst.size(); ++i) + { + HTMLStartEndPos* pTest = m_aStartLst[i]; + sal_Int32 nTestEnd = pTest->GetEnd(); + if( pTest->GetStart() >= nNewEnd ) + { + // the Test attribute and all the following ones start, after the + // split attribute ends + break; + } + else if( nTestEnd > nNewEnd ) + { + // the Test attribute starts before the split attribute + // ends, and ends afterwards, i.e., it must be split, as well + + // set the new end + pTest->SetEnd( nNewEnd ); + + // remove the attribute from the End list + it = std::find(m_aEndLst.begin(), m_aEndLst.end(), pTest); + OSL_ENSURE(it != m_aEndLst.end(), "Item not found in End List!"); + if (it != m_aEndLst.end()) + m_aEndLst.erase(it); + + // it now ends as the first attribute in the respective position. + // We already know this position in the End list. + m_aEndLst.insert(m_aEndLst.begin() + nEndPos, pTest); + + // insert the 'rest' of the attribute + InsertItem( *pTest->GetItem(), nNewEnd, nTestEnd ); + } + } +} + +void HTMLEndPosLst::InsertItem( const SfxPoolItem& rItem, sal_Int32 nStart, + sal_Int32 nEnd ) +{ + HTMLStartEndPositions::size_type i; + for (i = 0; i < m_aEndLst.size(); i++) + { + HTMLStartEndPos* pTest = m_aEndLst[i]; + sal_Int32 nTestEnd = pTest->GetEnd(); + if( nTestEnd <= nStart ) + { + // the Test attribute ends, before the new one starts + continue; + } + else if( nTestEnd < nEnd ) + { + if( pTest->GetStart() < nStart ) + { + // the Test attribute ends, before the new one ends. Thus, the + // new attribute must be split. + InsertItem_( new HTMLStartEndPos( rItem, nStart, nTestEnd ), i ); + nStart = nTestEnd; + } + } + else + { + // the Test attribute (and all that follow) ends, before the new + // one ends + break; + } + } + + // one attribute must still be inserted + InsertItem_( new HTMLStartEndPos( rItem, nStart, nEnd ), i ); +} + +void HTMLEndPosLst::SplitItem( const SfxPoolItem& rItem, sal_Int32 nStart, + sal_Int32 nEnd ) +{ + sal_uInt16 nWhich = rItem.Which(); + + // first, we must search for the old items by using the start list and + // determine the new item range + + for (HTMLStartEndPositions::size_type i = 0; i < m_aStartLst.size(); ++i) + { + HTMLStartEndPos* pTest = m_aStartLst[i]; + sal_Int32 nTestStart = pTest->GetStart(); + sal_Int32 nTestEnd = pTest->GetEnd(); + + if( nTestStart >= nEnd ) + { + // this attribute, and all that follow, start later + break; + } + else if( nTestEnd > nStart ) + { + // the Test attribute ends in the range that must be deleted + const SfxPoolItem *pItem = pTest->GetItem(); + + // only the corresponding OnTag attributes have to be considered + if( pItem->Which() == nWhich && + HTML_ON_VALUE == GetHTMLItemState( *pItem ) ) + { + bool bDelete = true; + + if( nTestStart < nStart ) + { + // the start of the new attribute corresponds to the new + // end of the attribute + FixSplittedItem( pTest, nStart, i ); + bDelete = false; + } + else + { + // the Test item only starts after the new end of the + // attribute. Therefore, it can be completely erased. + m_aStartLst.erase(m_aStartLst.begin() + i); + i--; + + HTMLStartEndPositions::iterator it + = std::find(m_aEndLst.begin(), m_aEndLst.end(), pTest); + OSL_ENSURE(it != m_aEndLst.end(), "Item not found in End List!"); + if (it != m_aEndLst.end()) + m_aEndLst.erase(it); + } + + // if necessary, insert the second part of the split + // attribute + if( nTestEnd > nEnd ) + { + InsertItem( *pTest->GetItem(), nEnd, nTestEnd ); + } + + if( bDelete ) + delete pTest; + } + } + } +} + +const SwHTMLFormatInfo *HTMLEndPosLst::GetFormatInfo( const SwFormat& rFormat, + SwHTMLFormatInfos& rFormatInfos ) +{ + SwHTMLFormatInfo *pFormatInfo; + std::unique_ptr<SwHTMLFormatInfo> pTmpInfo(new SwHTMLFormatInfo(&rFormat)); + SwHTMLFormatInfos::iterator it = rFormatInfos.find( pTmpInfo ); + if (it != rFormatInfos.end()) + { + pFormatInfo = it->get(); + } + else + { + pFormatInfo = new SwHTMLFormatInfo(&rFormat, m_pDoc, m_pTemplate, m_bOutStyles); + rFormatInfos.insert(std::unique_ptr<SwHTMLFormatInfo>(pFormatInfo)); + if (m_rScriptTextStyles.count(rFormat.GetName())) + pFormatInfo->bScriptDependent = true; + } + + return pFormatInfo; +} + +HTMLEndPosLst::HTMLEndPosLst(SwDoc* pD, SwDoc* pTempl, std::optional<Color> xDfltCol, bool bStyles, + sal_uLong nMode, const OUString& rText, std::set<OUString>& rStyles) + : m_pDoc(pD) + , m_pTemplate(pTempl) + , m_xDefaultColor(std::move(xDfltCol)) + , m_rScriptTextStyles(rStyles) + , m_nHTMLMode(nMode) + , m_bOutStyles(bStyles) +{ + sal_Int32 nEndPos = rText.getLength(); + sal_Int32 nPos = 0; + while( nPos < nEndPos ) + { + sal_uInt16 nScript = g_pBreakIt->GetBreakIter()->getScriptType( rText, nPos ); + nPos = g_pBreakIt->GetBreakIter()->endOfScript( rText, nPos, nScript ); + m_aScriptChgLst.push_back(nPos); + m_aScriptLst.push_back(nScript); + } +} + +HTMLEndPosLst::~HTMLEndPosLst() +{ + OSL_ENSURE(m_aStartLst.empty(), "Start List not empty in destructor"); + OSL_ENSURE(m_aEndLst.empty(), "End List not empty in destructor"); +} + +void HTMLEndPosLst::InsertNoScript( const SfxPoolItem& rItem, + sal_Int32 nStart, sal_Int32 nEnd, + SwHTMLFormatInfos& rFormatInfos, bool bParaAttrs ) +{ + // no range ?? in that case, don't take it, it will never take effect !! + if( nStart == nEnd ) + return; + + bool bSet = false, bSplit = false; + switch( GetHTMLItemState(rItem) ) + { + case HTML_ON_VALUE: + // output the attribute, if it isn't 'on', already + if( !ExistsOnTagItem( rItem.Which(), nStart ) ) + bSet = true; + break; + + case HTML_OFF_VALUE: + // If the corresponding attribute is 'on', split it. + // Additionally, output it as Style, if it is not set for the + // whole paragraph, because in that case it was already output + // together with the paragraph tag. + if( ExistsOnTagItem( rItem.Which(), nStart ) ) + bSplit = true; + bSet = m_bOutStyles && !bParaAttrs && !ExistsOffTagItem(rItem.Which(), nStart, nEnd); + break; + + case HTML_REAL_VALUE: + // we can always output the attribute + bSet = true; + break; + + case HTML_STYLE_VALUE: + // We can only output the attribute as CSS1. If it is set for + // the paragraph, it was already output with the paragraph tag. + // The only exception is the character-background attribute. This + // attribute must always be handled like a Hint. + bSet = m_bOutStyles + && (!bParaAttrs || rItem.Which() == RES_CHRATR_BACKGROUND + || rItem.Which() == RES_CHRATR_BOX || rItem.Which() == RES_CHRATR_OVERLINE); + break; + + case HTML_CHRFMT_VALUE: + { + OSL_ENSURE( RES_TXTATR_CHARFMT == rItem.Which(), + "Not a character style after all" ); + const SwFormatCharFormat& rChrFormat = rItem.StaticWhichCast(RES_TXTATR_CHARFMT); + const SwCharFormat* pFormat = rChrFormat.GetCharFormat(); + + const SwHTMLFormatInfo *pFormatInfo = GetFormatInfo( *pFormat, rFormatInfos ); + if( !pFormatInfo->aToken.isEmpty() ) + { + // output the character style tag before the hard + // attributes + InsertItem( rItem, nStart, nEnd ); + } + if( pFormatInfo->moItemSet ) + { + Insert( *pFormatInfo->moItemSet, nStart, nEnd, + rFormatInfos, true, bParaAttrs ); + } + } + break; + + case HTML_AUTOFMT_VALUE: + { + OSL_ENSURE( RES_TXTATR_AUTOFMT == rItem.Which(), + "Not an automatic style, after all" ); + const SwFormatAutoFormat& rAutoFormat = rItem.StaticWhichCast(RES_TXTATR_AUTOFMT); + const std::shared_ptr<SfxItemSet>& pSet = rAutoFormat.GetStyleHandle(); + if( pSet ) + Insert( *pSet, nStart, nEnd, rFormatInfos, true, bParaAttrs ); + } + break; + + case HTML_COLOR_VALUE: + // A foreground color as a paragraph attribute is only exported if + // it is not the same as the default color. + { + OSL_ENSURE( RES_CHRATR_COLOR == rItem.Which(), + "Not a foreground color, after all" ); + Color aColor( rItem.StaticWhichCast(RES_CHRATR_COLOR).GetValue() ); + if( COL_AUTO == aColor ) + aColor = COL_BLACK; + bSet = !bParaAttrs || !m_xDefaultColor || !m_xDefaultColor->IsRGBEqual(aColor); + } + break; + + case HTML_DROPCAP_VALUE: + { + OSL_ENSURE( RES_PARATR_DROP == rItem.Which(), + "Not a drop cap, after all" ); + const SwFormatDrop& rDrop = rItem.StaticWhichCast(RES_PARATR_DROP); + nEnd = nStart + rDrop.GetChars(); + if (!m_bOutStyles) + { + // At least use the attributes of the character style + const SwCharFormat *pCharFormat = rDrop.GetCharFormat(); + if( pCharFormat ) + { + Insert( pCharFormat->GetAttrSet(), nStart, nEnd, + rFormatInfos, true, bParaAttrs ); + } + } + else + { + bSet = true; + } + } + break; + default: + ; + } + + if( bSet ) + InsertItem( rItem, nStart, nEnd ); + if( bSplit ) + SplitItem( rItem, nStart, nEnd ); +} + +void HTMLEndPosLst::Insert( const SfxPoolItem& rItem, + sal_Int32 nStart, sal_Int32 nEnd, + SwHTMLFormatInfos& rFormatInfos, bool bParaAttrs ) +{ + bool bDependsOnScript = false, bDependsOnAnyScript = false; + sal_uInt16 nScript = i18n::ScriptType::LATIN; + switch( rItem.Which() ) + { + case RES_CHRATR_FONT: + case RES_CHRATR_FONTSIZE: + case RES_CHRATR_LANGUAGE: + case RES_CHRATR_POSTURE: + case RES_CHRATR_WEIGHT: + bDependsOnScript = true; + nScript = i18n::ScriptType::LATIN; + break; + + case RES_CHRATR_CJK_FONT: + case RES_CHRATR_CJK_FONTSIZE: + case RES_CHRATR_CJK_LANGUAGE: + case RES_CHRATR_CJK_POSTURE: + case RES_CHRATR_CJK_WEIGHT: + bDependsOnScript = true; + nScript = i18n::ScriptType::ASIAN; + break; + + case RES_CHRATR_CTL_FONT: + case RES_CHRATR_CTL_FONTSIZE: + case RES_CHRATR_CTL_LANGUAGE: + case RES_CHRATR_CTL_POSTURE: + case RES_CHRATR_CTL_WEIGHT: + bDependsOnScript = true; + nScript = i18n::ScriptType::COMPLEX; + break; + case RES_TXTATR_CHARFMT: + { + const SwFormatCharFormat& rChrFormat = rItem.StaticWhichCast(RES_TXTATR_CHARFMT); + const SwCharFormat* pFormat = rChrFormat.GetCharFormat(); + const SwHTMLFormatInfo *pFormatInfo = GetFormatInfo( *pFormat, rFormatInfos ); + if( pFormatInfo->bScriptDependent ) + { + bDependsOnScript = true; + bDependsOnAnyScript = true; + } + } + break; + case RES_TXTATR_INETFMT: + { + if (GetFormatInfo(*m_pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool( + RES_POOLCHR_INET_NORMAL), + rFormatInfos) + ->bScriptDependent + || GetFormatInfo(*m_pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool( + RES_POOLCHR_INET_VISIT), + rFormatInfos) + ->bScriptDependent) + { + bDependsOnScript = true; + bDependsOnAnyScript = true; + } + } + break; + } + + if( bDependsOnScript ) + { + sal_Int32 nPos = nStart; + for (size_t i = 0; i < m_aScriptChgLst.size(); i++) + { + sal_Int32 nChgPos = m_aScriptChgLst[i]; + if( nPos >= nChgPos ) + { + // the hint starts behind or at the next script change, + // so we may continue with this position. + continue; + } + if( nEnd <= nChgPos ) + { + // the (rest of) the hint ends before or at the next script + // change, so we can insert it, but only if it belongs + // to the current script. + if (bDependsOnAnyScript || nScript == m_aScriptLst[i]) + InsertNoScript( rItem, nPos, nEnd, rFormatInfos, + bParaAttrs ); + break; + } + + // the hint starts before the next script change and ends behind + // it, so we can insert a hint up to the next script change and + // continue with the rest of the hint. + if (bDependsOnAnyScript || nScript == m_aScriptLst[i]) + InsertNoScript( rItem, nPos, nChgPos, rFormatInfos, bParaAttrs ); + nPos = nChgPos; + } + } + else + { + InsertNoScript( rItem, nStart, nEnd, rFormatInfos, bParaAttrs ); + } +} + +void HTMLEndPosLst::Insert( const SfxItemSet& rItemSet, + sal_Int32 nStart, sal_Int32 nEnd, + SwHTMLFormatInfos& rFormatInfos, + bool bDeep, bool bParaAttrs ) +{ + SfxWhichIter aIter( rItemSet ); + + sal_uInt16 nWhich = aIter.FirstWhich(); + while( nWhich ) + { + const SfxPoolItem *pItem; + if( SfxItemState::SET == aIter.GetItemState( bDeep, &pItem ) ) + { + Insert( *pItem, nStart, nEnd, rFormatInfos, bParaAttrs ); + } + + nWhich = aIter.NextWhich(); + } +} + +void HTMLEndPosLst::Insert( const SwDrawFrameFormat& rFormat, sal_Int32 nPos, + SwHTMLFormatInfos& rFormatInfos ) +{ + const SdrObject* pTextObj = SwHTMLWriter::GetMarqueeTextObj( rFormat ); + + if( !pTextObj ) + return; + + // get the edit engine attributes of the object as SW attributes and + // insert them as hints. Because of the amount of Hints the styles + // are not considered! + const SfxItemSet& rFormatItemSet = rFormat.GetAttrSet(); + SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END> aItemSet( *rFormatItemSet.GetPool() ); + SwHTMLWriter::GetEEAttrsFromDrwObj( aItemSet, pTextObj ); + bool bOutStylesOld = m_bOutStyles; + m_bOutStyles = false; + Insert( aItemSet, nPos, nPos+1, rFormatInfos, false ); + m_bOutStyles = bOutStylesOld; +} + +sal_uInt16 HTMLEndPosLst::GetScriptAtPos( sal_Int32 nPos, sal_uInt16 nWeak ) +{ + sal_uInt16 nRet = CSS1_OUTMODE_ANY_SCRIPT; + + size_t nScriptChgs = m_aScriptChgLst.size(); + size_t i=0; + while (i < nScriptChgs && nPos >= m_aScriptChgLst[i]) + i++; + OSL_ENSURE( i < nScriptChgs, "script list is too short" ); + if( i < nScriptChgs ) + { + if (i18n::ScriptType::WEAK == m_aScriptLst[i]) + nRet = nWeak; + else + nRet = SwHTMLWriter::GetCSS1ScriptForScriptType(m_aScriptLst[i]); + } + + return nRet; +} + +void HTMLEndPosLst::OutStartAttrs( SwHTMLWriter& rWrt, sal_Int32 nPos ) +{ + rWrt.m_bTagOn = true; + + // Character border attribute must be the first which is written out + // because of border merge. + HTMLStartEndPositions::size_type nCharBoxIndex = 0; + while (nCharBoxIndex < m_aStartLst.size() + && m_aStartLst[nCharBoxIndex]->GetItem()->Which() != RES_CHRATR_BOX) + { + ++nCharBoxIndex; + } + + // the attributes of the start list are sorted in ascending order + for (HTMLStartEndPositions::size_type i = 0; i < m_aStartLst.size(); ++i) + { + HTMLStartEndPos *pPos = nullptr; + if (nCharBoxIndex < m_aStartLst.size()) + { + if( i == 0 ) + pPos = m_aStartLst[nCharBoxIndex]; + else if( i == nCharBoxIndex ) + pPos = m_aStartLst[0]; + else + pPos = m_aStartLst[i]; + } + else + pPos = m_aStartLst[i]; + + sal_Int32 nStart = pPos->GetStart(); + if( nStart > nPos ) + { + // this attribute, and all that follow, will be opened later on + break; + } + else if( nStart == nPos ) + { + // output the attribute + sal_uInt16 nCSS1Script = rWrt.m_nCSS1Script; + sal_uInt16 nWhich = pPos->GetItem()->Which(); + if( RES_TXTATR_CHARFMT == nWhich || + RES_TXTATR_INETFMT == nWhich || + RES_PARATR_DROP == nWhich ) + { + rWrt.m_nCSS1Script = GetScriptAtPos( nPos, nCSS1Script ); + } + HTMLOutFuncs::FlushToAscii( rWrt.Strm() ); // was one time only - do we still need it? + Out( aHTMLAttrFnTab, *pPos->GetItem(), rWrt ); + rWrt.maStartedAttributes[pPos->GetItem()->Which()]++; + rWrt.m_nCSS1Script = nCSS1Script; + } + } +} + +void HTMLEndPosLst::OutEndAttrs( SwHTMLWriter& rWrt, sal_Int32 nPos ) +{ + rWrt.m_bTagOn = false; + + // the attributes in the End list are sorted in ascending order + HTMLStartEndPositions::size_type i {0}; + while (i < m_aEndLst.size()) + { + HTMLStartEndPos* pPos = m_aEndLst[i]; + sal_Int32 nEnd = pPos->GetEnd(); + + if( SAL_MAX_INT32 == nPos || nEnd == nPos ) + { + HTMLOutFuncs::FlushToAscii( rWrt.Strm() ); // was one time only - do we still need it? + // Skip closing span if next character span has the same border (border merge) + bool bSkipOut = false; + if( pPos->GetItem()->Which() == RES_CHRATR_BOX ) + { + HTMLStartEndPositions::iterator it + = std::find(m_aStartLst.begin(), m_aStartLst.end(), pPos); + OSL_ENSURE(it != m_aStartLst.end(), "Item not found in Start List!"); + if (it != m_aStartLst.end()) + ++it; + while (it != m_aStartLst.end()) + { + HTMLStartEndPos *pEndPos = *it; + if( pEndPos->GetItem()->Which() == RES_CHRATR_BOX && + *static_cast<const SvxBoxItem*>(pEndPos->GetItem()) == + *static_cast<const SvxBoxItem*>(pPos->GetItem()) ) + { + pEndPos->SetStart(pPos->GetStart()); + bSkipOut = true; + break; + } + ++it; + } + } + if( !bSkipOut ) + { + Out( aHTMLAttrFnTab, *pPos->GetItem(), rWrt ); + rWrt.maStartedAttributes[pPos->GetItem()->Which()]--; + } + RemoveItem_( i ); + } + else if( nEnd > nPos ) + { + // this attribute, and all that follow, are closed later on + break; + } + else + { + // The attribute is closed before the current position. This + // is not allowed, but we can handle it anyway. + OSL_ENSURE( nEnd >= nPos, + "The attribute should've been closed a long time ago" ); + i++; + } + } +} + +static constexpr bool IsLF(sal_Unicode ch) { return ch == '\n'; } + +static constexpr bool IsWhitespaceExcludingLF(sal_Unicode ch) +{ + return ch == ' ' || ch == '\t' || ch == '\r'; +} + +static constexpr bool IsWhitespaceIncludingLF(sal_Unicode ch) +{ + return IsWhitespaceExcludingLF(ch) || IsLF(ch); +} + +static bool NeedPreserveWhitespace(std::u16string_view str, bool xml) +{ + if (str.empty()) + return false; + // leading / trailing spaces + // A leading / trailing \n would turn into a leading / trailing <br/>, + // and will not disappear, even without space preserving option + if (IsWhitespaceExcludingLF(str.front()) || IsWhitespaceExcludingLF(str.back())) + return true; + for (size_t i = 0; i < str.size(); ++i) + { + if (xml) + { + // No need to consider \n, which convert to <br/>, when it's after a space + // (but handle it *before* a space) + if (IsWhitespaceIncludingLF(str[i])) + { + do + { + ++i; + if (i == str.size()) + return false; + } while (IsLF(str[i])); + if (IsWhitespaceExcludingLF(str[i])) + return true; // Second whitespace in a row + } + } + else // html + { + // Only consider \n, when an adjacent space is not \n - which would be eaten + // without a space preserving option + if (IsWhitespaceExcludingLF(str[i])) + { + ++i; + if (i == str.size()) + return false; + if (IsWhitespaceIncludingLF(str[i])) + return true; // Any whitespace after a non-LF whitespace + } + else if (IsLF(str[i])) + { + do + { + ++i; + if (i == str.size()) + return false; + } + while (IsLF(str[i])); + if (IsWhitespaceExcludingLF(str[i])) + return true; // A non-LF whitespace after a LF + } + } + } + return false; +} + +/* Output of the nodes*/ +SwHTMLWriter& OutHTML_SwTextNode( SwHTMLWriter& rWrt, const SwContentNode& rNode ) +{ + const SwTextNode * pNd = &static_cast<const SwTextNode&>(rNode); + + const OUString& rStr = pNd->GetText(); + sal_Int32 nEnd = rStr.getLength(); + + // special case: empty node and HR style (horizontal rule) + // output a <HR>, only + sal_uInt16 nPoolId = pNd->GetAnyFormatColl().GetPoolFormatId(); + + // Handle horizontal rule <hr> + if (!nEnd && + (RES_POOLCOLL_HTML_HR==nPoolId || pNd->GetAnyFormatColl().GetName() == OOO_STRING_SVTOOLS_HTML_horzrule)) + { + // then, the paragraph-anchored graphics/OLE objects in the paragraph + // MIB 8.7.97: We enclose the line in a <PRE>. This means that the + // spacings are wrong, but otherwise we get an empty paragraph + // after the <HR> which is even uglier. + rWrt.ChangeParaToken( HtmlTokenId::NONE ); + + // Output all the nodes that are anchored to a frame + rWrt.OutFlyFrame( rNode.GetIndex(), 0, HtmlPosition::Any ); + + if (rWrt.IsLFPossible()) + rWrt.OutNewLine(); // paragraph tag on a new line + + rWrt.SetLFPossible(true); + + HtmlWriter aHtml(rWrt.Strm(), rWrt.maNamespace); + aHtml.prettyPrint(rWrt.IsPrettyPrint()); + aHtml.start(OOO_STRING_SVTOOLS_HTML_horzrule ""_ostr); + + const SfxItemSet* pItemSet = pNd->GetpSwAttrSet(); + if( !pItemSet ) + { + aHtml.end(); + return rWrt; + } + if (pItemSet->GetItemIfSet(RES_MARGIN_FIRSTLINE, false) + || pItemSet->GetItemIfSet(RES_MARGIN_TEXTLEFT, false) + || pItemSet->GetItemIfSet(RES_MARGIN_RIGHT, false)) + { + SvxFirstLineIndentItem const& rFirstLine(pItemSet->Get(RES_MARGIN_FIRSTLINE)); + SvxTextLeftMarginItem const& rTextLeftMargin(pItemSet->Get(RES_MARGIN_TEXTLEFT)); + SvxRightMarginItem const& rRightMargin(pItemSet->Get(RES_MARGIN_RIGHT)); + sal_Int32 const nLeft(rTextLeftMargin.GetLeft(rFirstLine)); + sal_Int32 const nRight(rRightMargin.GetRight()); + if( nLeft || nRight ) + { + const SwFrameFormat& rPgFormat = + rWrt.m_pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool + ( RES_POOLPAGE_HTML, false )->GetMaster(); + const SwFormatFrameSize& rSz = rPgFormat.GetFrameSize(); + const SvxLRSpaceItem& rLR = rPgFormat.GetLRSpace(); + const SwFormatCol& rCol = rPgFormat.GetCol(); + + tools::Long nPageWidth = rSz.GetWidth() - rLR.GetLeft() - rLR.GetRight(); + + if( 1 < rCol.GetNumCols() ) + nPageWidth /= rCol.GetNumCols(); + + const SwTableNode* pTableNd = pNd->FindTableNode(); + if( pTableNd ) + { + const SwTableBox* pBox = pTableNd->GetTable().GetTableBox( + pNd->StartOfSectionIndex() ); + if( pBox ) + nPageWidth = pBox->GetFrameFormat()->GetFrameSize().GetWidth(); + } + + OString sWidth = OString::number(SwHTMLWriter::ToPixel(nPageWidth - nLeft - nRight)); + aHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_width, sWidth); + + if( !nLeft ) + aHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_align, OOO_STRING_SVTOOLS_HTML_AL_left); + else if( !nRight ) + aHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_align, OOO_STRING_SVTOOLS_HTML_AL_right); + else + aHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_align, OOO_STRING_SVTOOLS_HTML_AL_center); + } + } + + if( const SvxBoxItem* pBoxItem = pItemSet->GetItemIfSet( RES_BOX, false )) + { + const editeng::SvxBorderLine* pBorderLine = pBoxItem->GetBottom(); + if( pBorderLine ) + { + sal_uInt16 nWidth = pBorderLine->GetScaledWidth(); + OString sWidth = OString::number(SwHTMLWriter::ToPixel(nWidth)); + aHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_size, sWidth); + + const Color& rBorderColor = pBorderLine->GetColor(); + if( !rBorderColor.IsRGBEqual( COL_GRAY ) ) + { + HtmlWriterHelper::applyColor(aHtml, OOO_STRING_SVTOOLS_HTML_O_color, rBorderColor); + } + + if( !pBorderLine->GetInWidth() ) + { + aHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_noshade, OOO_STRING_SVTOOLS_HTML_O_noshade); + } + } + } + aHtml.end(); + return rWrt; + } + + // Do not export the empty nodes with 2pt fonts and standard style that + // are inserted before tables and sections, but do export bookmarks + // and paragraph anchored frames. + if( !nEnd && (nPoolId == RES_POOLCOLL_STANDARD || + nPoolId == RES_POOLCOLL_TABLE || + nPoolId == RES_POOLCOLL_TABLE_HDLN) ) + { + // The current node is empty and contains the standard style ... + const SvxFontHeightItem* pFontHeightItem; + const SfxItemSet* pItemSet = pNd->GetpSwAttrSet(); + if( pItemSet && pItemSet->Count() && + (pFontHeightItem = pItemSet->GetItemIfSet( RES_CHRATR_FONTSIZE, false )) && + 40 == pFontHeightItem->GetHeight() ) + { + // ... moreover, the 2pt font is set ... + SwNodeOffset nNdPos = rWrt.m_pCurrentPam->GetPoint()->GetNodeIndex(); + const SwNode *pNextNd = rWrt.m_pDoc->GetNodes()[nNdPos+1]; + const SwNode *pPrevNd = rWrt.m_pDoc->GetNodes()[nNdPos-1]; + bool bStdColl = nPoolId == RES_POOLCOLL_STANDARD; + if( ( bStdColl && (pNextNd->IsTableNode() || pNextNd->IsSectionNode()) ) || + ( !bStdColl && + pNextNd->IsEndNode() && + pPrevNd->IsStartNode() && + SwTableBoxStartNode == pPrevNd->GetStartNode()->GetStartNodeType() ) ) + { + // ... and it is located before a table or a section + rWrt.OutBookmarks(); + rWrt.SetLFPossible(rWrt.m_nLastParaToken == HtmlTokenId::NONE); + + // Output all frames that are anchored to this node + rWrt.OutFlyFrame( rNode.GetIndex(), 0, HtmlPosition::Any ); + rWrt.SetLFPossible(false); + + return rWrt; + } + } + } + + // catch PageBreaks and PageDescs + bool bPageBreakBehind = false; + if( rWrt.m_bCfgFormFeed && + !(rWrt.m_bOutTable || rWrt.m_bOutFlyFrame) && + rWrt.m_pStartNdIdx->GetIndex() != rWrt.m_pCurrentPam->GetPoint()->GetNodeIndex() ) + { + bool bPageBreakBefore = false; + const SfxItemSet* pItemSet = pNd->GetpSwAttrSet(); + + if( pItemSet ) + { + const SwFormatPageDesc* pPageDescItem = pItemSet->GetItemIfSet( RES_PAGEDESC ); + if( pPageDescItem && pPageDescItem->GetPageDesc() ) + { + bPageBreakBefore = true; + } + else if( const SvxFormatBreakItem* pItem = pItemSet->GetItemIfSet( RES_BREAK ) ) + { + switch( pItem->GetBreak() ) + { + case SvxBreak::PageBefore: + bPageBreakBefore = true; + break; + case SvxBreak::PageAfter: + bPageBreakBehind = true; + break; + case SvxBreak::PageBoth: + bPageBreakBefore = true; + bPageBreakBehind = true; + break; + default: + break; + } + } + } + + if( bPageBreakBefore ) + rWrt.Strm().WriteChar( '\f' ); + } + + // if necessary, open a form + rWrt.OutForm(); + + // Output the page-anchored frames that are 'anchored' to this node + bool bFlysLeft = rWrt.OutFlyFrame( rNode.GetIndex(), 0, HtmlPosition::Prefix ); + + // Output all frames that are anchored to this node that are supposed to + // be written before the paragraph tag. + if( bFlysLeft ) + { + bFlysLeft = rWrt.OutFlyFrame( rNode.GetIndex(), 0, HtmlPosition::Before ); + } + + if( rWrt.m_pCurrentPam->GetPoint()->GetNode() == rWrt.m_pCurrentPam->GetMark()->GetNode() ) + { + nEnd = rWrt.m_pCurrentPam->GetMark()->GetContentIndex(); + } + + // are there any hard attributes that must be written as options? + rWrt.m_bTagOn = true; + + // now, output the tag of the paragraph + const SwFormat& rFormat = pNd->GetAnyFormatColl(); + SwHTMLTextCollOutputInfo aFormatInfo; + bool bOldLFPossible = rWrt.IsLFPossible(); + bool bOldSpacePreserve = rWrt.IsSpacePreserve(); + if (rWrt.IsPreserveSpacesOnWritePrefSet()) + rWrt.SetSpacePreserve(NeedPreserveWhitespace(rStr, rWrt.mbReqIF)); + OutHTML_SwFormat( rWrt, rFormat, pNd->GetpSwAttrSet(), aFormatInfo ); + + // If we didn't open a new line before the paragraph tag, we do that now + rWrt.SetLFPossible(rWrt.m_nLastParaToken == HtmlTokenId::NONE); + if (!bOldLFPossible && rWrt.IsLFPossible()) + rWrt.OutNewLine(); + + // then, the bookmarks (including end tag) + rWrt.m_bOutOpts = false; + rWrt.OutBookmarks(); + + // now it's a good opportunity again for an LF - if it is still allowed + // FIXME: for LOK case we set rWrt.m_nWishLineLen as -1, for now keep old flow + // when LOK side will be fixed - don't insert new line at the beginning + if( rWrt.IsLFPossible() && rWrt.IsPrettyPrint() && rWrt.m_nWishLineLen >= 0 && + rWrt.GetLineLen() >= rWrt.m_nWishLineLen ) + { + rWrt.OutNewLine(); + } + rWrt.SetLFPossible(false); + + // find text that originates from an outline numbering + sal_Int32 nOffset = 0; + OUString aOutlineText; + OUString aFullText; + + // export numbering string as plain text only for the outline numbering, + // because the outline numbering isn't exported as a numbering - see <SwHTMLNumRuleInfo::Set(..)> + if ( pNd->IsOutline() && + pNd->GetNumRule() == pNd->GetDoc().GetOutlineNumRule() ) + { + aOutlineText = pNd->GetNumString(); + nOffset = nOffset + aOutlineText.getLength(); + aFullText = aOutlineText; + } + OUString aFootEndNoteSym; + if( rWrt.m_pFormatFootnote ) + { + aFootEndNoteSym = rWrt.GetFootEndNoteSym( *rWrt.m_pFormatFootnote ); + nOffset = nOffset + aFootEndNoteSym.getLength(); + aFullText += aFootEndNoteSym; + } + + // Table of Contents or other paragraph with dot leaders? + sal_Int32 nIndexTab = rWrt.indexOfDotLeaders( nPoolId, rStr ); + if (nIndexTab > -1) + // skip part after the tabulator (page number) + nEnd = nIndexTab; + + // are there any hard attributes that must be written as tags? + aFullText += rStr; + HTMLEndPosLst aEndPosLst( rWrt.m_pDoc, rWrt.m_xTemplate.get(), + rWrt.m_xDfltColor, rWrt.m_bCfgOutStyles, + rWrt.GetHTMLMode(), aFullText, + rWrt.m_aScriptTextStyles ); + if( aFormatInfo.moItemSet ) + { + aEndPosLst.Insert( *aFormatInfo.moItemSet, 0, nEnd + nOffset, + rWrt.m_CharFormatInfos, false, true ); + } + + if( !aOutlineText.isEmpty() || rWrt.m_pFormatFootnote ) + { + // output paragraph attributes, so that the text gets the attributes of + // the paragraph. + aEndPosLst.OutStartAttrs( rWrt, 0 ); + + // Theoretically, we would have to consider the character style of + // the numbering. Because it cannot be set via the UI, let's ignore + // it for now. + + if( !aOutlineText.isEmpty() ) + HTMLOutFuncs::Out_String( rWrt.Strm(), aOutlineText ); + + if( rWrt.m_pFormatFootnote ) + { + rWrt.OutFootEndNoteSym( *rWrt.m_pFormatFootnote, aFootEndNoteSym, + aEndPosLst.GetScriptAtPos( aOutlineText.getLength(), rWrt.m_nCSS1Script ) ); + rWrt.m_pFormatFootnote = nullptr; + } + } + + // for now, correct the start. I.e., if we only output part of the sentence, + // the attributes must be correct there, as well!! + rWrt.m_bTextAttr = true; + + size_t nAttrPos = 0; + sal_Int32 nStrPos = rWrt.m_pCurrentPam->GetPoint()->GetContentIndex(); + const SwTextAttr * pHt = nullptr; + const size_t nCntAttr = pNd->HasHints() ? pNd->GetSwpHints().Count() : 0; + if( nCntAttr && nStrPos > ( pHt = pNd->GetSwpHints().Get(0) )->GetStart() ) + { + // Ok, there are earlier attributes that we must output + do { + aEndPosLst.OutEndAttrs( rWrt, nStrPos + nOffset ); + + nAttrPos++; + if( pHt->Which() == RES_TXTATR_FIELD + || pHt->Which() == RES_TXTATR_ANNOTATION ) + continue; + + if ( pHt->End() && !pHt->HasDummyChar() ) + { + const sal_Int32 nHtEnd = *pHt->End(), + nHtStt = pHt->GetStart(); + if( !rWrt.m_bWriteAll && nHtEnd <= nStrPos ) + continue; + + // don't consider empty hints at the beginning - or should we ?? + if( nHtEnd == nHtStt ) + continue; + + // add attribute to the list + if( rWrt.m_bWriteAll ) + aEndPosLst.Insert( pHt->GetAttr(), nHtStt + nOffset, + nHtEnd + nOffset, + rWrt.m_CharFormatInfos ); + else + { + sal_Int32 nTmpStt = nHtStt < nStrPos ? nStrPos : nHtStt; + sal_Int32 nTmpEnd = std::min(nHtEnd, nEnd); + aEndPosLst.Insert( pHt->GetAttr(), nTmpStt + nOffset, + nTmpEnd + nOffset, + rWrt.m_CharFormatInfos ); + } + continue; + // but don't output it, that will be done later !! + } + + } while( nAttrPos < nCntAttr && nStrPos > + ( pHt = pNd->GetSwpHints().Get( nAttrPos ) )->GetStart() ); + + // so, let's output all collected attributes from the string pos on + aEndPosLst.OutEndAttrs( rWrt, nStrPos + nOffset ); + aEndPosLst.OutStartAttrs( rWrt, nStrPos + nOffset ); + } + + bool bWriteBreak = (HtmlTokenId::PREFORMTXT_ON != rWrt.m_nLastParaToken); + if (bWriteBreak && (pNd->GetNumRule() || rWrt.mbReqIF)) + { + // One line-break is exactly one <br> in the ReqIF case. + bWriteBreak = false; + } + + { + // Tabs are leading till there is a non-tab since the start of the paragraph. + bool bLeadingTab = true; + for( ; nStrPos < nEnd; nStrPos++ ) + { + // output the frames that are anchored to the current position + if( bFlysLeft ) + { + aEndPosLst.OutEndAttrs( rWrt, nStrPos + nOffset ); + bFlysLeft = rWrt.OutFlyFrame( rNode.GetIndex(), + nStrPos, HtmlPosition::Inside ); + } + + bool bOutChar = true; + const SwTextAttr * pTextHt = nullptr; + if (nAttrPos < nCntAttr && pHt->GetStart() == nStrPos) + { + do { + if ( pHt->End() && !pHt->HasDummyChar() ) + { + if( *pHt->End() != nStrPos ) + { + // insert hints with end, if they don't start + // an empty range (hints that don't start a range + // are ignored) + aEndPosLst.Insert( pHt->GetAttr(), nStrPos + nOffset, + *pHt->End() + nOffset, + rWrt.m_CharFormatInfos ); + } + } + else + { + // hints without an end are output last + OSL_ENSURE( !pTextHt, "Why is there already an attribute without an end?" ); + if( rWrt.m_nTextAttrsToIgnore>0 ) + { + rWrt.m_nTextAttrsToIgnore--; + } + else + { + pTextHt = pHt; + SwFieldIds nFieldWhich; + if( RES_TXTATR_FIELD != pHt->Which() + || ( SwFieldIds::Postit != (nFieldWhich = static_cast<const SwFormatField&>(pHt->GetAttr()).GetField()->Which()) + && SwFieldIds::Script != nFieldWhich ) ) + { + bWriteBreak = false; + } + } + bOutChar = false; // don't output 255 + } + } while( ++nAttrPos < nCntAttr && nStrPos == + ( pHt = pNd->GetSwpHints().Get( nAttrPos ) )->GetStart() ); + } + + // Additionally, some draw formats can bring attributes + if( pTextHt && RES_TXTATR_FLYCNT == pTextHt->Which() ) + { + const SwFrameFormat* pFrameFormat = + pTextHt->GetAttr().StaticWhichCast(RES_TXTATR_FLYCNT).GetFrameFormat(); + + if( RES_DRAWFRMFMT == pFrameFormat->Which() ) + aEndPosLst.Insert( *static_cast<const SwDrawFrameFormat *>(pFrameFormat), + nStrPos + nOffset, + rWrt.m_CharFormatInfos ); + } + + aEndPosLst.OutEndAttrs( rWrt, nStrPos + nOffset ); + aEndPosLst.OutStartAttrs( rWrt, nStrPos + nOffset ); + + if( pTextHt ) + { + rWrt.SetLFPossible(rWrt.m_nLastParaToken == HtmlTokenId::NONE && + nStrPos > 0 && + rStr[nStrPos-1] == ' '); + sal_uInt16 nCSS1Script = rWrt.m_nCSS1Script; + rWrt.m_nCSS1Script = aEndPosLst.GetScriptAtPos( + nStrPos + nOffset, nCSS1Script ); + HTMLOutFuncs::FlushToAscii( rWrt.Strm() ); + Out( aHTMLAttrFnTab, pTextHt->GetAttr(), rWrt ); + rWrt.m_nCSS1Script = nCSS1Script; + rWrt.SetLFPossible(false); + } + + if( bOutChar ) + { + sal_uInt32 c = rStr[nStrPos]; + if( rtl::isHighSurrogate(c) && nStrPos < nEnd - 1 ) + { + const sal_Unicode d = rStr[nStrPos + 1]; + if( rtl::isLowSurrogate(d) ) + { + c = rtl::combineSurrogates(c, d); + nStrPos++; + } + } + + // try to split a line after about 255 characters + // at a space character unless in a PRE-context + if( ' ' == c && rWrt.m_nLastParaToken == HtmlTokenId::NONE && !rWrt.IsSpacePreserve() ) + { + sal_Int32 nLineLen; + nLineLen = rWrt.GetLineLen(); + + sal_Int32 nWordLen = rStr.indexOf( ' ', nStrPos+1 ); + if( nWordLen == -1 ) + nWordLen = nEnd; + nWordLen -= nStrPos; + + if( rWrt.IsPrettyPrint() && rWrt.m_nWishLineLen >= 0 && + (nLineLen >= rWrt.m_nWishLineLen || + (nLineLen+nWordLen) >= rWrt.m_nWishLineLen ) ) + { + HTMLOutFuncs::FlushToAscii( rWrt.Strm() ); + rWrt.OutNewLine(); + bOutChar = false; + } + } + + if( bOutChar ) + { + if( 0x0a == c ) + { + HTMLOutFuncs::FlushToAscii( rWrt.Strm() ); + HtmlWriter aHtml(rWrt.Strm(), rWrt.maNamespace); + aHtml.prettyPrint(rWrt.IsPrettyPrint()); + aHtml.single(OOO_STRING_SVTOOLS_HTML_linebreak ""_ostr); + } + else if (c == CH_TXT_ATR_FORMELEMENT) + { + // Placeholder for a single-point fieldmark. + + SwPosition aMarkPos = *rWrt.m_pCurrentPam->GetPoint(); + aMarkPos.AdjustContent( nStrPos - aMarkPos.GetContentIndex() ); + rWrt.OutPointFieldmarks(aMarkPos); + } + else + { + bool bConsumed = false; + if (c == '\t') + { + if (bLeadingTab && rWrt.m_nLeadingTabWidth.has_value()) + { + // Consume a tab if it's leading and we know the number of NBSPs to + // be used as a replacement. + for (sal_Int32 i = 0; i < *rWrt.m_nLeadingTabWidth; ++i) + { + rWrt.Strm().WriteOString(" "); + } + bConsumed = true; + } + } + else + { + // Not a tab -> later tabs are no longer leading. + bLeadingTab = false; + } + + if (!bConsumed) + { + HTMLOutFuncs::Out_Char(rWrt.Strm(), c); + } + } + + if (!rWrt.mbReqIF) + { + // if a paragraph's last character is a hard line break + // then we need to add an extra <br> + // because browsers like Mozilla wouldn't add a line for the next paragraph + bWriteBreak = (0x0a == c) && + (HtmlTokenId::PREFORMTXT_ON != rWrt.m_nLastParaToken); + } + } + } + } + HTMLOutFuncs::FlushToAscii( rWrt.Strm() ); + } + + aEndPosLst.OutEndAttrs( rWrt, SAL_MAX_INT32 ); + + // Output the frames that are anchored to the last position + if( bFlysLeft ) + bFlysLeft = rWrt.OutFlyFrame( rNode.GetIndex(), + nEnd, HtmlPosition::Inside ); + OSL_ENSURE( !bFlysLeft, "Not all frames were saved!" ); + + rWrt.m_bTextAttr = false; + + if( bWriteBreak ) + { + bool bEndOfCell = rWrt.m_bOutTable && + rWrt.m_pCurrentPam->GetPoint()->GetNodeIndex() == + rWrt.m_pCurrentPam->GetMark()->GetNodeIndex(); + + if( bEndOfCell && !nEnd && + rWrt.IsHTMLMode(HTMLMODE_NBSP_IN_TABLES) ) + { + // If the last paragraph of a table cell is empty and we export + // for the MS-IE, we write a instead of a <BR> + rWrt.Strm().WriteChar( '&' ).WriteOString( OOO_STRING_SVTOOLS_HTML_S_nbsp ).WriteChar( ';' ); + } + else + { + HtmlWriter aHtml(rWrt.Strm(), rWrt.maNamespace); + aHtml.prettyPrint(rWrt.IsPrettyPrint()); + aHtml.single(OOO_STRING_SVTOOLS_HTML_linebreak ""_ostr); + const SvxULSpaceItem& rULSpace = pNd->GetSwAttrSet().Get(RES_UL_SPACE); + if (rULSpace.GetLower() > 0 && !bEndOfCell) + { + aHtml.single(OOO_STRING_SVTOOLS_HTML_linebreak ""_ostr); + } + rWrt.SetLFPossible(true); + } + } + + if( rWrt.m_bClearLeft || rWrt.m_bClearRight ) + { + const char* pString; + if( rWrt.m_bClearLeft ) + { + if( rWrt.m_bClearRight ) + pString = OOO_STRING_SVTOOLS_HTML_AL_all; + else + pString = OOO_STRING_SVTOOLS_HTML_AL_left; + } + else + { + pString = OOO_STRING_SVTOOLS_HTML_AL_right; + } + + HtmlWriter aHtml(rWrt.Strm(), rWrt.maNamespace); + aHtml.prettyPrint(rWrt.IsPrettyPrint()); + aHtml.start(OOO_STRING_SVTOOLS_HTML_linebreak ""_ostr); + aHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_clear, pString); + aHtml.end(); + + rWrt.m_bClearLeft = false; + rWrt.m_bClearRight = false; + + rWrt.SetLFPossible(true); + } + + // if an LF is not allowed already, it is allowed once the paragraphs + // ends with a ' ' + if (!rWrt.IsLFPossible() && + rWrt.m_nLastParaToken == HtmlTokenId::NONE && + nEnd > 0 && ' ' == rStr[nEnd-1] ) + rWrt.SetLFPossible(true); + + // dot leaders: print the skipped page number in a different span element + if (nIndexTab > -1) { + OString sOut = OUStringToOString(rStr.subView(nIndexTab + 1), RTL_TEXTENCODING_ASCII_US); + rWrt.Strm().WriteOString( Concat2View("</span><span>" + sOut + "</span>") ); + } + + rWrt.m_bTagOn = false; + OutHTML_SwFormatOff( rWrt, aFormatInfo ); + rWrt.SetSpacePreserve(bOldSpacePreserve); + + // if necessary, close a form + rWrt.OutForm( false ); + + if( bPageBreakBehind ) + rWrt.Strm().WriteChar( '\f' ); + + return rWrt; +} + +// In CSS, "px" is 1/96 of inch: https://www.w3.org/TR/css3-values/#absolute-lengths +sal_uInt32 SwHTMLWriter::ToPixel(sal_uInt32 nTwips) +{ + // if there is a Twip, there should be a pixel as well + return nTwips + ? std::max(o3tl::convert(nTwips, o3tl::Length::twip, o3tl::Length::px), sal_Int64(1)) + : 0; +} + +Size SwHTMLWriter::ToPixel(Size aTwips) +{ + return Size(ToPixel(aTwips.Width()), ToPixel(aTwips.Height())); +} + +static SwHTMLWriter& OutHTML_CSS1Attr( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + // if hints are currently written, we try to write the hint as an + // CSS1 attribute + + if( rWrt.m_bCfgOutStyles && rWrt.m_bTextAttr ) + OutCSS1_HintSpanTag( rWrt, rHt ); + + return rWrt; +} + +/* File CHRATR.HXX: */ + +static SwHTMLWriter& OutHTML_SvxColor( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + if( rWrt.m_bOutOpts ) + return rWrt; + + if( !rWrt.m_bTextAttr && rWrt.m_bCfgOutStyles && rWrt.m_bCfgPreferStyles ) + { + // don't write the font color as a tag, if styles are preferred to + // normal tags + return rWrt; + } + + if( rWrt.m_bTagOn ) + { + Color aColor( static_cast<const SvxColorItem&>(rHt).GetValue() ); + if( COL_AUTO == aColor ) + aColor = COL_BLACK; + + if (rWrt.mbXHTML) + { + OString sOut = "<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_span + " " OOO_STRING_SVTOOLS_HTML_O_style "="; + rWrt.Strm().WriteOString(sOut); + HTMLOutFuncs::Out_Color(rWrt.Strm(), aColor, /*bXHTML=*/true).WriteChar('>'); + } + else + { + OString sOut = "<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_font " " + OOO_STRING_SVTOOLS_HTML_O_color "="; + rWrt.Strm().WriteOString( sOut ); + HTMLOutFuncs::Out_Color( rWrt.Strm(), aColor ).WriteChar( '>' ); + } + } + else + { + if (rWrt.mbXHTML) + HTMLOutFuncs::Out_AsciiTag( + rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_span), false); + else + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_font), false ); + } + + return rWrt; +} + +static SwHTMLWriter& OutHTML_SwPosture( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + if( rWrt.m_bOutOpts ) + return rWrt; + + const FontItalic nPosture = static_cast<const SvxPostureItem&>(rHt).GetPosture(); + if( ITALIC_NORMAL == nPosture ) + { + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_italic), rWrt.m_bTagOn ); + } + else if( rWrt.m_bCfgOutStyles && rWrt.m_bTextAttr ) + { + // maybe as CSS1 attribute? + OutCSS1_HintSpanTag( rWrt, rHt ); + } + + return rWrt; +} + +static SwHTMLWriter& OutHTML_SvxFont( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + if( rWrt.m_bOutOpts ) + return rWrt; + + if (IgnorePropertyForReqIF(rWrt.mbReqIF, "font-family", "")) + { + return rWrt; + } + + if( rWrt.m_bTagOn ) + { + OUString aNames; + SwHTMLWriter::PrepareFontList( static_cast<const SvxFontItem&>(rHt), aNames, 0, + rWrt.IsHTMLMode(HTMLMODE_FONT_GENERIC) ); + if (rWrt.mbXHTML) + { + OString sOut = "<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_span + " " OOO_STRING_SVTOOLS_HTML_O_style "=\"font-family: "; + rWrt.Strm().WriteOString(sOut); + HTMLOutFuncs::Out_String(rWrt.Strm(), aNames) + .WriteOString("\">"); + } + else + { + OString sOut = "<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_font " " + OOO_STRING_SVTOOLS_HTML_O_face "=\""; + rWrt.Strm().WriteOString( sOut ); + HTMLOutFuncs::Out_String( rWrt.Strm(), aNames ) + .WriteOString( "\">" ); + } + } + else + { + if (rWrt.mbXHTML) + HTMLOutFuncs::Out_AsciiTag( + rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_span), false); + else + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_font), false ); + } + + return rWrt; +} + +static SwHTMLWriter& OutHTML_SvxFontHeight( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + if( rWrt.m_bOutOpts ) + return rWrt; + + if (IgnorePropertyForReqIF(rWrt.mbReqIF, "font-size", "")) + { + return rWrt; + } + + if( rWrt.m_bTagOn ) + { + if (rWrt.mbXHTML) + { + OString sOut = "<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_span; + + sal_uInt32 nHeight = static_cast<const SvxFontHeightItem&>(rHt).GetHeight(); + // Twips -> points. + sal_uInt16 nSize = nHeight / 20; + sOut += " " OOO_STRING_SVTOOLS_HTML_O_style "=\"font-size: " + + OString::number(static_cast<sal_Int32>(nSize)) + "pt\""; + rWrt.Strm().WriteOString(sOut); + } + else + { + OString sOut = "<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_font; + + sal_uInt32 nHeight = static_cast<const SvxFontHeightItem&>(rHt).GetHeight(); + sal_uInt16 nSize = rWrt.GetHTMLFontSize( nHeight ); + sOut += " " OOO_STRING_SVTOOLS_HTML_O_size "=\"" + + OString::number(static_cast<sal_Int32>(nSize)) + "\""; + rWrt.Strm().WriteOString( sOut ); + + if( rWrt.m_bCfgOutStyles && rWrt.m_bTextAttr ) + { + // always export font size as CSS option, too + OutCSS1_HintStyleOpt( rWrt, rHt ); + } + } + rWrt.Strm().WriteChar( '>' ); + } + else + { + if (rWrt.mbXHTML) + HTMLOutFuncs::Out_AsciiTag( + rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_span), false); + else + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_font), false ); + } + + return rWrt; +} + +static SwHTMLWriter& OutHTML_SvxLanguage( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + if( rWrt.m_bOutOpts ) + return rWrt; + + LanguageType eLang = static_cast<const SvxLanguageItem &>(rHt).GetLanguage(); + if( LANGUAGE_DONTKNOW == eLang ) + return rWrt; + + if( rWrt.m_bTagOn ) + { + OString sOut = "<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_span; + rWrt.Strm().WriteOString( sOut ); + rWrt.OutLanguage( static_cast<const SvxLanguageItem &>(rHt).GetLanguage() ); + rWrt.Strm().WriteChar( '>' ); + } + else + { + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_span), false ); + } + + return rWrt; +} +static SwHTMLWriter& OutHTML_SwWeight( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + if( rWrt.m_bOutOpts ) + return rWrt; + + const FontWeight nBold = static_cast<const SvxWeightItem&>(rHt).GetWeight(); + if( WEIGHT_BOLD == nBold ) + { + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_bold), rWrt.m_bTagOn ); + } + else if( rWrt.m_bCfgOutStyles && rWrt.m_bTextAttr ) + { + // maybe as CSS1 attribute ? + OutCSS1_HintSpanTag( rWrt, rHt ); + } + + return rWrt; +} + +static SwHTMLWriter& OutHTML_SwCrossedOut( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + if( rWrt.m_bOutOpts ) + return rWrt; + + // Because of Netscape, we output STRIKE and not S! + const FontStrikeout nStrike = static_cast<const SvxCrossedOutItem&>(rHt).GetStrikeout(); + if( STRIKEOUT_NONE != nStrike && !rWrt.mbReqIF ) + { + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_strike), rWrt.m_bTagOn ); + } + else if( rWrt.m_bCfgOutStyles && rWrt.m_bTextAttr ) + { + // maybe as CSS1 attribute? + OutCSS1_HintSpanTag( rWrt, rHt ); + } + + return rWrt; +} + +static SwHTMLWriter& OutHTML_SvxEscapement( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + if( rWrt.m_bOutOpts ) + return rWrt; + + const SvxEscapement eEscape = + static_cast<SvxEscapement>(static_cast<const SvxEscapementItem&>(rHt).GetEnumValue()); + OString aTag; + switch( eEscape ) + { + case SvxEscapement::Superscript: aTag = OOO_STRING_SVTOOLS_HTML_superscript ""_ostr; break; + case SvxEscapement::Subscript: aTag = OOO_STRING_SVTOOLS_HTML_subscript ""_ostr; break; + default: + ; + } + + if( !aTag.isEmpty() ) + { + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + aTag), rWrt.m_bTagOn ); + } + else if( rWrt.m_bCfgOutStyles && rWrt.m_bTextAttr ) + { + // maybe as CSS1 attribute? + OutCSS1_HintSpanTag( rWrt, rHt ); + } + + return rWrt; +} + +static SwHTMLWriter& OutHTML_SwUnderline( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + if( rWrt.m_bOutOpts ) + return rWrt; + + const FontLineStyle eUnder = static_cast<const SvxUnderlineItem&>(rHt).GetLineStyle(); + if( LINESTYLE_NONE != eUnder && !rWrt.mbReqIF ) + { + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_underline), rWrt.m_bTagOn ); + } + else if( rWrt.m_bCfgOutStyles && rWrt.m_bTextAttr ) + { + // maybe as CSS1 attribute? + OutCSS1_HintSpanTag( rWrt, rHt ); + } + + return rWrt; +} + +static SwHTMLWriter& OutHTML_SwFlyCnt( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + const SwFormatFlyCnt& rFlyCnt = static_cast<const SwFormatFlyCnt&>(rHt); + + const SwFrameFormat& rFormat = *rFlyCnt.GetFrameFormat(); + const SdrObject *pSdrObj = nullptr; + + SwHTMLFrameType eType = rWrt.GuessFrameType( rFormat, pSdrObj ); + AllHtmlFlags nMode = getHTMLOutFrameAsCharTable(eType, rWrt.m_nExportMode); + rWrt.OutFrameFormat( nMode, rFormat, pSdrObj ); + return rWrt; +} + +// This is now our Blink item. Blinking is activated by setting the item to +// true! +static SwHTMLWriter& OutHTML_SwBlink( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + if( rWrt.m_bOutOpts ) + return rWrt; + + if( static_cast<const SvxBlinkItem&>(rHt).GetValue() ) + { + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_blink), rWrt.m_bTagOn ); + } + else if( rWrt.m_bCfgOutStyles && rWrt.m_bTextAttr ) + { + // maybe as CSS1 attribute? + OutCSS1_HintSpanTag( rWrt, rHt ); + } + + return rWrt; +} + +SwHTMLWriter& OutHTML_INetFormat( SwHTMLWriter& rWrt, const SwFormatINetFormat& rINetFormat, bool bOn ) +{ + OUString aURL( rINetFormat.GetValue() ); + const SvxMacroTableDtor *pMacTable = rINetFormat.GetMacroTable(); + bool bEvents = pMacTable != nullptr && !pMacTable->empty(); + + // Anything to output at all? + if( aURL.isEmpty() && !bEvents && rINetFormat.GetName().isEmpty() ) + return rWrt; + + // bOn controls if we are writing the opening or closing tag + if( !bOn ) + { + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_anchor), false ); + return rWrt; + } + + OString sOut("<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_anchor); + + bool bScriptDependent = false; + { + const SwCharFormat* pFormat = rWrt.m_pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool( + RES_POOLCHR_INET_NORMAL ); + std::unique_ptr<SwHTMLFormatInfo> pFormatInfo(new SwHTMLFormatInfo(pFormat)); + auto const it = rWrt.m_CharFormatInfos.find( pFormatInfo ); + if (it != rWrt.m_CharFormatInfos.end()) + { + bScriptDependent = (*it)->bScriptDependent; + } + } + if( !bScriptDependent ) + { + const SwCharFormat* pFormat = rWrt.m_pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool( + RES_POOLCHR_INET_VISIT ); + std::unique_ptr<SwHTMLFormatInfo> pFormatInfo(new SwHTMLFormatInfo(pFormat)); + auto const it = rWrt.m_CharFormatInfos.find( pFormatInfo ); + if (it != rWrt.m_CharFormatInfos.end()) + { + bScriptDependent = (*it)->bScriptDependent; + } + } + + if( bScriptDependent ) + { + sOut += " " OOO_STRING_SVTOOLS_HTML_O_class "=\""; + const char* pStr = nullptr; + switch( rWrt.m_nCSS1Script ) + { + case CSS1_OUTMODE_WESTERN: + pStr = "western"; + break; + case CSS1_OUTMODE_CJK: + pStr = "cjk"; + break; + case CSS1_OUTMODE_CTL: + pStr = "ctl"; + break; + } + sOut += pStr + OString::Concat("\""); + } + + rWrt.Strm().WriteOString( sOut ); + sOut = ""_ostr; + + OUString sRel; + + if( !aURL.isEmpty() || bEvents ) + { + OUString sTmp( aURL.toAsciiUpperCase() ); + sal_Int32 nPos = sTmp.indexOf( "\" REL=" ); + if( nPos >= 0 ) + { + sRel = aURL.copy( nPos+1 ); + aURL = aURL.copy( 0, nPos); + } + aURL = comphelper::string::strip(aURL, ' '); + + sOut += " " OOO_STRING_SVTOOLS_HTML_O_href "=\""; + rWrt.Strm().WriteOString( sOut ); + rWrt.OutHyperlinkHRefValue( aURL ); + sOut = "\""_ostr; + } + + if( !rINetFormat.GetName().isEmpty() ) + { + sOut += " " OOO_STRING_SVTOOLS_HTML_O_name "=\""; + rWrt.Strm().WriteOString( sOut ); + HTMLOutFuncs::Out_String( rWrt.Strm(), rINetFormat.GetName() ); + sOut = "\""_ostr; + } + + const OUString& rTarget = rINetFormat.GetTargetFrame(); + if( !rTarget.isEmpty() ) + { + sOut += " " OOO_STRING_SVTOOLS_HTML_O_target "=\""; + rWrt.Strm().WriteOString( sOut ); + HTMLOutFuncs::Out_String( rWrt.Strm(), rTarget ); + sOut = "\""_ostr; + } + + if( !sRel.isEmpty() ) + sOut += OUStringToOString(sRel, RTL_TEXTENCODING_ASCII_US); + + if( !sOut.isEmpty() ) + rWrt.Strm().WriteOString( sOut ); + + if( bEvents ) + HTMLOutFuncs::Out_Events( rWrt.Strm(), *pMacTable, aAnchorEventTable, + rWrt.m_bCfgStarBasic ); + rWrt.Strm().WriteOString( ">" ); + + return rWrt; +} + +static SwHTMLWriter& OutHTML_SwFormatINetFormat( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + if( rWrt.m_bOutOpts ) + return rWrt; + + const SwFormatINetFormat& rINetFormat = static_cast<const SwFormatINetFormat&>(rHt); + + if( rWrt.m_bTagOn ) + { + // if necessary, temporarily close an attribute that is still open + if( !rWrt.m_aINetFormats.empty() ) + { + SwFormatINetFormat *pINetFormat = + rWrt.m_aINetFormats.back(); + OutHTML_INetFormat( rWrt, *pINetFormat, false ); + } + + // now, open the new one + OutHTML_INetFormat( rWrt, rINetFormat, true ); + + // and remember it + SwFormatINetFormat *pINetFormat = new SwFormatINetFormat( rINetFormat ); + rWrt.m_aINetFormats.push_back( pINetFormat ); + } + else + { + OutHTML_INetFormat( rWrt, rINetFormat, false ); + + OSL_ENSURE( rWrt.m_aINetFormats.size(), "there must be a URL attribute missing" ); + if( !rWrt.m_aINetFormats.empty() ) + { + // get its own attribute from the stack + SwFormatINetFormat *pINetFormat = rWrt.m_aINetFormats.back(); + rWrt.m_aINetFormats.pop_back(); + delete pINetFormat; + } + + if( !rWrt.m_aINetFormats.empty() ) + { + // there is still an attribute on the stack that must be reopened + SwFormatINetFormat *pINetFormat = rWrt.m_aINetFormats.back(); + OutHTML_INetFormat( rWrt, *pINetFormat, true ); + } + } + + return rWrt; +} + +static SwHTMLWriter& OutHTML_SwTextCharFormat( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + if( rWrt.m_bOutOpts ) + return rWrt; + + const SwFormatCharFormat& rChrFormat = static_cast<const SwFormatCharFormat&>(rHt); + const SwCharFormat* pFormat = rChrFormat.GetCharFormat(); + + if( !pFormat ) + { + return rWrt; + } + + std::unique_ptr<SwHTMLFormatInfo> pTmpInfo(new SwHTMLFormatInfo(pFormat)); + SwHTMLFormatInfos::const_iterator it = rWrt.m_CharFormatInfos.find(pTmpInfo); + if (it == rWrt.m_CharFormatInfos.end()) + return rWrt; + + const SwHTMLFormatInfo *pFormatInfo = it->get(); + OSL_ENSURE( pFormatInfo, "Why is there no information about the character style?" ); + + if( rWrt.m_bTagOn ) + { + OString sOut = "<" + rWrt.GetNamespace(); + if( !pFormatInfo->aToken.isEmpty() ) + sOut += pFormatInfo->aToken; + else + sOut += OOO_STRING_SVTOOLS_HTML_span; + + if( rWrt.m_bCfgOutStyles && + (!pFormatInfo->aClass.isEmpty() || pFormatInfo->bScriptDependent) ) + { + sOut += " " OOO_STRING_SVTOOLS_HTML_O_class "=\""; + rWrt.Strm().WriteOString( sOut ); + OUString aClass( pFormatInfo->aClass ); + if( pFormatInfo->bScriptDependent ) + { + if( !aClass.isEmpty() ) + aClass += "-"; + switch( rWrt.m_nCSS1Script ) + { + case CSS1_OUTMODE_WESTERN: + aClass += "western"; + break; + case CSS1_OUTMODE_CJK: + aClass += "cjk"; + break; + case CSS1_OUTMODE_CTL: + aClass += "ctl"; + break; + } + } + HTMLOutFuncs::Out_String( rWrt.Strm(), aClass ); + sOut = "\""_ostr; + } + sOut += ">"; + rWrt.Strm().WriteOString( sOut ); + } + else + { + OString aTag = !pFormatInfo->aToken.isEmpty() ? pFormatInfo->aToken.getStr() + : OOO_STRING_SVTOOLS_HTML_span; + HTMLOutFuncs::Out_AsciiTag(rWrt.Strm(), Concat2View(rWrt.GetNamespace() + aTag), false); + } + + return rWrt; +} + +static SwHTMLWriter& OutHTML_SvxAdjust( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + if( !rWrt.m_bOutOpts || !rWrt.m_bTagOn ) + return rWrt; + + const SvxAdjustItem& rAdjust = static_cast<const SvxAdjustItem&>(rHt); + const char* pStr = nullptr; + switch( rAdjust.GetAdjust() ) + { + case SvxAdjust::Center: pStr = OOO_STRING_SVTOOLS_HTML_AL_center; break; + case SvxAdjust::Left: pStr = OOO_STRING_SVTOOLS_HTML_AL_left; break; + case SvxAdjust::Right: pStr = OOO_STRING_SVTOOLS_HTML_AL_right; break; + case SvxAdjust::Block: pStr = OOO_STRING_SVTOOLS_HTML_AL_justify; break; + default: + ; + } + if( pStr ) + { + OString sOut = OString::Concat(" " OOO_STRING_SVTOOLS_HTML_O_align "=\"") + + pStr + "\""; + rWrt.Strm().WriteOString( sOut ); + } + + return rWrt; +} + +/* + * here, define the table for the HTML function pointers to the output + * functions. + */ + +SwAttrFnTab aHTMLAttrFnTab = { +/* RES_CHRATR_CASEMAP */ OutHTML_CSS1Attr, +/* RES_CHRATR_CHARSETCOLOR */ nullptr, +/* RES_CHRATR_COLOR */ OutHTML_SvxColor, +/* RES_CHRATR_CONTOUR */ nullptr, +/* RES_CHRATR_CROSSEDOUT */ OutHTML_SwCrossedOut, +/* RES_CHRATR_ESCAPEMENT */ OutHTML_SvxEscapement, +/* RES_CHRATR_FONT */ OutHTML_SvxFont, +/* RES_CHRATR_FONTSIZE */ OutHTML_SvxFontHeight, +/* RES_CHRATR_KERNING */ OutHTML_CSS1Attr, +/* RES_CHRATR_LANGUAGE */ OutHTML_SvxLanguage, +/* RES_CHRATR_POSTURE */ OutHTML_SwPosture, +/* RES_CHRATR_UNUSED1*/ nullptr, +/* RES_CHRATR_SHADOWED */ nullptr, +/* RES_CHRATR_UNDERLINE */ OutHTML_SwUnderline, +/* RES_CHRATR_WEIGHT */ OutHTML_SwWeight, +/* RES_CHRATR_WORDLINEMODE */ nullptr, +/* RES_CHRATR_AUTOKERN */ nullptr, +/* RES_CHRATR_BLINK */ OutHTML_SwBlink, +/* RES_CHRATR_NOHYPHEN */ nullptr, // New: don't hyphenate +/* RES_CHRATR_UNUSED2 */ nullptr, +/* RES_CHRATR_BACKGROUND */ OutHTML_CSS1Attr, // New: character background +/* RES_CHRATR_CJK_FONT */ OutHTML_SvxFont, +/* RES_CHRATR_CJK_FONTSIZE */ OutHTML_SvxFontHeight, +/* RES_CHRATR_CJK_LANGUAGE */ OutHTML_SvxLanguage, +/* RES_CHRATR_CJK_POSTURE */ OutHTML_SwPosture, +/* RES_CHRATR_CJK_WEIGHT */ OutHTML_SwWeight, +/* RES_CHRATR_CTL_FONT */ OutHTML_SvxFont, +/* RES_CHRATR_CTL_FONTSIZE */ OutHTML_SvxFontHeight, +/* RES_CHRATR_CTL_LANGUAGE */ OutHTML_SvxLanguage, +/* RES_CHRATR_CTL_POSTURE */ OutHTML_SwPosture, +/* RES_CHRATR_CTL_WEIGHT */ OutHTML_SwWeight, +/* RES_CHRATR_ROTATE */ nullptr, +/* RES_CHRATR_EMPHASIS_MARK */ nullptr, +/* RES_CHRATR_TWO_LINES */ nullptr, +/* RES_CHRATR_SCALEW */ nullptr, +/* RES_CHRATR_RELIEF */ nullptr, +/* RES_CHRATR_HIDDEN */ OutHTML_CSS1Attr, +/* RES_CHRATR_OVERLINE */ OutHTML_CSS1Attr, +/* RES_CHRATR_RSID */ nullptr, +/* RES_CHRATR_BOX */ OutHTML_CSS1Attr, +/* RES_CHRATR_SHADOW */ nullptr, +/* RES_CHRATR_HIGHLIGHT */ nullptr, +/* RES_CHRATR_GRABBAG */ nullptr, +/* RES_CHRATR_BIDIRTL */ nullptr, +/* RES_CHRATR_IDCTHINT */ nullptr, + +/* RES_TXTATR_REFMARK */ nullptr, +/* RES_TXTATR_TOXMARK */ nullptr, +/* RES_TXTATR_META */ nullptr, +/* RES_TXTATR_METAFIELD */ nullptr, +/* RES_TXTATR_AUTOFMT */ nullptr, +/* RES_TXTATR_INETFMT */ OutHTML_SwFormatINetFormat, +/* RES_TXTATR_CHARFMT */ OutHTML_SwTextCharFormat, +/* RES_TXTATR_CJK_RUBY */ nullptr, +/* RES_TXTATR_UNKNOWN_CONTAINER */ nullptr, +/* RES_TXTATR_INPUTFIELD */ OutHTML_SwFormatField, +/* RES_TXTATR_CONTENTCONTROL */ nullptr, + +/* RES_TXTATR_FIELD */ OutHTML_SwFormatField, +/* RES_TXTATR_FLYCNT */ OutHTML_SwFlyCnt, +/* RES_TXTATR_FTN */ OutHTML_SwFormatFootnote, +/* RES_TXTATR_ANNOTATION */ OutHTML_SwFormatField, +/* RES_TXTATR_LINEBREAK */ OutHTML_SwFormatLineBreak, +/* RES_TXTATR_DUMMY1 */ nullptr, // Dummy: + +/* RES_PARATR_LINESPACING */ nullptr, +/* RES_PARATR_ADJUST */ OutHTML_SvxAdjust, +/* RES_PARATR_SPLIT */ nullptr, +/* RES_PARATR_ORPHANS */ nullptr, +/* RES_PARATR_WIDOWS */ nullptr, +/* RES_PARATR_TABSTOP */ nullptr, +/* RES_PARATR_HYPHENZONE*/ nullptr, +/* RES_PARATR_DROP */ OutHTML_CSS1Attr, +/* RES_PARATR_REGISTER */ nullptr, // new: register-true +/* RES_PARATR_NUMRULE */ nullptr, // Dummy: +/* RES_PARATR_SCRIPTSPACE */ nullptr, // Dummy: +/* RES_PARATR_HANGINGPUNCTUATION */ nullptr, // Dummy: +/* RES_PARATR_FORBIDDEN_RULES */ nullptr, // new +/* RES_PARATR_VERTALIGN */ nullptr, // new +/* RES_PARATR_SNAPTOGRID*/ nullptr, // new +/* RES_PARATR_CONNECT_TO_BORDER */ nullptr, // new +/* RES_PARATR_OUTLINELEVEL */ nullptr, +/* RES_PARATR_RSID */ nullptr, +/* RES_PARATR_GRABBAG */ nullptr, + +/* RES_PARATR_LIST_ID */ nullptr, // new +/* RES_PARATR_LIST_LEVEL */ nullptr, // new +/* RES_PARATR_LIST_ISRESTART */ nullptr, // new +/* RES_PARATR_LIST_RESTARTVALUE */ nullptr, // new +/* RES_PARATR_LIST_ISCOUNTED */ nullptr, // new + +/* RES_FILL_ORDER */ nullptr, +/* RES_FRM_SIZE */ nullptr, +/* RES_PAPER_BIN */ nullptr, +/* RES_MARGIN_FIRSTLINE */ nullptr, +/* RES_MARGIN_TEXTLEFT */ nullptr, +/* RES_MARGIN_RIGHT */ nullptr, +/* RES_MARGIN_LEFT */ nullptr, +/* RES_MARGIN_GUTTER */ nullptr, +/* RES_MARGIN_GUTTER_RIGHT */ nullptr, +/* RES_LR_SPACE */ nullptr, +/* RES_UL_SPACE */ nullptr, +/* RES_PAGEDESC */ nullptr, +/* RES_BREAK */ nullptr, +/* RES_CNTNT */ nullptr, +/* RES_HEADER */ nullptr, +/* RES_FOOTER */ nullptr, +/* RES_PRINT */ nullptr, +/* RES_OPAQUE */ nullptr, +/* RES_PROTECT */ nullptr, +/* RES_SURROUND */ nullptr, +/* RES_VERT_ORIENT */ nullptr, +/* RES_HORI_ORIENT */ nullptr, +/* RES_ANCHOR */ nullptr, +/* RES_BACKGROUND */ nullptr, +/* RES_BOX */ nullptr, +/* RES_SHADOW */ nullptr, +/* RES_FRMMACRO */ nullptr, +/* RES_COL */ nullptr, +/* RES_KEEP */ nullptr, +/* RES_URL */ nullptr, +/* RES_EDIT_IN_READONLY */ nullptr, +/* RES_LAYOUT_SPLIT */ nullptr, +/* RES_CHAIN */ nullptr, +/* RES_TEXTGRID */ nullptr, +/* RES_LINENUMBER */ nullptr, +/* RES_FTN_AT_TXTEND */ nullptr, +/* RES_END_AT_TXTEND */ nullptr, +/* RES_COLUMNBALANCE */ nullptr, +/* RES_FRAMEDIR */ nullptr, +/* RES_HEADER_FOOTER_EAT_SPACING */ nullptr, +/* RES_ROW_SPLIT */ nullptr, +/* RES_FLY_SPLIT */ nullptr, +/* RES_FOLLOW_TEXT_FLOW */ nullptr, +/* RES_COLLAPSING_BORDERS */ nullptr, +/* RES_WRAP_INFLUENCE_ON_OBJPOS */ nullptr, +/* RES_AUTO_STYLE */ nullptr, +/* RES_FRMATR_STYLE_NAME */ nullptr, +/* RES_FRMATR_CONDITIONAL_STYLE_NAME */ nullptr, +/* RES_FRMATR_GRABBAG */ nullptr, +/* RES_TEXT_VERT_ADJUST */ nullptr, +/* RES_BACKGROUND_FULL_SIZE */ nullptr, +/* RES_RTL_GUTTER */ nullptr, +/* RES_DECORATIVE */ nullptr, + +/* RES_GRFATR_MIRRORGRF */ nullptr, +/* RES_GRFATR_CROPGRF */ nullptr, +/* RES_GRFATR_ROTATION */ nullptr, +/* RES_GRFATR_LUMINANCE */ nullptr, +/* RES_GRFATR_CONTRAST */ nullptr, +/* RES_GRFATR_CHANNELR */ nullptr, +/* RES_GRFATR_CHANNELG */ nullptr, +/* RES_GRFATR_CHANNELB */ nullptr, +/* RES_GRFATR_GAMMA */ nullptr, +/* RES_GRFATR_INVERT */ nullptr, +/* RES_GRFATR_TRANSPARENCY */ nullptr, +/* RES_GRFATR_DRWAMODE */ nullptr, +/* RES_GRFATR_DUMMY3 */ nullptr, +/* RES_GRFATR_DUMMY4 */ nullptr, +/* RES_GRFATR_DUMMY5 */ nullptr, + +/* RES_BOXATR_FORMAT */ nullptr, +/* RES_BOXATR_FORMULA */ nullptr, +/* RES_BOXATR_VALUE */ nullptr +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlatr.hxx b/sw/source/filter/html/htmlatr.hxx new file mode 100644 index 0000000000..642a7c048e --- /dev/null +++ b/sw/source/filter/html/htmlatr.hxx @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#ifndef INCLUDED_SW_SOURCE_FILTER_HTML_HTMLATR_HXX +#define INCLUDED_SW_SOURCE_FILTER_HTML_HTMLATR_HXX + +#include <sal/config.h> + +struct HTMLOutEvent; + +extern HTMLOutEvent const aAnchorEventTable[]; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sw/source/filter/html/htmlbas.cxx b/sw/source/filter/html/htmlbas.cxx new file mode 100644 index 0000000000..3500a631f1 --- /dev/null +++ b/sw/source/filter/html/htmlbas.cxx @@ -0,0 +1,330 @@ +/* -*- 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 <config_features.h> + +#include <comphelper/string.hxx> +#include <osl/diagnose.h> +#include <basic/basmgr.hxx> +#include <basic/sbmod.hxx> +#include <sfx2/evntconf.hxx> +#include <sfx2/app.hxx> +#include <svtools/htmlout.hxx> +#include <svtools/htmlkywd.hxx> + +#include <com/sun/star/document/XEventsSupplier.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/script/XLibraryContainer.hpp> +#include <com/sun/star/container/XNameContainer.hpp> + +#include <fmtfld.hxx> + +#include <doc.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <docsh.hxx> +#include <docufld.hxx> +#include "wrthtml.hxx" +#include "swhtml.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::container; + +HTMLOutEvent const aBodyEventTable[] = +{ + { OOO_STRING_SVTOOLS_HTML_O_SDonload, OOO_STRING_SVTOOLS_HTML_O_onload, SvMacroItemId::OpenDoc }, + { OOO_STRING_SVTOOLS_HTML_O_SDonunload, OOO_STRING_SVTOOLS_HTML_O_onunload, SvMacroItemId::PrepareCloseDoc }, + { OOO_STRING_SVTOOLS_HTML_O_SDonfocus, OOO_STRING_SVTOOLS_HTML_O_onfocus, SvMacroItemId::ActivateDoc }, + { OOO_STRING_SVTOOLS_HTML_O_SDonblur, OOO_STRING_SVTOOLS_HTML_O_onblur, SvMacroItemId::DeactivateDoc }, + { nullptr, nullptr, SvMacroItemId::NONE } +}; + +void SwHTMLParser::NewScript() +{ + ParseScriptOptions( m_aScriptType, m_sBaseURL, m_eScriptLang, m_aScriptURL, + m_aBasicLib, m_aBasicModule ); + + if( !m_aScriptURL.isEmpty() ) + { + // Ignore the script tag + m_bIgnoreRawData = true; + } +} + +void SwHTMLParser::EndScript() +{ + bool bInsIntoBasic = false, + bInsSrcIntoField = false; + + switch( m_eScriptLang ) + { + case HTMLScriptLanguage::StarBasic: + bInsIntoBasic = true; + break; + default: + bInsSrcIntoField = true; + break; + } + + m_bIgnoreRawData = false; + m_aScriptSource = convertLineEnd(m_aScriptSource, GetSystemLineEnd()); + + // Except for StarBasic and unused JavaScript, save each script or module name in a field + if( bInsSrcIntoField && !m_bIgnoreHTMLComments ) + { + SwScriptFieldType *pType = + static_cast<SwScriptFieldType*>(m_xDoc->getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::Script )); + + SwScriptField aField( pType, m_aScriptType, + !m_aScriptURL.isEmpty() ? m_aScriptURL : m_aScriptSource, + !m_aScriptURL.isEmpty() ); + InsertAttr( SwFormatField( aField ), false ); + } + + SwDocShell *pDocSh = m_xDoc->GetDocShell(); + if( !m_aScriptSource.isEmpty() && pDocSh && + bInsIntoBasic && IsNewDoc() ) + { + // Create a Basic module for javascript and StarBasic. + + // The Basic does still not remove SGML comments + RemoveSGMLComment( m_aScriptSource ); + + // get library name + OUString aLibName; + if( !m_aBasicLib.isEmpty() ) + aLibName = m_aBasicLib; + else + aLibName = "Standard"; + + // get module library container + Reference< script::XLibraryContainer > xModLibContainer = pDocSh->GetBasicContainer(); + + if ( xModLibContainer.is() ) + { + Reference< container::XNameContainer > xModLib; + if ( xModLibContainer->hasByName( aLibName ) ) + { + // get module library + Any aElement = xModLibContainer->getByName( aLibName ); + aElement >>= xModLib; + } + else + { + // create module library + xModLib = xModLibContainer->createLibrary( aLibName ); + } + + if ( xModLib.is() ) + { + if( m_aBasicModule.isEmpty() ) + { + // create module name + bool bFound = true; + while( bFound ) + { + m_aBasicModule = "Modul" + OUString::number( static_cast<sal_Int32>(++m_nSBModuleCnt) ); + bFound = xModLib->hasByName( m_aBasicModule ); + } + } + + // create module + OUString aModName( m_aBasicModule ); + if ( !xModLib->hasByName( aModName ) ) + { + Any aElement; + aElement <<= m_aScriptSource; + xModLib->insertByName( aModName , aElement ); + } + } + } + + // get dialog library container + Reference< script::XLibraryContainer > xDlgLibContainer = pDocSh->GetDialogContainer(); + + if ( xDlgLibContainer.is() ) + { + if ( !xDlgLibContainer->hasByName( aLibName ) ) + { + // create dialog library + xDlgLibContainer->createLibrary( aLibName ); + } + } + } + + m_aScriptSource.clear(); + m_aScriptType.clear(); + m_aScriptURL.clear(); + + m_aBasicLib.clear(); + m_aBasicModule.clear(); +} + +void SwHTMLParser::AddScriptSource() +{ + // We'll just remember a few strings here + if( aToken.getLength() > 2 && + (HTMLScriptLanguage::StarBasic==m_eScriptLang && aToken[ 0 ] == '\'') ) + { + sal_Int32 nPos = -1; + if( m_aBasicLib.isEmpty() ) + { + nPos = aToken.indexOf( OOO_STRING_SVTOOLS_HTML_SB_library ); + if( nPos != -1 ) + { + m_aBasicLib = + aToken.subView( nPos + sizeof(OOO_STRING_SVTOOLS_HTML_SB_library) - 1 ); + m_aBasicLib = comphelper::string::strip(m_aBasicLib, ' '); + } + } + + if( m_aBasicModule.isEmpty() && nPos == -1 ) + { + nPos = aToken.indexOf( OOO_STRING_SVTOOLS_HTML_SB_module ); + if( nPos != -1 ) + { + m_aBasicModule = + aToken.subView( nPos + sizeof(OOO_STRING_SVTOOLS_HTML_SB_module) - 1 ); + m_aBasicModule = comphelper::string::strip(m_aBasicModule, ' '); + } + } + + if( nPos == -1 ) + { + if( !m_aScriptSource.isEmpty() ) + m_aScriptSource += "\n"; + m_aScriptSource += aToken; + } + } + else if( !m_aScriptSource.isEmpty() || !aToken.isEmpty() ) + { + // Empty lines are ignored on the beginning + if( !m_aScriptSource.isEmpty() ) + { + m_aScriptSource += "\n"; + } + m_aScriptSource += aToken; + } +} + +void SwHTMLParser::InsertBasicDocEvent( const OUString& aEvent, const OUString& rName, + ScriptType eScrType, + const OUString& rScrType ) +{ + OSL_ENSURE( !rName.isEmpty(), "InsertBasicDocEvent() called without macro" ); + if( rName.isEmpty() ) + return; + + SwDocShell *pDocSh = m_xDoc->GetDocShell(); + OSL_ENSURE( pDocSh, "Where is the DocShell?" ); + if( !pDocSh ) + return; + + OUString sEvent(convertLineEnd(rName, GetSystemLineEnd())); + OUString sScriptType; + if( EXTENDED_STYPE == eScrType ) + sScriptType = rScrType; + + SfxEventConfiguration::ConfigureEvent( aEvent, SvxMacro( sEvent, sScriptType, eScrType ), + pDocSh ); +} + +void SwHTMLWriter::OutBasic(const SwHTMLWriter & rHTMLWrt) +{ +#if !HAVE_FEATURE_SCRIPTING + (void) rHTMLWrt; +#else + if( !m_bCfgStarBasic ) + return; + + BasicManager *pBasicMan = m_pDoc->GetDocShell()->GetBasicManager(); + OSL_ENSURE( pBasicMan, "Where is the Basic-Manager?" ); + // Only write DocumentBasic + if( !pBasicMan || pBasicMan == SfxApplication::GetBasicManager() ) + { + return; + } + + bool bFirst=true; + // Now write all StarBasic and unused Javascript modules + for( sal_uInt16 i=0; i<pBasicMan->GetLibCount(); i++ ) + { + StarBASIC *pBasic = pBasicMan->GetLib( i ); + const OUString& rLibName = pBasic->GetName(); + for( const auto& pModule: pBasic->GetModules() ) + { + OUString sLang(SVX_MACRO_LANGUAGE_STARBASIC); + + if( bFirst ) + { + bFirst = false; + OutNewLine(); + OString sOut = + "<" + rHTMLWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_meta + " " OOO_STRING_SVTOOLS_HTML_O_httpequiv + "=\"" + OOO_STRING_SVTOOLS_HTML_META_content_script_type + "\" " OOO_STRING_SVTOOLS_HTML_O_content + "=\"text/x-"; + Strm().WriteOString( sOut ); + // Entities aren't welcome here + Strm().WriteOString( OUStringToOString(sLang, RTL_TEXTENCODING_UTF8) ) + .WriteOString( "\">" ); + } + + const OUString& rModName = pModule->GetName(); + Strm().WriteOString( SAL_NEWLINE_STRING ); // don't indent! + HTMLOutFuncs::OutScript( Strm(), GetBaseURL(), pModule->GetSource32(), + sLang, STARBASIC, OUString(), + &rLibName, &rModName ); + } + } +#endif +} + +static const char* aEventNames[] = +{ + "OnLoad", "OnPrepareUnload", "OnFocus", "OnUnfocus" +}; + +void SwHTMLWriter::OutBasicBodyEvents() +{ + SwDocShell *pDocSh = m_pDoc->GetDocShell(); + if( !pDocSh ) + return; + + SvxMacroTableDtor aDocTable; + + uno::Reference< document::XEventsSupplier > xSup( pDocSh->GetModel(), uno::UNO_QUERY ); + uno::Reference < container::XNameReplace > xEvents = xSup->getEvents(); + for ( sal_Int32 i=0; i<4; i++ ) + { + std::unique_ptr<SvxMacro> pMacro = SfxEventConfiguration::ConvertToMacro( xEvents->getByName( OUString::createFromAscii(aEventNames[i]) ), pDocSh ); + if ( pMacro ) + { + aDocTable.Insert( aBodyEventTable[i].nEvent, *pMacro ); + } + } + + if( !aDocTable.empty() ) + HTMLOutFuncs::Out_Events( Strm(), aDocTable, aBodyEventTable, + m_bCfgStarBasic ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlcss1.cxx b/sw/source/filter/html/htmlcss1.cxx new file mode 100644 index 0000000000..92e5d0d94d --- /dev/null +++ b/sw/source/filter/html/htmlcss1.cxx @@ -0,0 +1,2331 @@ +/* -*- 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 <hintids.hxx> +#include <svl/itemiter.hxx> +#include <svl/urihelper.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <sfx2/docfile.hxx> +#include <editeng/editids.hrc> +#include <editeng/fhgtitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/flstitem.hxx> +#include <editeng/formatbreakitem.hxx> +#include <editeng/keepitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <o3tl/string_view.hxx> +#include <svtools/htmltokn.h> +#include <svtools/htmlkywd.hxx> +#include <fmtpdsc.hxx> +#include <fmtanchr.hxx> +#include <fmtornt.hxx> +#include <fmtsrnd.hxx> +#include <fmtfsize.hxx> +#include <frmatr.hxx> +#include <charfmt.hxx> +#include <docary.hxx> +#include <osl/diagnose.h> + +#include <doc.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <pam.hxx> +#include <poolfmt.hxx> +#include <docsh.hxx> +#include <paratr.hxx> +#include <pagedesc.hxx> +#include "css1kywd.hxx" +#include "swcss1.hxx" +#include "htmlnum.hxx" +#include "swhtml.hxx" +#include <numrule.hxx> +#include "css1atr.hxx" + +using namespace ::com::sun::star; + +// How many rows/characters are allowed for DropCaps? +// (Are there maybe somewhere else corresponding values?) +#define MAX_DROPCAP_LINES 9 +#define MAX_DROPCAP_CHARS 9 + +static void lcl_swcss1_setEncoding( SwFormat& rFormat, rtl_TextEncoding eEnc ); + +// Implementation of SwCSS1Parsers (actually swcss1.cxx) +const sal_uInt16 aItemIds[] = +{ + RES_BREAK, + RES_PAGEDESC, + RES_KEEP, +}; + +void SwCSS1Parser::ChgPageDesc( const SwPageDesc *pPageDesc, + const SwPageDesc& rNewPageDesc ) +{ + size_t pos; + bool found = m_pDoc->ContainsPageDesc( pPageDesc, &pos ); + OSL_ENSURE( found, "style not found" ); + if (found) + m_pDoc->ChgPageDesc( pos, rNewPageDesc ); +} + +SwCSS1Parser::SwCSS1Parser(SwDoc *const pDoc, SwHTMLParser const& rParser, + const sal_uInt32 aFHeights[7], const OUString& rBaseURL, bool const bNewDoc) + : SvxCSS1Parser(pDoc->GetAttrPool(), rBaseURL, + aItemIds, SAL_N_ELEMENTS(aItemIds)) + , m_pDoc( pDoc ) + , m_rHTMLParser(rParser) + , m_nDropCapCnt( 0 ), + m_bIsNewDoc( bNewDoc ), + m_bBodyBGColorSet( false ), + m_bBodyBackgroundSet( false ), + m_bBodyTextSet( false ), + m_bBodyLinkSet( false ), + m_bBodyVLinkSet( false ), + m_bSetFirstPageDesc( false ), + m_bSetRightPageDesc( false ), + m_bTableHeaderTextCollSet( false ), + m_bTableTextCollSet( false ), + m_bLinkCharFormatsSet( false ) +{ + m_aFontHeights[0] = aFHeights[0]; + m_aFontHeights[1] = aFHeights[1]; + m_aFontHeights[2] = aFHeights[2]; + m_aFontHeights[3] = aFHeights[3]; + m_aFontHeights[4] = aFHeights[4]; + m_aFontHeights[5] = aFHeights[5]; + m_aFontHeights[6] = aFHeights[6]; +} + +SwCSS1Parser::~SwCSS1Parser() +{ +} + +// Feature: PrintExt +bool SwCSS1Parser::SetFormatBreak( SfxItemSet& rItemSet, + const SvxCSS1PropertyInfo& rPropInfo ) +{ + SvxBreak eBreak = SvxBreak::NONE; + bool bKeep = false; + bool bSetKeep = false, bSetBreak = false, bSetPageDesc = false; + const SwPageDesc *pPageDesc = nullptr; + switch( rPropInfo.m_ePageBreakBefore ) + { + case SVX_CSS1_PBREAK_ALWAYS: + eBreak = SvxBreak::PageBefore; + bSetBreak = true; + break; + case SVX_CSS1_PBREAK_LEFT: + pPageDesc = GetLeftPageDesc( true ); + bSetPageDesc = true; + break; + case SVX_CSS1_PBREAK_RIGHT: + pPageDesc = GetRightPageDesc( true ); + bSetPageDesc = true; + break; + case SVX_CSS1_PBREAK_AUTO: + bSetBreak = bSetPageDesc = true; + break; + default: + ; + } + switch( rPropInfo.m_ePageBreakAfter ) + { + case SVX_CSS1_PBREAK_ALWAYS: + case SVX_CSS1_PBREAK_LEFT: + case SVX_CSS1_PBREAK_RIGHT: + // LEFT/RIGHT also could be set in the previous paragraph + eBreak = SvxBreak::PageAfter; + bSetBreak = true; + break; + case SVX_CSS1_PBREAK_AUTO: + bSetBreak = bSetKeep = bSetPageDesc = true; + break; + case SVX_CSS1_PBREAK_AVOID: + bKeep = bSetKeep = true; + break; + default: + ; + } + + if( bSetBreak ) + rItemSet.Put( SvxFormatBreakItem( eBreak, RES_BREAK ) ); + if( bSetPageDesc ) + rItemSet.Put( SwFormatPageDesc( pPageDesc ) ); + if( bSetKeep ) + rItemSet.Put( SvxFormatKeepItem( bKeep, RES_KEEP ) ); + + return bSetBreak; +} + +static void SetCharFormatAttrs( SwCharFormat *pCharFormat, SfxItemSet& rItemSet ) +{ + static const TypedWhichId<SvxFontHeightItem> aWhichIds[3] = { RES_CHRATR_FONTSIZE,RES_CHRATR_CJK_FONTSIZE, + RES_CHRATR_CTL_FONTSIZE }; + for(auto const & i : aWhichIds) + { + const SvxFontHeightItem* pItem = rItemSet.GetItemIfSet( i, false ); + if( pItem && pItem->GetProp() != 100) + { + // percentage values at the FontHeight item aren't supported + rItemSet.ClearItem( i ); + } + } + + pCharFormat->SetFormatAttr( rItemSet ); + + if( const SvxBrushItem* pItem = rItemSet.GetItemIfSet( RES_BACKGROUND, false ) ) + { + // A Brush-Item with RES_BACKGROUND must be converted to one + // with RES_CHRATR_BACKGROUND + + SvxBrushItem aBrushItem( *pItem ); + aBrushItem.SetWhich( RES_CHRATR_BACKGROUND ); + pCharFormat->SetFormatAttr( aBrushItem ); + } + + if( const SvxBoxItem* pItem = rItemSet.GetItemIfSet( RES_BOX, false ) ) + { + SvxBoxItem aBoxItem( *pItem ); + aBoxItem.SetWhich( RES_CHRATR_BOX ); + pCharFormat->SetFormatAttr( aBoxItem ); + } +} + +void SwCSS1Parser::SetLinkCharFormats() +{ + OSL_ENSURE( !m_bLinkCharFormatsSet, "Call SetLinkCharFormats unnecessary" ); + + SvxCSS1MapEntry *pStyleEntry = + GetTag( OOO_STRING_SVTOOLS_HTML_anchor ); + SwCharFormat *pUnvisited = nullptr, *pVisited = nullptr; + if( pStyleEntry ) + { + SfxItemSet& rItemSet = pStyleEntry->GetItemSet(); + bool bColorSet = (SfxItemState::SET==rItemSet.GetItemState(RES_CHRATR_COLOR, + false)); + pUnvisited = GetCharFormatFromPool( RES_POOLCHR_INET_NORMAL ); + SetCharFormatAttrs( pUnvisited, rItemSet ); + m_bBodyLinkSet |= bColorSet; + + pVisited = GetCharFormatFromPool( RES_POOLCHR_INET_VISIT ); + SetCharFormatAttrs( pVisited, rItemSet ); + m_bBodyVLinkSet |= bColorSet; + } + + OUString sTmp = OOO_STRING_SVTOOLS_HTML_anchor ":link"; + + pStyleEntry = GetTag( sTmp ); + if( pStyleEntry ) + { + SfxItemSet& rItemSet = pStyleEntry->GetItemSet(); + bool bColorSet = (SfxItemState::SET==rItemSet.GetItemState(RES_CHRATR_COLOR, + false)); + if( !pUnvisited ) + pUnvisited = GetCharFormatFromPool( RES_POOLCHR_INET_NORMAL ); + SetCharFormatAttrs( pUnvisited, rItemSet ); + m_bBodyLinkSet |= bColorSet; + } + + sTmp = OOO_STRING_SVTOOLS_HTML_anchor ":visited"; + + pStyleEntry = GetTag( sTmp ); + if( pStyleEntry ) + { + SfxItemSet& rItemSet = pStyleEntry->GetItemSet(); + bool bColorSet = (SfxItemState::SET==rItemSet.GetItemState(RES_CHRATR_COLOR, + false)); + if( !pVisited ) + pVisited = GetCharFormatFromPool( RES_POOLCHR_INET_VISIT ); + SetCharFormatAttrs( pVisited, rItemSet ); + m_bBodyVLinkSet |= bColorSet; + } + + m_bLinkCharFormatsSet = true; +} + +static void SetTextCollAttrs( SwTextFormatColl *pColl, SfxItemSet& rItemSet, + SvxCSS1PropertyInfo const & rPropInfo, + SwCSS1Parser *pCSS1Parser ) +{ + const SfxItemSet& rCollItemSet = pColl->GetAttrSet(); + + // note: there was some SvxLRSpaceItem code here that was nonobvious + // but it looks like the only cases in which it would be required + // with split items are if some nProp != 100 or if SetAutoFirst() had + // been called (on the pColl items) but it looks like none of these are + // possible in HTML import. + + // top and bottom border + const SvxULSpaceItem* pCollULItem; + const SvxULSpaceItem* pULItem; + if( (rPropInfo.m_bTopMargin || rPropInfo.m_bBottomMargin) && + (!rPropInfo.m_bTopMargin || !rPropInfo.m_bBottomMargin) && + (pCollULItem = rCollItemSet.GetItemIfSet(RES_UL_SPACE)) && + (pULItem = rItemSet.GetItemIfSet(RES_UL_SPACE,false)) ) + { + SvxULSpaceItem aULItem( *pCollULItem ); + if( rPropInfo.m_bTopMargin ) + aULItem.SetUpper( pULItem->GetUpper() ); + if( rPropInfo.m_bBottomMargin ) + aULItem.SetLower( pULItem->GetLower() ); + + rItemSet.Put( aULItem ); + } + + static const TypedWhichId<SvxFontHeightItem> aWhichIds[3] = { RES_CHRATR_FONTSIZE,RES_CHRATR_CJK_FONTSIZE, + RES_CHRATR_CTL_FONTSIZE }; + for(auto const & i : aWhichIds) + { + const SvxFontHeightItem* pItem = rItemSet.GetItemIfSet( i, false ); + if( pItem && pItem->GetProp() != 100) + { + // percentage values at the FontHeight item aren't supported + rItemSet.ClearItem( i ); + } + } + + pCSS1Parser->SetFormatBreak( rItemSet, rPropInfo ); + + pColl->SetFormatAttr( rItemSet ); +} + +void SwCSS1Parser::SetTableTextColl( bool bHeader ) +{ + OSL_ENSURE( !(bHeader ? m_bTableHeaderTextCollSet : m_bTableTextCollSet), + "Call SetTableTextColl unnecessary" ); + + sal_uInt16 nPoolId; + OUString sTag; + if( bHeader ) + { + nPoolId = RES_POOLCOLL_TABLE_HDLN; + sTag = OOO_STRING_SVTOOLS_HTML_tableheader; + } + else + { + nPoolId = RES_POOLCOLL_TABLE; + sTag = OOO_STRING_SVTOOLS_HTML_tabledata; + } + + SwTextFormatColl *pColl = nullptr; + + // The following entries will never be used again and may be changed. + SvxCSS1MapEntry *pStyleEntry = GetTag( sTag ); + if( pStyleEntry ) + { + pColl = GetTextFormatColl(nPoolId, OUString()); + SetTextCollAttrs( pColl, pStyleEntry->GetItemSet(), + pStyleEntry->GetPropertyInfo(), this ); + } + + OUString sTmp = sTag + " " OOO_STRING_SVTOOLS_HTML_parabreak; + pStyleEntry = GetTag( sTmp ); + if( pStyleEntry ) + { + if( !pColl ) + pColl = GetTextFormatColl(nPoolId, OUString()); + SetTextCollAttrs( pColl, pStyleEntry->GetItemSet(), + pStyleEntry->GetPropertyInfo(), this ); + } + + if( bHeader ) + m_bTableHeaderTextCollSet = true; + else + m_bTableTextCollSet = true; +} + +void SwCSS1Parser::SetPageDescAttrs( const SvxBrushItem *pBrush, + SfxItemSet *pItemSet2 ) +{ + std::shared_ptr<SvxBrushItem> aBrushItem(std::make_shared<SvxBrushItem>(RES_BACKGROUND)); + std::shared_ptr<SvxBoxItem> aBoxItem(std::make_shared<SvxBoxItem>(RES_BOX)); + std::shared_ptr<SvxFrameDirectionItem> aFrameDirItem(std::make_shared<SvxFrameDirectionItem>(SvxFrameDirection::Environment, RES_FRAMEDIR)); + bool bSetBrush = pBrush!=nullptr, bSetBox = false, bSetFrameDir = false; + if( pBrush ) + aBrushItem.reset(pBrush->Clone()); + + if( pItemSet2 ) + { + if( const SvxBrushItem* pItem = pItemSet2->GetItemIfSet( RES_BACKGROUND, false ) ) + { + // set a background + aBrushItem.reset(pItem->Clone()); + pItemSet2->ClearItem( RES_BACKGROUND ); + bSetBrush = true; + } + + if( const SvxBoxItem* pItem = pItemSet2->GetItemIfSet( RES_BOX, false ) ) + { + // set a border + aBoxItem.reset(pItem->Clone()); + pItemSet2->ClearItem( RES_BOX ); + bSetBox = true; + } + + if( const SvxFrameDirectionItem* pItem = pItemSet2->GetItemIfSet( RES_FRAMEDIR, false ) ) + { + // set a frame + aFrameDirItem.reset(pItem->Clone()); + pItemSet2->ClearItem( RES_FRAMEDIR ); + bSetFrameDir = true; + } + } + + if( !(bSetBrush || bSetBox || bSetFrameDir) ) + return; + + static sal_uInt16 aPoolIds[] = { RES_POOLPAGE_HTML, RES_POOLPAGE_FIRST, + RES_POOLPAGE_LEFT, RES_POOLPAGE_RIGHT }; + for(sal_uInt16 i : aPoolIds) + { + const SwPageDesc *pPageDesc = GetPageDesc( i, false ); + if( pPageDesc ) + { + SwPageDesc aNewPageDesc( *pPageDesc ); + SwFrameFormat &rMaster = aNewPageDesc.GetMaster(); + if( bSetBrush ) + rMaster.SetFormatAttr( *aBrushItem ); + if( bSetBox ) + rMaster.SetFormatAttr( *aBoxItem ); + if( bSetFrameDir ) + rMaster.SetFormatAttr( *aFrameDirItem ); + + ChgPageDesc( pPageDesc, aNewPageDesc ); + } + } +} + +// Feature: PrintExt +void SwCSS1Parser::SetPageDescAttrs( const SwPageDesc *pPageDesc, + SfxItemSet& rItemSet, + const SvxCSS1PropertyInfo& rPropInfo ) +{ + if( !pPageDesc ) + return; + + SwPageDesc aNewPageDesc( *pPageDesc ); + SwFrameFormat &rMaster = aNewPageDesc.GetMaster(); + const SfxItemSet& rPageItemSet = rMaster.GetAttrSet(); + bool bChanged = false; + + // left, right border and first line indentation + ::std::optional<SvxLRSpaceItem> oLRSpace; + assert(!rItemSet.GetItemIfSet(RES_LR_SPACE,false)); + if (rPropInfo.m_bLeftMargin) + { + // note: parser never creates SvxLeftMarginItem! must be converted + if (SvxTextLeftMarginItem const*const pLeft = rItemSet.GetItemIfSet(RES_MARGIN_TEXTLEFT, false)) + { + if (!oLRSpace) + { + if (const SvxLRSpaceItem* pPageItem = rPageItemSet.GetItemIfSet(RES_LR_SPACE)) + { + oLRSpace.emplace(*pPageItem); + } + else + { + oLRSpace.emplace(RES_LR_SPACE); + } + } + oLRSpace->SetLeft(pLeft->GetTextLeft()); + } + } + if (rPropInfo.m_bRightMargin) + { + // note: parser never creates SvxLeftMarginItem! must be converted + if (SvxRightMarginItem const*const pRight = rItemSet.GetItemIfSet(RES_MARGIN_RIGHT, false)) + { + if (!oLRSpace) + { + if (const SvxLRSpaceItem* pPageItem = rPageItemSet.GetItemIfSet(RES_LR_SPACE)) + { + oLRSpace.emplace(*pPageItem); + } + else + { + oLRSpace.emplace(RES_LR_SPACE); + } + } + oLRSpace->SetRight(pRight->GetRight()); + } + } + if (oLRSpace) + { + rMaster.SetFormatAttr(*oLRSpace); + bChanged = true; + } + + // top and bottom border + const SvxULSpaceItem *pULItem; + if( (rPropInfo.m_bTopMargin || rPropInfo.m_bBottomMargin) && + (pULItem = rItemSet.GetItemIfSet(RES_UL_SPACE,false)) ) + { + const SvxULSpaceItem* pPageItem; + if( (!rPropInfo.m_bTopMargin || !rPropInfo.m_bBottomMargin) && + (pPageItem = rPageItemSet.GetItemIfSet(RES_UL_SPACE) ) ) + { + SvxULSpaceItem aULItem( *pPageItem ); + if( rPropInfo.m_bTopMargin ) + aULItem.SetUpper( pULItem->GetUpper() ); + if( rPropInfo.m_bBottomMargin ) + aULItem.SetLower( pULItem->GetLower() ); + + rMaster.SetFormatAttr( aULItem ); + } + else + { + rMaster.SetFormatAttr( *pULItem ); + } + bChanged = true; + } + + // the size + if( rPropInfo.m_eSizeType != SVX_CSS1_STYPE_NONE ) + { + if( rPropInfo.m_eSizeType == SVX_CSS1_STYPE_TWIP ) + { + rMaster.SetFormatAttr( SwFormatFrameSize( SwFrameSize::Fixed, rPropInfo.m_nWidth, + rPropInfo.m_nHeight ) ); + bChanged = true; + } + else + { + // With "size: auto|portrait|landscape" the current size + // of the style remains. If "landscape" and "portrait" then + // the landscape flag will be set and maybe the width/height + // are swapped. + SwFormatFrameSize aFrameSz( rMaster.GetFrameSize() ); + bool bLandscape = aNewPageDesc.GetLandscape(); + if( ( bLandscape && + rPropInfo.m_eSizeType == SVX_CSS1_STYPE_PORTRAIT ) || + ( !bLandscape && + rPropInfo.m_eSizeType == SVX_CSS1_STYPE_LANDSCAPE ) ) + { + SwTwips nTmp = aFrameSz.GetHeight(); + aFrameSz.SetHeight( aFrameSz.GetWidth() ); + aFrameSz.SetWidth( nTmp ); + rMaster.SetFormatAttr( aFrameSz ); + aNewPageDesc.SetLandscape( !bLandscape ); + bChanged = true; + } + } + } + + // Is that possible? + if( const SvxBrushItem* pItem = rItemSet.GetItemIfSet( RES_BACKGROUND, false ) ) + { + // set a background + rMaster.SetFormatAttr( *pItem ); + rItemSet.ClearItem( RES_BACKGROUND ); + bChanged = true; + } + + if( bChanged ) + ChgPageDesc( pPageDesc, aNewPageDesc ); +} + +std::unique_ptr<SvxBrushItem> SwCSS1Parser::makePageDescBackground() const +{ + return m_pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool( RES_POOLPAGE_HTML, false ) + ->GetMaster().makeBackgroundBrushItem(); +} + +Css1ScriptFlags SwCSS1Parser::GetScriptFromClass( OUString& rClass, + bool bSubClassOnly ) +{ + Css1ScriptFlags nScriptFlags = Css1ScriptFlags::AllMask; + sal_Int32 nLen = rClass.getLength(); + sal_Int32 nPos = nLen > 4 ? rClass.lastIndexOf( '-' ) : -1; + + if( nPos == -1 ) + { + if( bSubClassOnly ) + return nScriptFlags; + nPos = 0; + } + else + { + nPos++; + nLen = nLen - nPos; + } + + switch( nLen ) + { + case 3: + if( rClass.matchIgnoreAsciiCase( "cjk", nPos ) ) + { + nScriptFlags = Css1ScriptFlags::CJK; + } + else if( rClass.matchIgnoreAsciiCase( "ctl", nPos ) ) + { + nScriptFlags = Css1ScriptFlags::CTL; + } + break; + case 7: + if( rClass.matchIgnoreAsciiCase( "western", nPos ) ) + { + nScriptFlags = Css1ScriptFlags::Western; + } + break; + } + if( Css1ScriptFlags::AllMask != nScriptFlags ) + { + if( nPos ) + { + rClass = rClass.copy( 0, nPos-1 ); + } + else + { + rClass.clear(); + } + } + + return nScriptFlags; +} + +static CSS1SelectorType GetTokenAndClass( const CSS1Selector *pSelector, + OUString& rToken, OUString& rClass, + Css1ScriptFlags& rScriptFlags ) +{ + rToken = pSelector->GetString(); + rClass.clear(); + rScriptFlags = Css1ScriptFlags::AllMask; + + CSS1SelectorType eType = pSelector->GetType(); + if( CSS1_SELTYPE_ELEM_CLASS==eType ) + { + sal_Int32 nPos = rToken.indexOf( '.' ); + OSL_ENSURE( nPos >= 0, "No dot in Class-Selector???" ); + if( nPos >= 0 ) + { + rClass = rToken.copy( nPos+1 ); + rToken = rToken.copy( 0, nPos ); + + rScriptFlags = SwCSS1Parser::GetScriptFromClass( rClass, false ); + if( rClass.isEmpty() ) + eType = CSS1_SELTYPE_ELEMENT; + } + } + + rToken = rToken.toAsciiLowerCase(); + return eType; +} + +static void RemoveScriptItems( SfxItemSet& rItemSet, Css1ScriptFlags nScript, + const SfxItemSet *pParentItemSet = nullptr ) +{ + static const sal_uInt16 aWhichIds[3][5] = + { + { RES_CHRATR_FONT, RES_CHRATR_FONTSIZE, RES_CHRATR_LANGUAGE, + RES_CHRATR_POSTURE, RES_CHRATR_WEIGHT }, + { RES_CHRATR_CJK_FONT, RES_CHRATR_CJK_FONTSIZE, RES_CHRATR_CJK_LANGUAGE, + RES_CHRATR_CJK_POSTURE, RES_CHRATR_CJK_WEIGHT }, + { RES_CHRATR_CTL_FONT, RES_CHRATR_CTL_FONTSIZE, RES_CHRATR_CTL_LANGUAGE, + RES_CHRATR_CTL_POSTURE, RES_CHRATR_CTL_WEIGHT } + }; + + bool aClearItems[3] = { false, false, false }; + switch( nScript ) + { + case Css1ScriptFlags::Western: + aClearItems[1] = aClearItems[2] = true; + break; + case Css1ScriptFlags::CJK: + aClearItems[0] = aClearItems[2] = true; + break; + case Css1ScriptFlags::CTL: + aClearItems[0] = aClearItems[1] = true; + break; + case Css1ScriptFlags::AllMask: + break; + default: + OSL_ENSURE( aClearItems[0], "unknown script type" ); + break; + } + + for( size_t j=0; j < SAL_N_ELEMENTS(aWhichIds); ++j ) + { + for( size_t i=0; i < SAL_N_ELEMENTS(aWhichIds[0]); ++i ) + { + sal_uInt16 nWhich = aWhichIds[j][i]; + const SfxPoolItem *pItem; + if( aClearItems[j] || + (pParentItemSet && + SfxItemState::SET == rItemSet.GetItemState( nWhich, false, &pItem ) && + (0==i ? swhtml_css1atr_equalFontItems( *pItem, pParentItemSet->Get(nWhich ) ) + : *pItem == pParentItemSet->Get(nWhich ) ) ) ) + { + rItemSet.ClearItem( nWhich ); + } + } + } +} + +void SwCSS1Parser::StyleParsed( const CSS1Selector *pSelector, + SfxItemSet& rItemSet, + SvxCSS1PropertyInfo& rPropInfo ) +{ + if( !m_bIsNewDoc ) + return; + + CSS1SelectorType eSelType = pSelector->GetType(); + const CSS1Selector *pNext = pSelector->GetNext(); + + if( CSS1_SELTYPE_ID==eSelType && !pNext ) + { + InsertId( pSelector->GetString(), rItemSet, rPropInfo ); + } + else if( CSS1_SELTYPE_CLASS==eSelType && !pNext ) + { + OUString aClass( pSelector->GetString() ); + Css1ScriptFlags nScript = GetScriptFromClass( aClass ); + if( Css1ScriptFlags::AllMask != nScript ) + { + SfxItemSet aScriptItemSet( rItemSet ); + RemoveScriptItems( aScriptItemSet, nScript ); + InsertClass( aClass, aScriptItemSet, rPropInfo ); + } + else + { + InsertClass( aClass, rItemSet, rPropInfo ); + } + } + else if( CSS1_SELTYPE_PAGE==eSelType ) + { + if( !pNext || + (CSS1_SELTYPE_PSEUDO == pNext->GetType() && + (pNext->GetString().equalsIgnoreAsciiCase( "left" ) || + pNext->GetString().equalsIgnoreAsciiCase( "right" ) || + pNext->GetString().equalsIgnoreAsciiCase( "first" ) ) ) ) + { + OUString aName; + if( pNext ) + aName = pNext->GetString(); + InsertPage( aName, + pNext != nullptr, + rItemSet, rPropInfo ); + } + } + + if( CSS1_SELTYPE_ELEMENT != eSelType && + CSS1_SELTYPE_ELEM_CLASS != eSelType) + return; + + // get token and class of selector + OUString aToken2; + OUString aClass; + Css1ScriptFlags nScript; + eSelType = GetTokenAndClass( pSelector, aToken2, aClass, nScript ); + HtmlTokenId nToken2 = GetHTMLToken( aToken2 ); + + // and also some information of the next element + CSS1SelectorType eNextType = pNext ? pNext->GetType() + : CSS1_SELTYPE_ELEMENT; + + // first some special cases + if( CSS1_SELTYPE_ELEMENT==eSelType ) + { + switch( nToken2 ) + { + case HtmlTokenId::ANCHOR_ON: + if( !pNext ) + { + InsertTag( aToken2, rItemSet, rPropInfo ); + return; + } + else if (CSS1_SELTYPE_PSEUDO == eNextType) + { + // maybe A:visited or A:link + + OUString aPseudo( pNext->GetString() ); + aPseudo = aPseudo.toAsciiLowerCase(); + bool bInsert = false; + switch( aPseudo[0] ) + { + case 'l': + if( aPseudo == "link" ) + { + bInsert = true; + } + break; + case 'v': + if( aPseudo == "visited" ) + { + bInsert = true; + } + break; + } + if( bInsert ) + { + OUString sTmp = aToken2 + ":" + aPseudo; + if( Css1ScriptFlags::AllMask != nScript ) + { + SfxItemSet aScriptItemSet( rItemSet ); + RemoveScriptItems( aScriptItemSet, nScript ); + InsertTag( sTmp, aScriptItemSet, rPropInfo ); + } + else + { + InsertTag( sTmp, rItemSet, rPropInfo ); + } + return; + } + } + break; + case HtmlTokenId::BODY_ON: + if( !pNext ) + { + // BODY + + // We must test the background before setting, because + // in SetPageDescAttrs it will be deleted. + if( const SvxBrushItem *pBrushItem = rItemSet.GetItemIfSet(RES_BACKGROUND,false) ) + { + /// Body has a background color, if it is not "no fill"/"auto fill" + if( pBrushItem->GetColor() != COL_TRANSPARENT ) + m_bBodyBGColorSet = true; + if( GPOS_NONE != pBrushItem->GetGraphicPos() ) + m_bBodyBackgroundSet = true; + } + + // Border and Padding + rPropInfo.SetBoxItem( rItemSet, MIN_BORDER_DIST ); + + // Some attributes must be set at the style, the ones which + // aren't inherited + SetPageDescAttrs( nullptr, &rItemSet ); + + // all remaining options can be set at the default style, + // then they're the default + if( SfxItemState::SET==rItemSet.GetItemState(RES_CHRATR_COLOR,false) ) + m_bBodyTextSet = true; + SetTextCollAttrs( + GetTextCollFromPool( RES_POOLCOLL_STANDARD ), + rItemSet, rPropInfo, this ); + + return; + } + break; + default: break; + } + } + else if( CSS1_SELTYPE_ELEM_CLASS==eSelType && HtmlTokenId::ANCHOR_ON==nToken2 && + !pNext && aClass.getLength() >= 9 && + ('s' == aClass[0] || 'S' == aClass[0]) ) + { + sal_uInt16 nPoolFormatId = 0; + if( aClass.equalsIgnoreAsciiCase(OOO_STRING_SVTOOLS_HTML_sdendnote_sym) ) + nPoolFormatId = RES_POOLCHR_ENDNOTE; + else if( aClass.equalsIgnoreAsciiCase(OOO_STRING_SVTOOLS_HTML_sdfootnote_sym) ) + nPoolFormatId = RES_POOLCHR_FOOTNOTE; + if( nPoolFormatId ) + { + if( Css1ScriptFlags::AllMask == nScript ) + { + SetCharFormatAttrs( GetCharFormatFromPool(nPoolFormatId), rItemSet ); + } + else + { + SfxItemSet aScriptItemSet( rItemSet ); + RemoveScriptItems( aScriptItemSet, nScript ); + SetCharFormatAttrs( GetCharFormatFromPool(nPoolFormatId), + aScriptItemSet); + } + return; + } + } + + // Now the selectors are processed which belong to a paragraph style + sal_uInt16 nPoolCollId = 0; + switch( nToken2 ) + { + case HtmlTokenId::HEAD1_ON: + nPoolCollId = RES_POOLCOLL_HEADLINE1; + break; + case HtmlTokenId::HEAD2_ON: + nPoolCollId = RES_POOLCOLL_HEADLINE2; + break; + case HtmlTokenId::HEAD3_ON: + nPoolCollId = RES_POOLCOLL_HEADLINE3; + break; + case HtmlTokenId::HEAD4_ON: + nPoolCollId = RES_POOLCOLL_HEADLINE4; + break; + case HtmlTokenId::HEAD5_ON: + nPoolCollId = RES_POOLCOLL_HEADLINE5; + break; + case HtmlTokenId::HEAD6_ON: + nPoolCollId = RES_POOLCOLL_HEADLINE6; + break; + case HtmlTokenId::PARABREAK_ON: + if( aClass.getLength() >= 9 && + ('s' == aClass[0] || 'S' == aClass[0]) ) + { + if( aClass.equalsIgnoreAsciiCase(OOO_STRING_SVTOOLS_HTML_sdendnote) ) + nPoolCollId = RES_POOLCOLL_ENDNOTE; + else if( aClass.equalsIgnoreAsciiCase(OOO_STRING_SVTOOLS_HTML_sdfootnote) ) + nPoolCollId = RES_POOLCOLL_FOOTNOTE; + + if( nPoolCollId ) + aClass.clear(); + else + nPoolCollId = RES_POOLCOLL_TEXT; + } + else + { + nPoolCollId = RES_POOLCOLL_TEXT; + } + break; + case HtmlTokenId::ADDRESS_ON: + nPoolCollId = RES_POOLCOLL_SEND_ADDRESS; + break; + case HtmlTokenId::BLOCKQUOTE_ON: + nPoolCollId = RES_POOLCOLL_HTML_BLOCKQUOTE; + break; + case HtmlTokenId::DT_ON: + nPoolCollId = RES_POOLCOLL_HTML_DT; + break; + case HtmlTokenId::DD_ON: + nPoolCollId = RES_POOLCOLL_HTML_DD; + break; + case HtmlTokenId::PREFORMTXT_ON: + nPoolCollId = RES_POOLCOLL_HTML_PRE; + break; + case HtmlTokenId::TABLEHEADER_ON: + case HtmlTokenId::TABLEDATA_ON: + if( CSS1_SELTYPE_ELEMENT==eSelType && !pNext ) + { + InsertTag( aToken2, rItemSet, rPropInfo ); + return; + } + else if( CSS1_SELTYPE_ELEMENT==eSelType && pNext && + (CSS1_SELTYPE_ELEMENT==eNextType || + CSS1_SELTYPE_ELEM_CLASS==eNextType) ) + { + // not TH and TD, but TH P and TD P + OUString aSubToken, aSubClass; + GetTokenAndClass( pNext, aSubToken, aSubClass, nScript ); + if( HtmlTokenId::PARABREAK_ON == GetHTMLToken( aSubToken ) ) + { + aClass = aSubClass; + pNext = pNext->GetNext(); + eNextType = pNext ? pNext->GetType() : CSS1_SELTYPE_ELEMENT; + + if( !aClass.isEmpty() || pNext ) + { + nPoolCollId = static_cast< sal_uInt16 >( + HtmlTokenId::TABLEHEADER_ON == nToken2 ? RES_POOLCOLL_TABLE_HDLN + : RES_POOLCOLL_TABLE ); + } + else + { + OUString sTmp = aToken2 + " " OOO_STRING_SVTOOLS_HTML_parabreak; + + if( Css1ScriptFlags::AllMask == nScript ) + { + InsertTag( sTmp, rItemSet, rPropInfo ); + } + else + { + SfxItemSet aScriptItemSet( rItemSet ); + RemoveScriptItems( aScriptItemSet, nScript ); + InsertTag( sTmp, aScriptItemSet, rPropInfo ); + } + + return; + } + } + } + break; + + default: + ; + } + + if( nPoolCollId ) + { + if( !pNext || + (CSS1_SELTYPE_PSEUDO==eNextType && + pNext->GetString().equalsIgnoreAsciiCase( "first-letter" ) && + SvxAdjust::Left == rPropInfo.m_eFloat) ) + { + // either not a composed selector or a X:first-line { float: left; ... } + + // search resp. create the style + SwTextFormatColl* pColl = GetTextFormatColl(nPoolCollId, OUString()); + SwTextFormatColl* pParentColl = nullptr; + if( !aClass.isEmpty() ) + { + OUString aName( pColl->GetName() ); + AddClassName( aName, aClass ); + + pParentColl = pColl; + pColl = m_pDoc->FindTextFormatCollByName( aName ); + if( !pColl ) + pColl = m_pDoc->MakeTextFormatColl( aName, pParentColl ); + } + if( !pNext ) + { + // set only the attributes at the style + const SvxBoxItem *pBoxItem = + pColl->GetAttrSet().GetItemIfSet(RES_BOX); + rPropInfo.SetBoxItem( rItemSet, MIN_BORDER_DIST, pBoxItem ); + if( Css1ScriptFlags::AllMask == nScript && !pParentColl ) + { + SetTextCollAttrs( pColl, rItemSet, rPropInfo, this ); + } + else + { + SfxItemSet aScriptItemSet( rItemSet ); + RemoveScriptItems( aScriptItemSet, nScript, + pParentColl ? &pParentColl->GetAttrSet() : nullptr ); + SetTextCollAttrs( pColl, aScriptItemSet, rPropInfo, this ); + } + } + else + { + // create a DropCap attribute + SwFormatDrop aDrop( pColl->GetDrop() ); + aDrop.GetChars() = 1; + + // set the attributes of the DropCap attribute + if( Css1ScriptFlags::AllMask == nScript ) + { + OUString sName(pColl->GetName()); + FillDropCap( aDrop, rItemSet, &sName ); + } + else + { + SfxItemSet aScriptItemSet( rItemSet ); + if( Css1ScriptFlags::Western != nScript ) + { + aScriptItemSet.ClearItem( RES_CHRATR_FONT ); + aScriptItemSet.ClearItem( RES_CHRATR_LANGUAGE ); + aScriptItemSet.ClearItem( RES_CHRATR_POSTURE ); + aScriptItemSet.ClearItem( RES_CHRATR_WEIGHT ); + } + if( Css1ScriptFlags::CJK != nScript ) + { + aScriptItemSet.ClearItem( RES_CHRATR_CJK_FONT ); + aScriptItemSet.ClearItem( RES_CHRATR_CJK_LANGUAGE ); + aScriptItemSet.ClearItem( RES_CHRATR_CJK_POSTURE ); + aScriptItemSet.ClearItem( RES_CHRATR_CJK_WEIGHT ); + } + if( Css1ScriptFlags::CTL != nScript ) + { + aScriptItemSet.ClearItem( RES_CHRATR_CTL_FONT ); + aScriptItemSet.ClearItem( RES_CHRATR_CTL_LANGUAGE ); + aScriptItemSet.ClearItem( RES_CHRATR_CTL_POSTURE ); + aScriptItemSet.ClearItem( RES_CHRATR_CTL_WEIGHT ); + } + OUString sName(pColl->GetName()); + FillDropCap( aDrop, aScriptItemSet, &sName ); + } + + // Only set the attribute if "float: left" is specified and + // the Initial is over several lines. Otherwise the maybe + // created character style will be later searched and set + // via name. + if( aDrop.GetLines() > 1 && + (SvxAdjust::Left == rPropInfo.m_eFloat || + Css1ScriptFlags::AllMask == nScript) ) + { + pColl->SetFormatAttr( aDrop ); + } + } + } + + return; + } + + // Now the selectors are processed which are belonging to the character + // template. There are no composed ones here. + if( pNext ) + return; + + SwCharFormat* pCFormat = GetChrFormat(nToken2, OUString()); + if( !pCFormat ) + return; + + SwCharFormat *pParentCFormat = nullptr; + if( !aClass.isEmpty() ) + { + OUString aName( pCFormat->GetName() ); + AddClassName( aName, aClass ); + pParentCFormat = pCFormat; + + pCFormat = m_pDoc->FindCharFormatByName( aName ); + if( !pCFormat ) + { + pCFormat = m_pDoc->MakeCharFormat( aName, pParentCFormat ); + pCFormat->SetAuto(false); + } + } + + if( Css1ScriptFlags::AllMask == nScript && !pParentCFormat ) + { + SetCharFormatAttrs( pCFormat, rItemSet ); + } + else + { + SfxItemSet aScriptItemSet( rItemSet ); + RemoveScriptItems( aScriptItemSet, nScript, + pParentCFormat ? &pParentCFormat->GetAttrSet() : nullptr ); + SetCharFormatAttrs( pCFormat, aScriptItemSet ); + } +} + +sal_uInt32 SwCSS1Parser::GetFontHeight( sal_uInt16 nSize ) const +{ + return m_aFontHeights[ std::min<sal_uInt16>(nSize,6) ]; +} + +const FontList *SwCSS1Parser::GetFontList() const +{ + const FontList *pFList = nullptr; + SwDocShell *pDocSh = m_pDoc->GetDocShell(); + if( pDocSh ) + { + const SvxFontListItem *pFListItem = + static_cast<const SvxFontListItem *>(pDocSh->GetItem(SID_ATTR_CHAR_FONTLIST)); + if( pFListItem ) + pFList = pFListItem->GetFontList(); + } + + return pFList; +} + +SwCharFormat* SwCSS1Parser::GetChrFormat( HtmlTokenId nToken2, const OUString& rClass ) const +{ + // search the corresponding style + sal_uInt16 nPoolId = 0; + const char* sName = nullptr; + switch( nToken2 ) + { + case HtmlTokenId::EMPHASIS_ON: nPoolId = RES_POOLCHR_HTML_EMPHASIS; break; + case HtmlTokenId::CITATION_ON: nPoolId = RES_POOLCHR_HTML_CITATION; break; + case HtmlTokenId::STRONG_ON: nPoolId = RES_POOLCHR_HTML_STRONG; break; + case HtmlTokenId::CODE_ON: nPoolId = RES_POOLCHR_HTML_CODE; break; + case HtmlTokenId::SAMPLE_ON: nPoolId = RES_POOLCHR_HTML_SAMPLE; break; + case HtmlTokenId::KEYBOARD_ON: nPoolId = RES_POOLCHR_HTML_KEYBOARD; break; + case HtmlTokenId::VARIABLE_ON: nPoolId = RES_POOLCHR_HTML_VARIABLE; break; + case HtmlTokenId::DEFINSTANCE_ON: nPoolId = RES_POOLCHR_HTML_DEFINSTANCE; break; + case HtmlTokenId::TELETYPE_ON: nPoolId = RES_POOLCHR_HTML_TELETYPE; break; + + case HtmlTokenId::SHORTQUOTE_ON: sName = OOO_STRING_SVTOOLS_HTML_shortquote; break; + case HtmlTokenId::LANGUAGE_ON: sName = OOO_STRING_SVTOOLS_HTML_language; break; + case HtmlTokenId::AUTHOR_ON: sName = OOO_STRING_SVTOOLS_HTML_author; break; + case HtmlTokenId::PERSON_ON: sName = OOO_STRING_SVTOOLS_HTML_person; break; + case HtmlTokenId::ACRONYM_ON: sName = OOO_STRING_SVTOOLS_HTML_acronym; break; + case HtmlTokenId::ABBREVIATION_ON: sName = OOO_STRING_SVTOOLS_HTML_abbreviation; break; + case HtmlTokenId::INSERTEDTEXT_ON: sName = OOO_STRING_SVTOOLS_HTML_insertedtext; break; + case HtmlTokenId::DELETEDTEXT_ON: sName = OOO_STRING_SVTOOLS_HTML_deletedtext; break; + default: break; + } + + // search or create the style (only possible with name) + if( !nPoolId && !sName ) + return nullptr; + + // search or create style (without class) + SwCharFormat *pCFormat = nullptr; + if( nPoolId ) + { + pCFormat = GetCharFormatFromPool( nPoolId ); + } + else + { + OUString sCName( OUString::createFromAscii(sName) ); + pCFormat = m_pDoc->FindCharFormatByName( sCName ); + if( !pCFormat ) + { + pCFormat = m_pDoc->MakeCharFormat( sCName, m_pDoc->GetDfltCharFormat() ); + pCFormat->SetAuto(false); + } + } + + OSL_ENSURE( pCFormat, "No character style???" ); + + // If a class exists, then search for the class style but don't + // create one. + OUString aClass( rClass ); + GetScriptFromClass( aClass, false ); + if( !aClass.isEmpty() ) + { + OUString aTmp( pCFormat->GetName() ); + AddClassName( aTmp, aClass ); + SwCharFormat *pClassCFormat = m_pDoc->FindCharFormatByName( aTmp ); + if( pClassCFormat ) + { + pCFormat = pClassCFormat; + } + else + { + const SvxCSS1MapEntry *pClass = GetClass( aClass ); + if( pClass ) + { + pCFormat = m_pDoc->MakeCharFormat( aTmp, pCFormat ); + pCFormat->SetAuto(false); + SfxItemSet aItemSet( pClass->GetItemSet() ); + SetCharFormatAttrs( pCFormat, aItemSet ); + } + } + } + + return pCFormat; +} + +SwTextFormatColl *SwCSS1Parser::GetTextCollFromPool( sal_uInt16 nPoolId ) const +{ + const SwTextFormatColls::size_type nOldArrLen = m_pDoc->GetTextFormatColls()->size(); + + SwTextFormatColl *pColl = m_pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( nPoolId, false ); + + if( m_bIsNewDoc ) + { + const SwTextFormatColls::size_type nArrLen = m_pDoc->GetTextFormatColls()->size(); + for( SwTextFormatColls::size_type i=nOldArrLen; i<nArrLen; ++i ) + lcl_swcss1_setEncoding( *(*m_pDoc->GetTextFormatColls())[i], + GetDfltEncoding() ); + } + + return pColl; +} + +SwCharFormat *SwCSS1Parser::GetCharFormatFromPool( sal_uInt16 nPoolId ) const +{ + const SwCharFormats::size_type nOldArrLen = m_pDoc->GetCharFormats()->size(); + + SwCharFormat *pCharFormat = m_pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool( nPoolId ); + + if( m_bIsNewDoc ) + { + const SwCharFormats::size_type nArrLen = m_pDoc->GetCharFormats()->size(); + + for( SwCharFormats::size_type i=nOldArrLen; i<nArrLen; i++ ) + lcl_swcss1_setEncoding( *(*m_pDoc->GetCharFormats())[i], + GetDfltEncoding() ); + } + + return pCharFormat; +} + +SwTextFormatColl *SwCSS1Parser::GetTextFormatColl( sal_uInt16 nTextColl, + const OUString& rClass ) +{ + SwTextFormatColl* pColl = nullptr; + + OUString aClass( rClass ); + GetScriptFromClass( aClass, false ); + if( RES_POOLCOLL_TEXT == nTextColl && aClass.getLength() >= 9 && + ('s' == aClass[0] || 'S' == aClass[0] ) ) + { + if( aClass.equalsIgnoreAsciiCase(OOO_STRING_SVTOOLS_HTML_sdendnote) ) + { + nTextColl = RES_POOLCOLL_ENDNOTE; + aClass.clear(); + } + else if( aClass.equalsIgnoreAsciiCase(OOO_STRING_SVTOOLS_HTML_sdfootnote) ) + { + nTextColl = RES_POOLCOLL_FOOTNOTE; + aClass.clear(); + } + } + + if( USER_FMT & nTextColl ) // one created by Reader + { + OSL_ENSURE( false, "Where does the user style comes from?" ); + pColl = GetTextCollFromPool( RES_POOLCOLL_STANDARD ); + } + else + { + pColl = GetTextCollFromPool( nTextColl ); + } + + OSL_ENSURE( pColl, "No paragraph style???" ); + if( !aClass.isEmpty() ) + { + OUString aTmp( pColl->GetName() ); + AddClassName( aTmp, aClass ); + SwTextFormatColl* pClassColl = m_pDoc->FindTextFormatCollByName( aTmp ); + + if( !pClassColl && + (nTextColl==RES_POOLCOLL_TABLE || + nTextColl==RES_POOLCOLL_TABLE_HDLN) ) + { + // In this case there was a <TD><P CLASS=foo>, but no TD.foo + // style was found. The we must use P.foo, if available. + SwTextFormatColl* pCollText = + GetTextCollFromPool( RES_POOLCOLL_TEXT ); + aTmp = pCollText->GetName(); + AddClassName( aTmp, aClass ); + pClassColl = m_pDoc->FindTextFormatCollByName( aTmp ); + } + + if( pClassColl ) + { + pColl = pClassColl; + } + else + { + const SvxCSS1MapEntry *pClass = GetClass( aClass ); + if( pClass ) + { + pColl = m_pDoc->MakeTextFormatColl( aTmp, pColl ); + SfxItemSet aItemSet( pClass->GetItemSet() ); + SvxCSS1PropertyInfo aPropInfo( pClass->GetPropertyInfo() ); + aPropInfo.SetBoxItem( aItemSet, MIN_BORDER_DIST ); + bool bPositioned = MayBePositioned( pClass->GetPropertyInfo() ); + if( bPositioned ) + aItemSet.ClearItem( RES_BACKGROUND ); + SetTextCollAttrs( pColl, aItemSet, aPropInfo, + this ); + } + } + + } + + if( pColl ) + lcl_swcss1_setEncoding( *pColl, GetDfltEncoding() ); + + return pColl; +} + +SwPageDesc *SwCSS1Parser::GetMasterPageDesc() +{ + return m_pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool( RES_POOLPAGE_HTML, false ); +} + +static SwPageDesc *FindPageDesc(SwDoc *pDoc, sal_uInt16 nPoolId) +{ + size_t nPageDescs = pDoc->GetPageDescCnt(); + size_t nPage; + for (nPage=0; nPage < nPageDescs && + pDoc->GetPageDesc(nPage).GetPoolFormatId() != nPoolId; ++nPage) + ; + + return nPage < nPageDescs ? &pDoc->GetPageDesc(nPage) : nullptr; +} + +const SwPageDesc *SwCSS1Parser::GetPageDesc( sal_uInt16 nPoolId, bool bCreate ) +{ + if( RES_POOLPAGE_HTML == nPoolId ) + return m_pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool( RES_POOLPAGE_HTML, false ); + + const SwPageDesc *pPageDesc = FindPageDesc(m_pDoc, nPoolId); + if( !pPageDesc && bCreate ) + { + if (m_rHTMLParser.IsReadingHeaderOrFooter()) + { // (there should be only one definition of header/footer in HTML) + SAL_WARN("sw.html", "no creating PageDesc while reading header/footer"); + return nullptr; + } + + // The first page is created from the right page, if there is one. + SwPageDesc *pMasterPageDesc = nullptr; + if( RES_POOLPAGE_FIRST == nPoolId ) + pMasterPageDesc = FindPageDesc(m_pDoc, RES_POOLPAGE_RIGHT); + if( !pMasterPageDesc ) + pMasterPageDesc = m_pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool( RES_POOLPAGE_HTML, false ); + + // The new page style is created by copying from master + SwPageDesc *pNewPageDesc = m_pDoc-> + getIDocumentStylePoolAccess().GetPageDescFromPool( nPoolId, false ); + + // therefore we also need the number of the new style + OSL_ENSURE(pNewPageDesc == FindPageDesc(m_pDoc, nPoolId), "page style not found"); + + m_pDoc->CopyPageDesc( *pMasterPageDesc, *pNewPageDesc, false ); + + // Modify the styles for their new purpose. + const SwPageDesc *pFollow = nullptr; + bool bSetFollowFollow = false; + switch( nPoolId ) + { + case RES_POOLPAGE_FIRST: + // If there is already a left page, then is it the follow-up + // style, else it is the HTML style. + pFollow = GetLeftPageDesc(); + if( !pFollow ) + pFollow = pMasterPageDesc; + break; + + case RES_POOLPAGE_RIGHT: + // If the left style is already created, nothing will happen here. + // Otherwise the left style is created and ensures the link with + // the right style. + GetLeftPageDesc( true ); + break; + + case RES_POOLPAGE_LEFT: + // The right style is created if none exists. No links are created. + // If there is already a first page style, then the left style becomes + // follow-up style of the first page. + pFollow = GetRightPageDesc( true ); + bSetFollowFollow = true; + { + const SwPageDesc *pFirstPageDesc = GetFirstPageDesc(); + if( pFirstPageDesc ) + { + SwPageDesc aNewFirstPageDesc( *pFirstPageDesc ); + aNewFirstPageDesc.SetFollow( pNewPageDesc ); + ChgPageDesc( pFirstPageDesc, aNewFirstPageDesc ); + } + } + break; + } + + if( pFollow ) + { + SwPageDesc aNewPageDesc( *pNewPageDesc ); + aNewPageDesc.SetFollow( pFollow ); + ChgPageDesc( pNewPageDesc, aNewPageDesc ); + + if( bSetFollowFollow ) + { + SwPageDesc aNewFollowPageDesc( *pFollow ); + aNewFollowPageDesc.SetFollow( pNewPageDesc ); + ChgPageDesc( pFollow, aNewFollowPageDesc ); + } + } + pPageDesc = pNewPageDesc; + } + + return pPageDesc; +} + +bool SwCSS1Parser::MayBePositioned( const SvxCSS1PropertyInfo& rPropInfo, + bool bAutoWidth ) +{ + if (!rPropInfo.m_bVisible) + { + // Don't create a textframe for this div if it's hidden. + return false; + } + + // abs-pos + // left/top none auto twip perc + + // none Z Z - - + // auto Z Z - - + // twip Z Z S/R - + // perc - - - - + + // - the tag will be positioned absolutely and left/top are both + // present and don't contain a percentage value, or + // - the tag should flow, and + // - a width was specified (needed in both cases) + return ( ( SVX_CSS1_POS_ABSOLUTE == rPropInfo.m_ePosition && + SVX_CSS1_LTYPE_PERCENTAGE != rPropInfo.m_eLeftType && + SVX_CSS1_LTYPE_PERCENTAGE != rPropInfo.m_eTopType && + (SVX_CSS1_LTYPE_TWIP == rPropInfo.m_eLeftType || + SVX_CSS1_LTYPE_TWIP != rPropInfo.m_eTopType) ) || + ( SvxAdjust::End != rPropInfo.m_eFloat ) ) && + ( bAutoWidth || + SVX_CSS1_LTYPE_TWIP == rPropInfo.m_eWidthType || + SVX_CSS1_LTYPE_PERCENTAGE == rPropInfo.m_eWidthType ); +} + +void SwCSS1Parser::AddClassName( OUString& rFormatName, std::u16string_view rClass ) +{ + OSL_ENSURE( !rClass.empty(), "Style class without length?" ); + + rFormatName += OUString::Concat(".") + rClass; +} + +void SwCSS1Parser::FillDropCap( SwFormatDrop& rDrop, + SfxItemSet& rItemSet, + const OUString *pName ) +{ + // the number of lines matches somehow a percentage value + // for the height (what happens with absolute heights???) + sal_uInt8 nLines = rDrop.GetLines(); + if( const SvxFontHeightItem* pFontHeightItem = rItemSet.GetItemIfSet( RES_CHRATR_FONTSIZE, false ) ) + { + sal_uInt16 nProp = pFontHeightItem->GetProp(); + nLines = static_cast<sal_uInt8>((nProp + 50) / 100); + if( nLines < 1 ) + nLines = 1; + else if( nLines > MAX_DROPCAP_LINES ) + nLines = MAX_DROPCAP_LINES; + + // Only when nLines>1, then the attribute also is set. Then + // we don't need the font height in the character style. + if( nLines > 1 ) + { + rItemSet.ClearItem( RES_CHRATR_FONTSIZE ); + rItemSet.ClearItem( RES_CHRATR_CJK_FONTSIZE ); + rItemSet.ClearItem( RES_CHRATR_CTL_FONTSIZE ); + } + } + + // In case of hard attribution (pName==0) we can stop, if the Initial is + // only one line. + if( nLines<=1 ) + return; + + rDrop.GetLines() = nLines; + + // a right border becomes the spacing to text! + if (const SvxRightMarginItem *const pRightMargin = rItemSet.GetItemIfSet(RES_MARGIN_RIGHT, false)) + { + rDrop.GetDistance() = static_cast<sal_uInt16>(pRightMargin->GetRight()); + rItemSet.ClearItem(RES_MARGIN_RIGHT); + } + rItemSet.ClearItem(RES_MARGIN_FIRSTLINE); + rItemSet.ClearItem(RES_MARGIN_TEXTLEFT); + + // for every other attribute create a character style + if( !rItemSet.Count() ) + return; + + SwCharFormat *pCFormat = nullptr; + OUString aName; + if( pName ) + { + aName = *pName + ".FL"; // first letter + pCFormat = m_pDoc->FindCharFormatByName( aName ); + } + else + { + do + { + aName = "first-letter " + OUString::number( static_cast<sal_Int32>(++m_nDropCapCnt) ); + } + while( m_pDoc->FindCharFormatByName(aName) ); + } + + if( !pCFormat ) + { + pCFormat = m_pDoc->MakeCharFormat( aName, m_pDoc->GetDfltCharFormat() ); + pCFormat->SetAuto(false); + } + SetCharFormatAttrs( pCFormat, rItemSet ); + + // The character style needs only be set in the attribute, when + // the attribute also is set. + if( nLines > 1 ) + rDrop.SetCharFormat( pCFormat ); +} + +// specific CSS1 of SwHTMLParsers + +HTMLAttr **SwHTMLParser::GetAttrTabEntry( sal_uInt16 nWhich ) +{ + // find the table entry of the item ... + HTMLAttr **ppAttr = nullptr; + switch( nWhich ) + { + case RES_CHRATR_BLINK: + ppAttr = &m_xAttrTab->pBlink; + break; + case RES_CHRATR_CASEMAP: + ppAttr = &m_xAttrTab->pCaseMap; + break; + case RES_CHRATR_COLOR: + ppAttr = &m_xAttrTab->pFontColor; + break; + case RES_CHRATR_CROSSEDOUT: + ppAttr = &m_xAttrTab->pStrike; + break; + case RES_CHRATR_ESCAPEMENT: + ppAttr = &m_xAttrTab->pEscapement; + break; + case RES_CHRATR_FONT: + ppAttr = &m_xAttrTab->pFont; + break; + case RES_CHRATR_CJK_FONT: + ppAttr = &m_xAttrTab->pFontCJK; + break; + case RES_CHRATR_CTL_FONT: + ppAttr = &m_xAttrTab->pFontCTL; + break; + case RES_CHRATR_FONTSIZE: + ppAttr = &m_xAttrTab->pFontHeight; + break; + case RES_CHRATR_CJK_FONTSIZE: + ppAttr = &m_xAttrTab->pFontHeightCJK; + break; + case RES_CHRATR_CTL_FONTSIZE: + ppAttr = &m_xAttrTab->pFontHeightCTL; + break; + case RES_CHRATR_KERNING: + ppAttr = &m_xAttrTab->pKerning; + break; + case RES_CHRATR_POSTURE: + ppAttr = &m_xAttrTab->pItalic; + break; + case RES_CHRATR_CJK_POSTURE: + ppAttr = &m_xAttrTab->pItalicCJK; + break; + case RES_CHRATR_CTL_POSTURE: + ppAttr = &m_xAttrTab->pItalicCTL; + break; + case RES_CHRATR_UNDERLINE: + ppAttr = &m_xAttrTab->pUnderline; + break; + case RES_CHRATR_WEIGHT: + ppAttr = &m_xAttrTab->pBold; + break; + case RES_CHRATR_CJK_WEIGHT: + ppAttr = &m_xAttrTab->pBoldCJK; + break; + case RES_CHRATR_CTL_WEIGHT: + ppAttr = &m_xAttrTab->pBoldCTL; + break; + case RES_CHRATR_BACKGROUND: + ppAttr = &m_xAttrTab->pCharBrush; + break; + case RES_CHRATR_BOX: + ppAttr = &m_xAttrTab->pCharBox; + break; + + case RES_PARATR_LINESPACING: + ppAttr = &m_xAttrTab->pLineSpacing; + break; + case RES_PARATR_ADJUST: + ppAttr = &m_xAttrTab->pAdjust; + break; + + case RES_MARGIN_FIRSTLINE: + ppAttr = &m_xAttrTab->pFirstLineIndent; + break; + case RES_MARGIN_TEXTLEFT: + ppAttr = &m_xAttrTab->pTextLeftMargin; + break; + case RES_MARGIN_RIGHT: + ppAttr = &m_xAttrTab->pRightMargin; + break; + case RES_UL_SPACE: + ppAttr = &m_xAttrTab->pULSpace; + break; + case RES_BOX: + ppAttr = &m_xAttrTab->pBox; + break; + case RES_BACKGROUND: + ppAttr = &m_xAttrTab->pBrush; + break; + case RES_BREAK: + ppAttr = &m_xAttrTab->pBreak; + break; + case RES_PAGEDESC: + ppAttr = &m_xAttrTab->pPageDesc; + break; + case RES_PARATR_SPLIT: + ppAttr = &m_xAttrTab->pSplit; + break; + case RES_PARATR_WIDOWS: + ppAttr = &m_xAttrTab->pWidows; + break; + case RES_PARATR_ORPHANS: + ppAttr = &m_xAttrTab->pOrphans; + break; + case RES_KEEP: + ppAttr = &m_xAttrTab->pKeep; + break; + + case RES_CHRATR_LANGUAGE: + ppAttr = &m_xAttrTab->pLanguage; + break; + case RES_CHRATR_CJK_LANGUAGE: + ppAttr = &m_xAttrTab->pLanguageCJK; + break; + case RES_CHRATR_CTL_LANGUAGE: + ppAttr = &m_xAttrTab->pLanguageCTL; + break; + + case RES_FRAMEDIR: + ppAttr = &m_xAttrTab->pDirection; + break; + } + + return ppAttr; +} + +void SwHTMLParser::NewStyle() +{ + OUString sType; + + const HTMLOptions& rOptions2 = GetOptions(); + for (size_t i = rOptions2.size(); i; ) + { + const HTMLOption& rOption = rOptions2[--i]; + if( HtmlOptionId::TYPE == rOption.GetToken() ) + sType = rOption.GetString(); + } + + m_bIgnoreRawData = sType.getLength() && + !o3tl::equalsAscii(o3tl::getToken(sType, 0,';'), sCSS_mimetype); +} + +void SwHTMLParser::EndStyle() +{ + m_bIgnoreRawData = false; + + if( !m_aStyleSource.isEmpty() ) + { + m_pCSS1Parser->ParseStyleSheet( m_aStyleSource ); + m_aStyleSource.clear(); + } +} + +bool SwHTMLParser::FileDownload( const OUString& rURL, + OUString& rStr ) +{ + // depose view (because of reschedule) + SwViewShell *pOldVSh = CallEndAction(); + + SfxMedium aDLMedium( rURL, StreamMode::READ | StreamMode::SHARE_DENYWRITE ); + + SvStream* pStream = aDLMedium.GetInStream(); + if( pStream ) + { + SvMemoryStream aStream; + aStream.WriteStream( *pStream ); + + rStr = OUString(static_cast<const char *>(aStream.GetData()), aStream.TellEnd(), + GetSrcEncoding()); + } + + // was aborted? + if( ( m_xDoc->GetDocShell() && m_xDoc->GetDocShell()->IsAbortingImport() ) + || 1 == m_xDoc->getReferenceCount() ) + { + // was the import aborted from SFX? + eState = SvParserState::Error; + pStream = nullptr; + } + + // recreate View + SwViewShell *const pVSh = CallStartAction( pOldVSh ); + OSL_ENSURE( pOldVSh == pVSh, "FileDownload: SwViewShell changed on us" ); + + return pStream!=nullptr; +} + +void SwHTMLParser::InsertLink() +{ + bool bFinishDownload = false; + if( !m_vPendingStack.empty() ) + { + OSL_ENSURE( ShouldFinishFileDownload(), + "Pending-Stack without File-Download?" ); + + m_vPendingStack.pop_back(); + assert( m_vPendingStack.empty() && "Where does the Pending-Stack come from?" ); + + bFinishDownload = true; + } + else + { + OUString sRel, sHRef, sType; + + const HTMLOptions& rOptions2 = GetOptions(); + for (size_t i = rOptions2.size(); i; ) + { + const HTMLOption& rOption = rOptions2[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::REL: + sRel = rOption.GetString(); + break; + case HtmlOptionId::HREF: + sHRef = URIHelper::SmartRel2Abs( INetURLObject( m_sBaseURL ), rOption.GetString(), Link<OUString *, bool>(), false ); + break; + case HtmlOptionId::TYPE: + sType = rOption.GetString(); + break; + default: break; + } + } + + if( !sHRef.isEmpty() && sRel.equalsIgnoreAsciiCase( "STYLESHEET" ) && + ( sType.isEmpty() || + o3tl::equalsAscii(o3tl::getToken(sType, 0,';'), sCSS_mimetype) ) ) + { + if( GetMedium() ) + { + // start download of style source + StartFileDownload(sHRef); + if( IsParserWorking() ) + { + // The style was loaded synchronously and we can call it directly. + bFinishDownload = true; + } + else + { + // The style was load asynchronously and is only available + // on the next continue call. Therefore we must create a + // Pending stack, so that we will return to here. + m_vPendingStack.emplace_back( HtmlTokenId::LINK ); + } + } + else + { + // load file synchronous + OUString sSource; + if( FileDownload( sHRef, sSource ) ) + m_pCSS1Parser->ParseStyleSheet( sSource ); + } + } + } + + if( bFinishDownload ) + { + OUString sSource; + if( FinishFileDownload( sSource ) && !sSource.isEmpty() ) + m_pCSS1Parser->ParseStyleSheet( sSource ); + } +} + +bool SwCSS1Parser::ParseStyleSheet( const OUString& rIn ) +{ + if( !SvxCSS1Parser::ParseStyleSheet( rIn ) ) + return false; + + SwPageDesc *pMasterPageDesc = + m_pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool( RES_POOLPAGE_HTML, false ); + + SvxCSS1MapEntry* pPageEntry = GetPage(OUString(), false); + if( pPageEntry ) + { + // @page (affects all already existing pages) + + SetPageDescAttrs( pMasterPageDesc, pPageEntry->GetItemSet(), + pPageEntry->GetPropertyInfo() ); + + // For all other already existing page styles the attributes + // must also be set + + SetPageDescAttrs( GetFirstPageDesc(), pPageEntry->GetItemSet(), + pPageEntry->GetPropertyInfo() ); + SetPageDescAttrs( GetLeftPageDesc(), pPageEntry->GetItemSet(), + pPageEntry->GetPropertyInfo() ); + SetPageDescAttrs( GetRightPageDesc(), pPageEntry->GetItemSet(), + pPageEntry->GetPropertyInfo() ); + + } + + pPageEntry = GetPage( "first", true ); + if( pPageEntry ) + { + SetPageDescAttrs( GetFirstPageDesc(true), pPageEntry->GetItemSet(), + pPageEntry->GetPropertyInfo() ); + m_bSetFirstPageDesc = true; + } + + pPageEntry = GetPage( "right", true ); + if( pPageEntry ) + { + SetPageDescAttrs( GetRightPageDesc(true), pPageEntry->GetItemSet(), + pPageEntry->GetPropertyInfo() ); + m_bSetRightPageDesc = true; + } + + pPageEntry = GetPage( "left", true ); + if( pPageEntry ) + SetPageDescAttrs( GetLeftPageDesc(true), pPageEntry->GetItemSet(), + pPageEntry->GetPropertyInfo() ); + + return true; +} + +bool SwHTMLParser::ParseStyleOptions( const OUString &rStyle, + const OUString &rId, + const OUString &rClass, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo &rPropInfo, + const OUString *pLang, + const OUString *pDir ) +{ + bool bRet = false; + + if( !rClass.isEmpty() ) + { + OUString aClass( rClass ); + SwCSS1Parser::GetScriptFromClass( aClass ); + const SvxCSS1MapEntry *pClass = m_pCSS1Parser->GetClass( aClass ); + if( pClass ) + { + SvxCSS1Parser::MergeStyles( pClass->GetItemSet(), + pClass->GetPropertyInfo(), + rItemSet, rPropInfo, false ); + bRet = true; + } + } + + if( !rId.isEmpty() ) + { + const SvxCSS1MapEntry *pId = m_pCSS1Parser->GetId( rId ); + if( pId ) + SvxCSS1Parser::MergeStyles( pId->GetItemSet(), + pId->GetPropertyInfo(), + rItemSet, rPropInfo, !rClass.isEmpty() ); + rPropInfo.m_aId = rId; + bRet = true; + } + + if( !rStyle.isEmpty() ) + { + m_pCSS1Parser->ParseStyleOption( rStyle, rItemSet, rPropInfo ); + bRet = true; + } + + if( bRet ) + rPropInfo.SetBoxItem( rItemSet, MIN_BORDER_DIST ); + + if( pLang && !pLang->isEmpty() ) + { + LanguageType eLang = LanguageTag::convertToLanguageTypeWithFallback( *pLang ); + if( LANGUAGE_DONTKNOW != eLang ) + { + SvxLanguageItem aLang( eLang, RES_CHRATR_LANGUAGE ); + rItemSet.Put( aLang ); + aLang.SetWhich( RES_CHRATR_CJK_LANGUAGE ); + rItemSet.Put( aLang ); + aLang.SetWhich( RES_CHRATR_CTL_LANGUAGE ); + rItemSet.Put( aLang ); + + bRet = true; + } + } + if( pDir && !pDir->isEmpty() ) + { + OUString aValue( *pDir ); + SvxFrameDirection eDir = SvxFrameDirection::Environment; + if (aValue.equalsIgnoreAsciiCase("LTR")) + eDir = SvxFrameDirection::Horizontal_LR_TB; + else if (aValue.equalsIgnoreAsciiCase("RTL")) + eDir = SvxFrameDirection::Horizontal_RL_TB; + + if( SvxFrameDirection::Environment != eDir ) + { + SvxFrameDirectionItem aDir( eDir, RES_FRAMEDIR ); + rItemSet.Put( aDir ); + + bRet = true; + } + } + + return bRet; +} + +void SwHTMLParser::SetAnchorAndAdjustment( const SvxCSS1PropertyInfo &rPropInfo, + SfxItemSet &rFrameItemSet ) +{ + SwFormatAnchor aAnchor; + + sal_Int16 eHoriOri = text::HoriOrientation::NONE; + sal_Int16 eVertOri = text::VertOrientation::NONE; + sal_Int16 eHoriRel = text::RelOrientation::FRAME; + sal_Int16 eVertRel = text::RelOrientation::FRAME; + SwTwips nHoriPos = 0, nVertPos = 0; + css::text::WrapTextMode eSurround = css::text::WrapTextMode_THROUGH; + if( SVX_CSS1_POS_ABSOLUTE == rPropInfo.m_ePosition ) + { + if( SVX_CSS1_LTYPE_TWIP == rPropInfo.m_eLeftType && + SVX_CSS1_LTYPE_TWIP == rPropInfo.m_eTopType ) + { + // Absolute positioned objects are page-bound, when they + // aren't in a frame and otherwise frame-bound. + const SwStartNode *pFlySttNd = + m_pPam->GetPoint()->GetNode().FindFlyStartNode(); + if( pFlySttNd ) + { + aAnchor.SetType( RndStdIds::FLY_AT_FLY ); + SwPosition aPos( *pFlySttNd ); + aAnchor.SetAnchor( &aPos ); + } + else + { + aAnchor.SetType( RndStdIds::FLY_AT_PAGE ); + aAnchor.SetPageNum( 1 ); + } + nHoriPos = rPropInfo.m_nLeft; + nVertPos = rPropInfo.m_nTop; + } + else + { + aAnchor.SetType( RndStdIds::FLY_AT_PARA ); + aAnchor.SetAnchor( m_pPam->GetPoint() ); + eVertOri = text::VertOrientation::TOP; + eVertRel = text::RelOrientation::CHAR; + if( SVX_CSS1_LTYPE_TWIP == rPropInfo.m_eLeftType ) + { + eHoriOri = text::HoriOrientation::NONE; + eHoriRel = text::RelOrientation::PAGE_FRAME; + nHoriPos = rPropInfo.m_nLeft; + } + else + { + eHoriOri = text::HoriOrientation::LEFT; + eHoriRel = text::RelOrientation::FRAME; // to be changed later + } + } + } + else + { + // Flowing object are inserted as paragraph-bound, when the paragraph is + // still empty and otherwise auto-bound. + // Auto-bound frames for the time being inserted at the previous position + // and later moved. + const sal_Int32 nContent = m_pPam->GetPoint()->GetContentIndex(); + if( nContent ) + { + aAnchor.SetType( RndStdIds::FLY_AT_CHAR ); + m_pPam->Move( fnMoveBackward ); + eVertOri = text::VertOrientation::CHAR_BOTTOM; + eVertRel = text::RelOrientation::CHAR; + } + else + { + aAnchor.SetType( RndStdIds::FLY_AT_PARA ); + eVertOri = text::VertOrientation::TOP; + eVertRel = text::RelOrientation::PRINT_AREA; + } + + aAnchor.SetAnchor( m_pPam->GetPoint() ); + + if( nContent ) + m_pPam->Move( fnMoveForward ); + + sal_uInt16 nLeftSpace = 0, nRightSpace = 0; + short nIndent = 0; + GetMarginsFromContextWithNumberBullet( nLeftSpace, nRightSpace, nIndent ); + + if( SvxAdjust::Right==rPropInfo.m_eFloat ) + { + eHoriOri = text::HoriOrientation::RIGHT; + eHoriRel = nRightSpace ? text::RelOrientation::PRINT_AREA : text::RelOrientation::FRAME; + eSurround = css::text::WrapTextMode_LEFT; + } + else + { + eHoriOri = text::HoriOrientation::LEFT; + eHoriRel = nLeftSpace ? text::RelOrientation::PRINT_AREA : text::RelOrientation::FRAME; + eSurround = css::text::WrapTextMode_RIGHT; + } + } + rFrameItemSet.Put( aAnchor ); + + // positioned absolutely with wrap + rFrameItemSet.Put( SwFormatHoriOrient( nHoriPos, eHoriOri, eHoriRel ) ); + rFrameItemSet.Put( SwFormatVertOrient( nVertPos, eVertOri, eVertRel ) ); + rFrameItemSet.Put( SwFormatSurround( eSurround ) ); +} + +void SwHTMLParser::SetVarSize( SvxCSS1PropertyInfo const &rPropInfo, + SfxItemSet &rFrameItemSet, + SwTwips nDfltWidth, sal_uInt8 nDfltPrcWidth ) +{ + SwTwips nWidth = nDfltWidth, nHeight = MINFLY; + sal_uInt8 nPercentWidth = nDfltPrcWidth, nPercentHeight = 0; + switch( rPropInfo.m_eWidthType ) + { + case SVX_CSS1_LTYPE_PERCENTAGE: + nPercentWidth = rPropInfo.m_nWidth > 0 ? static_cast<sal_uInt8>(rPropInfo.m_nWidth) : 1; + nWidth = MINFLY; + break; + case SVX_CSS1_LTYPE_TWIP: + nWidth = std::max<tools::Long>(rPropInfo.m_nWidth, MINFLY); + nPercentWidth = 0; + break; + default: + ; + } + switch( rPropInfo.m_eHeightType ) + { + case SVX_CSS1_LTYPE_PERCENTAGE: + nPercentHeight = rPropInfo.m_nHeight > 0 ? static_cast<sal_uInt8>(rPropInfo.m_nHeight) : 1; + break; + case SVX_CSS1_LTYPE_TWIP: + // Netscape and MS-IE interpreting the height incorrectly as minimum height, + // therefore we are doing the same. + nHeight = std::max<tools::Long>(rPropInfo.m_nHeight, MINFLY); + break; + default: + ; + } + + SwFormatFrameSize aFrameSize( SwFrameSize::Minimum, nWidth, nHeight ); + aFrameSize.SetWidthPercent( nPercentWidth ); + aFrameSize.SetHeightPercent( nPercentHeight ); + rFrameItemSet.Put( aFrameSize ); +} + +void SwHTMLParser::SetFrameFormatAttrs( SfxItemSet &rItemSet, + HtmlFrameFormatFlags nFlags, + SfxItemSet &rFrameItemSet ) +{ + const SvxBoxItem *pBoxItem; + if( (nFlags & HtmlFrameFormatFlags::Box) && + (pBoxItem = rItemSet.GetItemIfSet( RES_BOX )) ) + { + if( nFlags & HtmlFrameFormatFlags::Padding ) + { + SvxBoxItem aBoxItem( *pBoxItem ); + // reset all 4 sides to 0 + aBoxItem.SetAllDistances(0); + rFrameItemSet.Put( aBoxItem ); + } + else + { + rFrameItemSet.Put( *pBoxItem ); + } + rItemSet.ClearItem( RES_BOX ); + } + + const SvxBrushItem* pBrushItem; + if( (nFlags & HtmlFrameFormatFlags::Background) && + (pBrushItem = rItemSet.GetItemIfSet( RES_BACKGROUND )) ) + { + rFrameItemSet.Put( *pBrushItem ); + rItemSet.ClearItem( RES_BACKGROUND ); + } + + const SvxFrameDirectionItem* pFrameDirectionItem; + if( (nFlags & HtmlFrameFormatFlags::Direction) && + (pFrameDirectionItem = rItemSet.GetItemIfSet( RES_FRAMEDIR )) ) + { + rFrameItemSet.Put( *pFrameDirectionItem ); + rItemSet.ClearItem( RES_FRAMEDIR ); + } +} + +std::unique_ptr<HTMLAttrContext> SwHTMLParser::PopContext( HtmlTokenId nToken ) +{ + std::unique_ptr<HTMLAttrContext> xCntxt; + + HTMLAttrContexts::size_type nPos = m_aContexts.size(); + if( nPos <= m_nContextStMin ) + return nullptr; + + bool bFound = HtmlTokenId::NONE == nToken; + if( nToken != HtmlTokenId::NONE ) + { + // search for stack entry of token ... + while( nPos > m_nContextStMin ) + { + HtmlTokenId nCntxtToken = m_aContexts[--nPos]->GetToken(); + if( nCntxtToken == nToken ) + { + bFound = true; + break; + } + else if( nCntxtToken == HtmlTokenId::NONE ) // zero as token doesn't occur + { + break; + } + } + } + else + { + nPos--; + } + + if( bFound ) + { + xCntxt = std::move(m_aContexts[nPos]); + m_aContexts.erase( m_aContexts.begin() + nPos ); + } + + return xCntxt; +} + +void SwHTMLParser::GetMarginsFromContext( sal_uInt16& nLeft, + sal_uInt16& nRight, + short& nIndent, + bool bIgnoreTopContext ) const +{ + HTMLAttrContexts::size_type nPos = m_aContexts.size(); + if( bIgnoreTopContext ) + { + if( !nPos ) + return; + else + nPos--; + } + + while( nPos > m_nContextStAttrMin ) + { + const HTMLAttrContext *pCntxt = m_aContexts[--nPos].get(); + if( pCntxt->IsLRSpaceChanged() ) + { + pCntxt->GetMargins( nLeft, nRight, nIndent ); + return; + } + } +} + +void SwHTMLParser::GetMarginsFromContextWithNumberBullet( sal_uInt16& nLeft, + sal_uInt16& nRight, + short& nIndent ) const +{ + GetMarginsFromContext( nLeft, nRight, nIndent ); + const SwHTMLNumRuleInfo& rInfo = const_cast<SwHTMLParser*>(this)->GetNumInfo(); + if( rInfo.GetDepth() ) + { + sal_uInt8 nLevel = static_cast<sal_uInt8>( (rInfo.GetDepth() <= MAXLEVEL ? rInfo.GetDepth() + : MAXLEVEL) - 1 ); + const SwNumFormat& rNumFormat = rInfo.GetNumRule()->Get(nLevel); + nLeft = nLeft + rNumFormat.GetAbsLSpace(); //TODO: overflow + nIndent = rNumFormat.GetFirstLineOffset(); //TODO: overflow + } +} + +void SwHTMLParser::GetULSpaceFromContext( sal_uInt16& nUpper, + sal_uInt16& nLower ) const +{ + sal_uInt16 nDefaultColl = 0; + OUString aDefaultClass; + + HTMLAttrContexts::size_type nPos = m_aContexts.size(); + while( nPos > m_nContextStAttrMin ) + { + const HTMLAttrContext *pCntxt = m_aContexts[--nPos].get(); + if( pCntxt->IsULSpaceChanged() ) + { + pCntxt->GetULSpace( nUpper, nLower ); + return; + } + else if (!nDefaultColl) + { + nDefaultColl = pCntxt->GetDefaultTextFormatColl(); + if (nDefaultColl) + aDefaultClass = pCntxt->GetClass(); + } + } + + if (!nDefaultColl) + nDefaultColl = RES_POOLCOLL_TEXT; + + const SwTextFormatColl *pColl = + m_pCSS1Parser->GetTextFormatColl(nDefaultColl, aDefaultClass); + const SvxULSpaceItem& rULSpace = pColl->GetULSpace(); + nUpper = rULSpace.GetUpper(); + nLower = rULSpace.GetLower(); +} + +void SwHTMLParser::EndContextAttrs( HTMLAttrContext *pContext ) +{ + HTMLAttrs &rAttrs = pContext->GetAttrs(); + for( auto pAttr : rAttrs ) + { + if( RES_PARATR_DROP==pAttr->GetItem().Which() ) + { + // Set the number of characters for DropCaps. If it's zero at the + // end, the attribute is set to invalid and then isn't set from SetAttr. + sal_Int32 nChars = m_pPam->GetPoint()->GetContentIndex(); + if( nChars < 1 ) + pAttr->Invalidate(); + else if( nChars > MAX_DROPCAP_CHARS ) + nChars = MAX_DROPCAP_CHARS; + static_cast<SwFormatDrop&>(pAttr->GetItem()).GetChars() = static_cast<sal_uInt8>(nChars); + } + + EndAttr( pAttr ); + } +} + +void SwHTMLParser::InsertParaAttrs( const SfxItemSet& rItemSet ) +{ + SfxItemIter aIter( rItemSet ); + + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + // search for the table entry of the item... + sal_uInt16 nWhich = pItem->Which(); + HTMLAttr **ppAttr = GetAttrTabEntry( nWhich ); + + if( ppAttr ) + { + NewAttr(m_xAttrTab, ppAttr, *pItem); + if( RES_PARATR_BEGIN > nWhich ) + (*ppAttr)->SetLikePara(); + m_aParaAttrs.push_back( *ppAttr ); + bool bSuccess = EndAttr( *ppAttr, false ); + if (!bSuccess) + m_aParaAttrs.pop_back(); + } + } +} + +static void lcl_swcss1_setEncoding( SwFormat& rFormat, rtl_TextEncoding eEnc ) +{ + if( RTL_TEXTENCODING_DONTKNOW == eEnc ) + return; + + const SfxItemSet& rItemSet = rFormat.GetAttrSet(); + static const TypedWhichId<SvxFontItem> aWhichIds[3] = { RES_CHRATR_FONT, RES_CHRATR_CJK_FONT, + RES_CHRATR_CTL_FONT }; + for (auto const & i : aWhichIds) + { + const SvxFontItem *pFontItem = rItemSet.GetItemIfSet(i, false); + if (!pFontItem) + continue; + if (RTL_TEXTENCODING_SYMBOL == pFontItem->GetCharSet()) + continue; + if (eEnc == pFontItem->GetCharSet()) + continue; + SvxFontItem aFont(pFontItem->GetFamily(), pFontItem->GetFamilyName(), + pFontItem->GetStyleName(), pFontItem->GetPitch(), + eEnc, i); + rFormat.SetFormatAttr(aFont); + } +} + +void SwCSS1Parser::SetDfltEncoding( rtl_TextEncoding eEnc ) +{ + if( eEnc == GetDfltEncoding() ) + return; + + if( m_bIsNewDoc ) + { + // Set new encoding as pool default + static const sal_uInt16 aWhichIds[3] = { RES_CHRATR_FONT, RES_CHRATR_CJK_FONT, + RES_CHRATR_CTL_FONT }; + for(sal_uInt16 i : aWhichIds) + { + const SvxFontItem& rDfltFont = + static_cast<const SvxFontItem&>(m_pDoc->GetDefault( i)); + SvxFontItem aFont( rDfltFont.GetFamily(), + rDfltFont.GetFamilyName(), + rDfltFont.GetStyleName(), + rDfltFont.GetPitch(), + eEnc, i ); + m_pDoc->SetDefault( aFont ); + } + + // Change all paragraph styles that do specify a font. + for( auto pTextFormatColl : *m_pDoc->GetTextFormatColls() ) + lcl_swcss1_setEncoding( *pTextFormatColl, eEnc ); + + // Change all character styles that do specify a font. + for( auto pCharFormat : *m_pDoc->GetCharFormats() ) + lcl_swcss1_setEncoding( *pCharFormat, eEnc ); + } + + SvxCSS1Parser::SetDfltEncoding( eEnc ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlctxt.cxx b/sw/source/filter/html/htmlctxt.cxx new file mode 100644 index 0000000000..80245ba2ea --- /dev/null +++ b/sw/source/filter/html/htmlctxt.cxx @@ -0,0 +1,806 @@ +/* -*- 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/HoriOrientation.hpp> +#include <com/sun/star/text/VertOrientation.hpp> + +#include <hintids.hxx> +#include <svl/itemiter.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <svtools/htmltokn.h> +#include <editeng/boxitem.hxx> +#include <osl/diagnose.h> +#include <o3tl/string_view.hxx> + +#include <doc.hxx> +#include <pam.hxx> +#include <shellio.hxx> +#include <paratr.hxx> +#include "htmlnum.hxx" +#include "swcss1.hxx" +#include "swhtml.hxx" + +#include <memory> +#include <utility> + +using namespace ::com::sun::star; + +class HTMLAttrContext_SaveDoc +{ + SwHTMLNumRuleInfo m_aNumRuleInfo; // Numbering for this environment + std::unique_ptr<SwPosition> + m_pPos; // Jump back to here when leaving context + std::shared_ptr<HTMLAttrTable> + m_xAttrTab; // Valid attributes for the environment, + // if attributes shouldn't be preserved + + size_t m_nContextStMin; // Stack lower bound for the environment + // if stack needs to be protected + size_t m_nContextStAttrMin; // Stack lower bound for the environment + // if the attributes shouldn't be preserved + bool m_bStripTrailingPara : 1; + bool m_bKeepNumRules : 1; + bool m_bFixHeaderDist : 1; + bool m_bFixFooterDist : 1; + +public: + + HTMLAttrContext_SaveDoc() : + m_nContextStMin( SIZE_MAX ), m_nContextStAttrMin( SIZE_MAX ), + m_bStripTrailingPara( false ), m_bKeepNumRules( false ), + m_bFixHeaderDist( false ), m_bFixFooterDist( false ) + {} + + // The position is ours, so we need to create and delete it + void SetPos( const SwPosition& rPos ) { m_pPos.reset( new SwPosition(rPos) ); } + const SwPosition *GetPos() const { return m_pPos.get(); } + + // The index isn't ours. So no creation or deletion + void SetNumInfo( const SwHTMLNumRuleInfo& rInf ) { m_aNumRuleInfo.Set(rInf); } + const SwHTMLNumRuleInfo& GetNumInfo() const { return m_aNumRuleInfo; } + + std::shared_ptr<HTMLAttrTable> const & GetAttrTab(bool bCreate = false); + + void SetContextStMin( size_t nMin ) { m_nContextStMin = nMin; } + size_t GetContextStMin() const { return m_nContextStMin; } + + void SetContextStAttrMin( size_t nMin ) { m_nContextStAttrMin = nMin; } + size_t GetContextStAttrMin() const { return m_nContextStAttrMin; } + + void SetStripTrailingPara( bool bSet ) { m_bStripTrailingPara = bSet; } + bool GetStripTrailingPara() const { return m_bStripTrailingPara; } + + void SetKeepNumRules( bool bSet ) { m_bKeepNumRules = bSet; } + bool GetKeepNumRules() const { return m_bKeepNumRules; } + + void SetFixHeaderDist( bool bSet ) { m_bFixHeaderDist = bSet; } + bool GetFixHeaderDist() const { return m_bFixHeaderDist; } + + void SetFixFooterDist( bool bSet ) { m_bFixFooterDist = bSet; } + bool GetFixFooterDist() const { return m_bFixFooterDist; } +}; + +std::shared_ptr<HTMLAttrTable> const & HTMLAttrContext_SaveDoc::GetAttrTab( bool bCreate ) +{ + if (!m_xAttrTab && bCreate) + { + m_xAttrTab = std::make_shared<HTMLAttrTable>(); + memset(m_xAttrTab.get(), 0, sizeof(HTMLAttrTable)); + } + return m_xAttrTab; +} + +HTMLAttrContext_SaveDoc *HTMLAttrContext::GetSaveDocContext( bool bCreate ) +{ + if( !m_pSaveDocContext && bCreate ) + m_pSaveDocContext.reset(new HTMLAttrContext_SaveDoc); + + return m_pSaveDocContext.get(); +} + +HTMLAttrContext::HTMLAttrContext( HtmlTokenId nTokn, sal_uInt16 nPoolId, OUString aClass, + bool bDfltColl ) : + m_aClass(std::move( aClass )), + m_nToken( nTokn ), + m_nTextFormatColl( nPoolId ), + m_nLeftMargin( 0 ), + m_nRightMargin( 0 ), + m_nFirstLineIndent( 0 ), + m_nUpperSpace( 0 ), + m_nLowerSpace( 0 ), + m_eAppend( AM_NONE ), + m_bLRSpaceChanged( false ), + m_bULSpaceChanged( false ), + m_bDefaultTextFormatColl( bDfltColl ), + m_bSpansSection( false ), + m_bPopStack( false ), + m_bFinishPREListingXMP( false ), + m_bRestartPRE( false ), + m_bRestartXMP( false ), + m_bRestartListing( false ), + m_bHeaderOrFooter( false ) +{} + +HTMLAttrContext::HTMLAttrContext( HtmlTokenId nTokn ) : + m_nToken( nTokn ), + m_nTextFormatColl( 0 ), + m_nLeftMargin( 0 ), + m_nRightMargin( 0 ), + m_nFirstLineIndent( 0 ), + m_nUpperSpace( 0 ), + m_nLowerSpace( 0 ), + m_eAppend( AM_NONE ), + m_bLRSpaceChanged( false ), + m_bULSpaceChanged( false ), + m_bDefaultTextFormatColl( false ), + m_bSpansSection( false ), + m_bPopStack( false ), + m_bFinishPREListingXMP( false ), + m_bRestartPRE( false ), + m_bRestartXMP( false ), + m_bRestartListing( false ), + m_bHeaderOrFooter( false ) +{} + +HTMLAttrContext::~HTMLAttrContext() +{ + m_pSaveDocContext.reset(); +} + +void HTMLAttrContext::ClearSaveDocContext() +{ + m_pSaveDocContext.reset(); +} + +void SwHTMLParser::SplitAttrTab( const SwPosition& rNewPos ) +{ + // preliminary paragraph attributes are not allowed here, they could + // be set here and then the pointers become invalid! + OSL_ENSURE(m_aParaAttrs.empty(), + "Danger: there are non-final paragraph attributes"); + m_aParaAttrs.clear(); + + const SwPosition* pOldEndPara = m_pPam->GetPoint(); +#ifndef NDEBUG + auto const nOld(pOldEndPara->GetNodeIndex()); +#endif + sal_Int32 nOldEndCnt = m_pPam->GetPoint()->GetContentIndex(); + + const SwPosition& rNewSttPara = rNewPos; + sal_Int32 nNewSttCnt = rNewPos.GetContentIndex(); + + bool bMoveBack = false; + + // close all open attributes and re-open them after the table + HTMLAttr** pHTMLAttributes = reinterpret_cast<HTMLAttr**>(m_xAttrTab.get()); + for (auto nCnt = sizeof(HTMLAttrTable) / sizeof(HTMLAttr*); nCnt--; ++pHTMLAttributes) + { + HTMLAttr *pAttr = *pHTMLAttributes; + while( pAttr ) + { + HTMLAttr *pNext = pAttr->GetNext(); + HTMLAttr *pPrev = pAttr->GetPrev(); + + sal_uInt16 nWhich = pAttr->m_pItem->Which(); + if( !nOldEndCnt && RES_PARATR_BEGIN <= nWhich && + pAttr->GetStartParagraphIdx() < pOldEndPara->GetNodeIndex() ) + { + // The attribute needs to be closed one content position beforehand + if( !bMoveBack ) + { + bMoveBack = m_pPam->Move( fnMoveBackward ); + nOldEndCnt = m_pPam->GetPoint()->GetContentIndex(); + } + } + else if( bMoveBack ) + { + m_pPam->Move( fnMoveForward ); + nOldEndCnt = m_pPam->GetPoint()->GetContentIndex(); + bMoveBack = false; + } + + if( (RES_PARATR_BEGIN <= nWhich && bMoveBack) || + pAttr->GetStartParagraphIdx() < pOldEndPara->GetNodeIndex() || + (pAttr->GetStartParagraph() == pOldEndPara->GetNode() && + pAttr->GetStartContent() != nOldEndCnt) ) + { + // The attribute needs to be set. Because we still need the original, since + // pointers to the attribute still exists in the contexts, we need to clone it. + // The next-list gets lost but the previous-list is preserved + HTMLAttr *pSetAttr = pAttr->Clone( pOldEndPara->GetNode(), nOldEndCnt ); + + if( pNext ) + pNext->InsertPrev( pSetAttr ); + else + { + if (pSetAttr->m_bInsAtStart) + m_aSetAttrTab.push_front( pSetAttr ); + else + m_aSetAttrTab.push_back( pSetAttr ); + } + } + else if( pPrev ) + { + // The previous attributes still need to be set, even if the current attribute + // doesn't need to be set before the table + if( pNext ) + pNext->InsertPrev( pPrev ); + else + { + if (pPrev->m_bInsAtStart) + m_aSetAttrTab.push_front( pPrev ); + else + m_aSetAttrTab.push_back( pPrev ); + } + } + + // Set the start of the attribute + pAttr->m_nStartPara = rNewSttPara.GetNode(); + pAttr->m_nEndPara = rNewSttPara.GetNode(); + pAttr->m_nStartContent = nNewSttCnt; + pAttr->m_nEndContent = nNewSttCnt; + pAttr->m_pPrev = nullptr; + + pAttr = pNext; + } + } + + if( bMoveBack ) + m_pPam->Move( fnMoveForward ); + + assert(m_pPam->GetPoint()->GetNodeIndex() == nOld); +} + +void SwHTMLParser::SaveDocContext( HTMLAttrContext *pCntxt, + HtmlContextFlags nFlags, + const SwPosition *pNewPos ) +{ + HTMLAttrContext_SaveDoc *pSave = pCntxt->GetSaveDocContext( true ); + pSave->SetStripTrailingPara( bool(HtmlContextFlags::StripPara & nFlags) ); + pSave->SetKeepNumRules( bool(HtmlContextFlags::KeepNumrule & nFlags) ); + pSave->SetFixHeaderDist( bool(HtmlContextFlags::HeaderDist & nFlags) ); + pSave->SetFixFooterDist( bool(HtmlContextFlags::FooterDist & nFlags) ); + + if( pNewPos ) + { + // If the PaM needs to be set to a different position, we need to preserve numbering + if( !pSave->GetKeepNumRules() ) + { + // Numbering shall not be preserved. So we need to preserve the current state + // and turn off numbering afterwards + pSave->SetNumInfo( GetNumInfo() ); + GetNumInfo().Clear(); + } + + if( HtmlContextFlags::KeepAttrs & nFlags ) + { + // Close attribute on current position and start on new one + SplitAttrTab( *pNewPos ); + } + else + { + std::shared_ptr<HTMLAttrTable> xSaveAttrTab = pSave->GetAttrTab(true); + SaveAttrTab(xSaveAttrTab); + } + + pSave->SetPos( *m_pPam->GetPoint() ); + *m_pPam->GetPoint() = *pNewPos; + } + + // Settings nContextStMin automatically means, that no + // currently open lists (DL/OL/UL) can be closed + if( HtmlContextFlags::ProtectStack & nFlags ) + { + pSave->SetContextStMin( m_nContextStMin ); + m_nContextStMin = m_aContexts.size(); + + if( HtmlContextFlags::KeepAttrs & nFlags ) + { + pSave->SetContextStAttrMin( m_nContextStAttrMin ); + m_nContextStAttrMin = m_aContexts.size(); + } + } +} + +void SwHTMLParser::RestoreDocContext( HTMLAttrContext *pCntxt ) +{ + HTMLAttrContext_SaveDoc *pSave = pCntxt->GetSaveDocContext(); + if( !pSave ) + return; + + if( pSave->GetStripTrailingPara() ) + StripTrailingPara(); + + if( pSave->GetPos() ) + { + if( pSave->GetFixHeaderDist() || pSave->GetFixFooterDist() ) + FixHeaderFooterDistance( pSave->GetFixHeaderDist(), + pSave->GetPos() ); + + std::shared_ptr<HTMLAttrTable> xSaveAttrTab = pSave->GetAttrTab(); + if (!xSaveAttrTab) + { + // Close attribute on current position and start on the old one + SplitAttrTab( *pSave->GetPos() ); + } + else + { + RestoreAttrTab(xSaveAttrTab); + } + + *m_pPam->GetPoint() = *pSave->GetPos(); + + // We can already set the attributes so far + SetAttr(); + } + + if( SIZE_MAX != pSave->GetContextStMin() ) + { + m_nContextStMin = pSave->GetContextStMin(); + if( SIZE_MAX != pSave->GetContextStAttrMin() ) + m_nContextStAttrMin = pSave->GetContextStAttrMin(); + } + + if( !pSave->GetKeepNumRules() ) + { + // Set the preserved numbering back + GetNumInfo().Set( pSave->GetNumInfo() ); + } + + pCntxt->ClearSaveDocContext(); +} + +void SwHTMLParser::EndContext( HTMLAttrContext *pContext ) +{ + if( pContext->GetPopStack() ) + { + // Close all still open contexts. Our own context needs to be deleted already! + while( m_aContexts.size() > m_nContextStMin ) + { + std::unique_ptr<HTMLAttrContext> xCntxt(PopContext()); + OSL_ENSURE(xCntxt.get() != pContext, + "Context still on the stack" ); + if (xCntxt.get() == pContext) + break; + + EndContext(xCntxt.get()); + } + } + + // Close all still open attributes + if( pContext->HasAttrs() ) + EndContextAttrs( pContext ); + + // If a section has been opened, end it. Since sections can be part of absolute-positioned + // objects, this needs to be done before restoring document context + if( pContext->GetSpansSection() ) + EndSection(); + + // Leave borders and other special sections + if( pContext->HasSaveDocContext() ) + RestoreDocContext( pContext ); + + // Add a paragraph break if needed + if( AM_NONE != pContext->GetAppendMode() && + m_pPam->GetPoint()->GetContentIndex() ) + AppendTextNode( pContext->GetAppendMode() ); + + // Restart PRE, LISTING and XMP environments + if( pContext->IsFinishPREListingXMP() ) + FinishPREListingXMP(); + + if( pContext->IsRestartPRE() ) + StartPRE(); + + if( pContext->IsRestartXMP() ) + StartXMP(); + + if( pContext->IsRestartListing() ) + StartListing(); +} + +void SwHTMLParser::ClearContext( HTMLAttrContext *pContext ) +{ + HTMLAttrs &rAttrs = pContext->GetAttrs(); + for( auto pAttr : rAttrs ) + { + // Simple deletion doesn't to the job, since the attribute + // needs to be deregistered with its list. + // In theory, you could delete the list and its attributes separately + // but if you get that wrong, quite a lot is messed up + DeleteAttr( pAttr ); + } + rAttrs.clear(); + + OSL_ENSURE( !pContext->GetSpansSection(), + "Area can no longer be exited" ); + + OSL_ENSURE( !pContext->HasSaveDocContext(), + "Frame can no longer be exited" ); + + // like RestoreDocContext reset enough of this to not catastrophically + // fail if we still have a SaveDocContext here + if (HTMLAttrContext_SaveDoc *pSave = pContext->GetSaveDocContext()) + { + if (SIZE_MAX != pSave->GetContextStMin()) + { + m_nContextStMin = pSave->GetContextStMin(); + if (SIZE_MAX != pSave->GetContextStAttrMin()) + m_nContextStAttrMin = pSave->GetContextStAttrMin(); + } + + pContext->ClearSaveDocContext(); + } + + // Restart PRE/LISTING/XMP environments + if( pContext->IsFinishPREListingXMP() ) + FinishPREListingXMP(); + + if( pContext->IsRestartPRE() ) + StartPRE(); + + if( pContext->IsRestartXMP() ) + StartXMP(); + + if( pContext->IsRestartListing() ) + StartListing(); +} + +bool SwHTMLParser::DoPositioning( SfxItemSet &rItemSet, + SvxCSS1PropertyInfo &rPropInfo, + HTMLAttrContext *pContext ) +{ + bool bRet = false; + + // A border is opened on the following conditions + // - the tag is absolute-positioned AND left/top are both known AND don't contain a % property + // OR + // - the tag should be floating AND + // - there's a given width + if( SwCSS1Parser::MayBePositioned( rPropInfo ) ) + { + SfxItemSetFixed<RES_FRMATR_BEGIN, RES_FRMATR_END-1> aFrameItemSet( m_xDoc->GetAttrPool() ); + if( !IsNewDoc() ) + Reader::ResetFrameFormatAttrs(aFrameItemSet ); + + SetAnchorAndAdjustment( text::VertOrientation::NONE, text::HoriOrientation::NONE, rPropInfo, + aFrameItemSet ); + + SetVarSize( rPropInfo, aFrameItemSet ); + + SetSpace( Size(0,0), rItemSet, rPropInfo, aFrameItemSet ); + + SetFrameFormatAttrs( rItemSet, + HtmlFrameFormatFlags::Box|HtmlFrameFormatFlags::Padding|HtmlFrameFormatFlags::Background|HtmlFrameFormatFlags::Direction, + aFrameItemSet ); + + InsertFlyFrame(aFrameItemSet, pContext, rPropInfo.m_aId); + pContext->SetPopStack( true ); + rPropInfo.m_aId.clear(); + bRet = true; + } + + return bRet; +} + +bool SwHTMLParser::CreateContainer( std::u16string_view rClass, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo &rPropInfo, + HTMLAttrContext *pContext ) +{ + bool bRet = false; + if( o3tl::equalsIgnoreAsciiCase( rClass, u"sd-abs-pos" ) && + SwCSS1Parser::MayBePositioned( rPropInfo ) ) + { + // Container class + SfxItemSet *pFrameItemSet = pContext->GetFrameItemSet( m_xDoc.get() ); + if( !IsNewDoc() ) + Reader::ResetFrameFormatAttrs( *pFrameItemSet ); + + SetAnchorAndAdjustment( text::VertOrientation::NONE, text::HoriOrientation::NONE, + rPropInfo, *pFrameItemSet ); + Size aDummy(0,0); + SetFixSize( aDummy, aDummy, false, false, rPropInfo, *pFrameItemSet ); + SetSpace( aDummy, rItemSet, rPropInfo, *pFrameItemSet ); + SetFrameFormatAttrs( rItemSet, HtmlFrameFormatFlags::Box|HtmlFrameFormatFlags::Background|HtmlFrameFormatFlags::Direction, + *pFrameItemSet ); + + bRet = true; + } + + return bRet; +} + +void SwHTMLParser::InsertAttrs( SfxItemSet &rItemSet, + SvxCSS1PropertyInfo const &rPropInfo, + HTMLAttrContext *pContext, + bool bCharLvl ) +{ + // Put together a DropCap attribute, if a "float:left" is before the first character + if( bCharLvl && !m_pPam->GetPoint()->GetContentIndex() && + SvxAdjust::Left == rPropInfo.m_eFloat ) + { + SwFormatDrop aDrop; + aDrop.GetChars() = 1; + + m_pCSS1Parser->FillDropCap( aDrop, rItemSet ); + + // We only set the DropCap attribute if the initial spans multiple lines + if( aDrop.GetLines() > 1 ) + { + NewAttr(m_xAttrTab, &m_xAttrTab->pDropCap, aDrop); + + HTMLAttrs &rAttrs = pContext->GetAttrs(); + rAttrs.push_back( m_xAttrTab->pDropCap ); + + return; + } + } + + if( !bCharLvl ) + m_pCSS1Parser->SetFormatBreak( rItemSet, rPropInfo ); + + OSL_ENSURE(m_aContexts.size() <= m_nContextStAttrMin || + m_aContexts.back().get() != pContext, + "SwHTMLParser::InsertAttrs: Context already on the Stack"); + + SfxItemIter aIter( rItemSet ); + + const SvxFirstLineIndentItem * pFirstLineItem(nullptr); + const SvxTextLeftMarginItem * pTextLeftMargin(nullptr); + const SvxRightMarginItem * pRightMargin(nullptr); + + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + switch( pItem->Which() ) + { + case RES_MARGIN_FIRSTLINE: + { + pFirstLineItem = static_cast<const SvxFirstLineIndentItem*>(pItem); + } + break; + case RES_MARGIN_TEXTLEFT: + { + pTextLeftMargin = static_cast<const SvxTextLeftMarginItem*>(pItem); + } + break; + case RES_MARGIN_RIGHT: + { + pRightMargin = static_cast<const SvxRightMarginItem*>(pItem); + } + break; + } + } + +#if 1 + { + // Paragraph indents need to be added and are generated for each paragraphs + // (here for the first paragraph only, all the following in SetTextCollAttrs) + + // Get old paragraph indents without the top context (that's the one we're editing) + sal_uInt16 nOldLeft = 0, nOldRight = 0; + short nOldIndent = 0; + bool bIgnoreTop = m_aContexts.size() > m_nContextStMin && + m_aContexts.back().get() == pContext; + GetMarginsFromContext( nOldLeft, nOldRight, nOldIndent, + bIgnoreTop ); + + // ... and the currently valid ones + sal_uInt16 nLeft = nOldLeft, nRight = nOldRight; + short nIndent = nOldIndent; + pContext->GetMargins( nLeft, nRight, nIndent ); + + // ... and add the new indents to the old ones + // Here, we don't get the ones from the item but the separately remembered ones, + // since they could be negative. Accessing those via the item still works, since + // the item (with value 0) will be added + if( rPropInfo.m_bLeftMargin ) + { + OSL_ENSURE( rPropInfo.m_nLeftMargin < 0 || + !pTextLeftMargin || + rPropInfo.m_nLeftMargin == pTextLeftMargin->GetTextLeft(), + "left margin does not match with item" ); + if( rPropInfo.m_nLeftMargin < 0 && + -rPropInfo.m_nLeftMargin > nOldLeft ) + nLeft = 0; + else + nLeft = nOldLeft + static_cast< sal_uInt16 >(rPropInfo.m_nLeftMargin); + } + if( rPropInfo.m_bRightMargin ) + { + OSL_ENSURE( rPropInfo.m_nRightMargin < 0 || + !pRightMargin || + rPropInfo.m_nRightMargin == pRightMargin->GetRight(), + "right margin does not match with item" ); + if( rPropInfo.m_nRightMargin < 0 && + -rPropInfo.m_nRightMargin > nOldRight ) + nRight = 0; + else + nRight = nOldRight + static_cast< sal_uInt16 >(rPropInfo.m_nRightMargin); + } + if (rPropInfo.m_bTextIndent && pFirstLineItem) + nIndent = pFirstLineItem->GetTextFirstLineOffset(); + + // Remember the value for the following paragraphs + pContext->SetMargins( nLeft, nRight, nIndent ); + + // Set the attribute on the current paragraph + SvxFirstLineIndentItem const firstLine(nIndent, RES_MARGIN_FIRSTLINE); + NewAttr(m_xAttrTab, &m_xAttrTab->pFirstLineIndent, firstLine); + EndAttr(m_xAttrTab->pFirstLineIndent, false); + SvxTextLeftMarginItem const leftMargin(nLeft, RES_MARGIN_TEXTLEFT); + NewAttr(m_xAttrTab, &m_xAttrTab->pTextLeftMargin, leftMargin); + EndAttr(m_xAttrTab->pTextLeftMargin, false); + SvxRightMarginItem const rightMargin(nRight, RES_MARGIN_RIGHT); + NewAttr(m_xAttrTab, &m_xAttrTab->pRightMargin, rightMargin); + EndAttr(m_xAttrTab->pRightMargin, false); + } +#endif + + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + HTMLAttr **ppAttr = nullptr; + + switch( pItem->Which() ) + { + + case RES_UL_SPACE: + if( !rPropInfo.m_bTopMargin || !rPropInfo.m_bBottomMargin ) + { + sal_uInt16 nUpper = 0, nLower = 0; + GetULSpaceFromContext( nUpper, nLower ); + SvxULSpaceItem aULSpace( *static_cast<const SvxULSpaceItem *>(pItem) ); + if( !rPropInfo.m_bTopMargin ) + aULSpace.SetUpper( nUpper ); + if( !rPropInfo.m_bBottomMargin ) + aULSpace.SetLower( nLower ); + + NewAttr(m_xAttrTab, &m_xAttrTab->pULSpace, aULSpace); + + // save context information + HTMLAttrs &rAttrs = pContext->GetAttrs(); + rAttrs.push_back( m_xAttrTab->pULSpace ); + + pContext->SetULSpace( aULSpace.GetUpper(), aULSpace.GetLower() ); + } + else + { + ppAttr = &m_xAttrTab->pULSpace; + } + break; + case RES_CHRATR_FONTSIZE: + // don't set attributes with a % property + if( static_cast<const SvxFontHeightItem *>(pItem)->GetProp() == 100 ) + ppAttr = &m_xAttrTab->pFontHeight; + break; + case RES_CHRATR_CJK_FONTSIZE: + // don't set attributes with a % property + if( static_cast<const SvxFontHeightItem *>(pItem)->GetProp() == 100 ) + ppAttr = &m_xAttrTab->pFontHeightCJK; + break; + case RES_CHRATR_CTL_FONTSIZE: + // don't set attributes with a % property + if( static_cast<const SvxFontHeightItem *>(pItem)->GetProp() == 100 ) + ppAttr = &m_xAttrTab->pFontHeightCTL; + break; + + case RES_BACKGROUND: + if( bCharLvl ) + { + // Convert the Frame attribute to a Char attribute (if needed) + SvxBrushItem aBrushItem( *static_cast<const SvxBrushItem *>(pItem) ); + aBrushItem.SetWhich( RES_CHRATR_BACKGROUND ); + + // Set the attribute + NewAttr(m_xAttrTab, &m_xAttrTab->pCharBrush, aBrushItem); + + // and save context information + HTMLAttrs &rAttrs = pContext->GetAttrs(); + rAttrs.push_back( m_xAttrTab->pCharBrush ); + } + else if( pContext->GetToken() != HtmlTokenId::TABLEHEADER_ON && + pContext->GetToken() != HtmlTokenId::TABLEDATA_ON ) + { + ppAttr = &m_xAttrTab->pBrush; + } + break; + + case RES_BOX: + if( bCharLvl ) + { + SvxBoxItem aBoxItem( *static_cast<const SvxBoxItem *>(pItem) ); + aBoxItem.SetWhich( RES_CHRATR_BOX ); + + NewAttr(m_xAttrTab, &m_xAttrTab->pCharBox, aBoxItem); + + HTMLAttrs &rAttrs = pContext->GetAttrs(); + rAttrs.push_back( m_xAttrTab->pCharBox ); + } + else + { + ppAttr = &m_xAttrTab->pBox; + } + break; + + default: + ppAttr = GetAttrTabEntry( pItem->Which() ); + break; + } + + if( ppAttr ) + { + // Set the attribute + NewAttr(m_xAttrTab, ppAttr, *pItem); + + // and save context information + HTMLAttrs &rAttrs = pContext->GetAttrs(); + rAttrs.push_back( *ppAttr ); + } + } + + if( !rPropInfo.m_aId.isEmpty() ) + InsertBookmark( rPropInfo.m_aId ); +} + +void SwHTMLParser::InsertAttr( HTMLAttr **ppAttr, const SfxPoolItem & rItem, + HTMLAttrContext *pCntxt ) +{ + if( !ppAttr ) + { + ppAttr = GetAttrTabEntry( rItem.Which() ); + if( !ppAttr ) + return; + } + + // Set the attribute + NewAttr(m_xAttrTab, ppAttr, rItem); + + // save context information + HTMLAttrs &rAttrs = pCntxt->GetAttrs(); + rAttrs.push_back( *ppAttr ); +} + +void SwHTMLParser::SplitPREListingXMP( HTMLAttrContext *pCntxt ) +{ + // PRE/Listing/XMP need to be finished when finishing context + pCntxt->SetFinishPREListingXMP( true ); + + // And set all now valid flags + if( IsReadPRE() ) + pCntxt->SetRestartPRE( true ); + if( IsReadXMP() ) + pCntxt->SetRestartXMP( true ); + if( IsReadListing() ) + pCntxt->SetRestartListing( true ); + + FinishPREListingXMP(); +} + +SfxItemSet *HTMLAttrContext::GetFrameItemSet( SwDoc *pCreateDoc ) +{ + if( !m_pFrameItemSet && pCreateDoc ) + m_pFrameItemSet = std::make_unique<SfxItemSetFixed<RES_FRMATR_BEGIN, RES_FRMATR_END-1>> + ( pCreateDoc->GetAttrPool() ); + return m_pFrameItemSet.get(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmldrawreader.cxx b/sw/source/filter/html/htmldrawreader.cxx new file mode 100644 index 0000000000..fdbc187348 --- /dev/null +++ b/sw/source/filter/html/htmldrawreader.cxx @@ -0,0 +1,572 @@ +/* -*- 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 <hintids.hxx> +#include <vcl/svapp.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdotext.hxx> +#include <svx/sdtagitm.hxx> +#include <svx/sdtacitm.hxx> +#include <svx/sdtayitm.hxx> +#include <svx/sdtaaitm.hxx> +#include <svx/sdtaiitm.hxx> +#include <svx/sdtmfitm.hxx> +#include <editeng/eeitem.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xflclit.hxx> +#include <editeng/colritem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <svl/itemiter.hxx> +#include <svtools/htmltokn.h> +#include <svtools/htmlkywd.hxx> +#include <osl/diagnose.h> + +#include <charatr.hxx> +#include <drawdoc.hxx> +#include <fmtanchr.hxx> +#include <fmtornt.hxx> +#include <fmtsrnd.hxx> +#include <ndtxt.hxx> +#include <doc.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <poolfmt.hxx> +#include "swcss1.hxx" +#include "swhtml.hxx" +#include <shellio.hxx> + +using namespace css; + +HTMLOptionEnum<SdrTextAniKind> const aHTMLMarqBehaviorTable[] = +{ + { OOO_STRING_SVTOOLS_HTML_BEHAV_scroll, SdrTextAniKind::Scroll }, + { OOO_STRING_SVTOOLS_HTML_BEHAV_alternate, SdrTextAniKind::Alternate }, + { OOO_STRING_SVTOOLS_HTML_BEHAV_slide, SdrTextAniKind::Slide }, + { nullptr, SdrTextAniKind(0) } +}; + +HTMLOptionEnum<SdrTextAniDirection> const aHTMLMarqDirectionTable[] = +{ + { OOO_STRING_SVTOOLS_HTML_AL_left, SdrTextAniDirection::Left }, + { OOO_STRING_SVTOOLS_HTML_AL_right, SdrTextAniDirection::Right }, + { nullptr, SdrTextAniDirection(0) } +}; + +void SwHTMLParser::InsertDrawObject( SdrObject* pNewDrawObj, + const Size& rPixSpace, + sal_Int16 eVertOri, + sal_Int16 eHoriOri, + SfxItemSet& rCSS1ItemSet, + SvxCSS1PropertyInfo& rCSS1PropInfo ) +{ + // always on top of text. + // but in invisible layer. <ConnectToLayout> will move the object + // to the visible layer. + pNewDrawObj->SetLayer( m_xDoc->getIDocumentDrawModelAccess().GetInvisibleHeavenId() ); + + SfxItemSetFixed<RES_FRMATR_BEGIN, RES_FRMATR_END-1> aFrameSet( m_xDoc->GetAttrPool() ); + if( !IsNewDoc() ) + Reader::ResetFrameFormatAttrs( aFrameSet ); + + sal_uInt16 nLeftSpace = 0, nRightSpace = 0, nUpperSpace = 0, nLowerSpace = 0; + if( rPixSpace.Width() || rPixSpace.Height() ) + { + nLeftSpace = nRightSpace = o3tl::convert(rPixSpace.Width(), o3tl::Length::px, o3tl::Length::twip); + nUpperSpace = nLowerSpace = o3tl::convert(rPixSpace.Height(), o3tl::Length::px, o3tl::Length::twip); + } + + // set left/right border + // note: parser never creates SvxLeftMarginItem! must be converted + if (const SvxTextLeftMarginItem *const pLeft = rCSS1ItemSet.GetItemIfSet(RES_MARGIN_TEXTLEFT)) + { + if( rCSS1PropInfo.m_bLeftMargin ) + { + // should be SvxLeftMarginItem... "cast" it + nLeftSpace = static_cast<sal_uInt16>(pLeft->GetTextLeft()); + rCSS1PropInfo.m_bLeftMargin = false; + } + rCSS1ItemSet.ClearItem(RES_MARGIN_TEXTLEFT); + } + if (const SvxRightMarginItem *const pRight = rCSS1ItemSet.GetItemIfSet(RES_MARGIN_RIGHT)) + { + if( rCSS1PropInfo.m_bRightMargin ) + { + nRightSpace = static_cast< sal_uInt16 >(pRight->GetRight()); + rCSS1PropInfo.m_bRightMargin = false; + } + rCSS1ItemSet.ClearItem(RES_MARGIN_RIGHT); + } + if( nLeftSpace || nRightSpace ) + { + SvxLRSpaceItem aLRItem( RES_LR_SPACE ); + aLRItem.SetLeft( nLeftSpace ); + aLRItem.SetRight( nRightSpace ); + aFrameSet.Put( aLRItem ); + } + + // set top/bottom border + if( const SvxULSpaceItem* pULItem = rCSS1ItemSet.GetItemIfSet( RES_UL_SPACE ) ) + { + // maybe flatten the first line indentation + if( rCSS1PropInfo.m_bTopMargin ) + { + nUpperSpace = pULItem->GetUpper(); + rCSS1PropInfo.m_bTopMargin = false; + } + if( rCSS1PropInfo.m_bBottomMargin ) + { + nLowerSpace = pULItem->GetLower(); + rCSS1PropInfo.m_bBottomMargin = false; + } + + rCSS1ItemSet.ClearItem( RES_UL_SPACE ); + } + if( nUpperSpace || nLowerSpace ) + { + SvxULSpaceItem aULItem( RES_UL_SPACE ); + aULItem.SetUpper( nUpperSpace ); + aULItem.SetLower( nLowerSpace ); + aFrameSet.Put( aULItem ); + } + + SwFormatAnchor aAnchor( RndStdIds::FLY_AS_CHAR ); + if( SVX_CSS1_POS_ABSOLUTE == rCSS1PropInfo.m_ePosition && + SVX_CSS1_LTYPE_TWIP == rCSS1PropInfo.m_eLeftType && + SVX_CSS1_LTYPE_TWIP == rCSS1PropInfo.m_eTopType ) + { + const SwStartNode *pFlySttNd = + m_pPam->GetPoint()->GetNode().FindFlyStartNode(); + + if( pFlySttNd ) + { + aAnchor.SetType( RndStdIds::FLY_AT_FLY ); + SwPosition aPos( *pFlySttNd ); + aAnchor.SetAnchor( &aPos ); + } + else + { + aAnchor.SetType( RndStdIds::FLY_AT_PAGE ); + } + // #i26791# - direct positioning for <SwDoc::Insert(..)> + pNewDrawObj->SetRelativePos( Point(rCSS1PropInfo.m_nLeft + nLeftSpace, + rCSS1PropInfo.m_nTop + nUpperSpace) ); + aFrameSet.Put( SwFormatSurround(css::text::WrapTextMode_THROUGH) ); + } + else if( SvxAdjust::Left == rCSS1PropInfo.m_eFloat || + text::HoriOrientation::LEFT == eHoriOri ) + { + aAnchor.SetType( RndStdIds::FLY_AT_PARA ); + aFrameSet.Put( SwFormatSurround(css::text::WrapTextMode_RIGHT) ); + // #i26791# - direct positioning for <SwDoc::Insert(..)> + pNewDrawObj->SetRelativePos( Point(nLeftSpace, nUpperSpace) ); + } + else if( text::VertOrientation::NONE != eVertOri ) + { + aFrameSet.Put( SwFormatVertOrient( 0, eVertOri ) ); + } + + if (RndStdIds::FLY_AT_PAGE == aAnchor.GetAnchorId()) + { + aAnchor.SetPageNum( 1 ); + } + else if( RndStdIds::FLY_AT_FLY != aAnchor.GetAnchorId() ) + { + aAnchor.SetAnchor( m_pPam->GetPoint() ); + } + aFrameSet.Put( aAnchor ); + + m_xDoc->getIDocumentContentOperations().InsertDrawObj( *m_pPam, *pNewDrawObj, aFrameSet ); +} + +static void PutEEPoolItem( SfxItemSet &rEEItemSet, + const SfxPoolItem& rSwItem ) +{ + + sal_uInt16 nEEWhich = 0; + + switch( rSwItem.Which() ) + { + case RES_CHRATR_COLOR: nEEWhich = EE_CHAR_COLOR; break; + case RES_CHRATR_CROSSEDOUT: nEEWhich = EE_CHAR_STRIKEOUT; break; + case RES_CHRATR_ESCAPEMENT: nEEWhich = EE_CHAR_ESCAPEMENT; break; + case RES_CHRATR_FONT: nEEWhich = EE_CHAR_FONTINFO; break; + case RES_CHRATR_CJK_FONT: nEEWhich = EE_CHAR_FONTINFO_CJK; break; + case RES_CHRATR_CTL_FONT: nEEWhich = EE_CHAR_FONTINFO_CTL; break; + case RES_CHRATR_FONTSIZE: nEEWhich = EE_CHAR_FONTHEIGHT; break; + case RES_CHRATR_CJK_FONTSIZE: nEEWhich = EE_CHAR_FONTHEIGHT_CJK; break; + case RES_CHRATR_CTL_FONTSIZE: nEEWhich = EE_CHAR_FONTHEIGHT_CTL; break; + case RES_CHRATR_KERNING: nEEWhich = EE_CHAR_KERNING; break; + case RES_CHRATR_POSTURE: nEEWhich = EE_CHAR_ITALIC; break; + case RES_CHRATR_CJK_POSTURE: nEEWhich = EE_CHAR_ITALIC_CJK; break; + case RES_CHRATR_CTL_POSTURE: nEEWhich = EE_CHAR_ITALIC_CTL; break; + case RES_CHRATR_UNDERLINE: nEEWhich = EE_CHAR_UNDERLINE; break; + case RES_CHRATR_WEIGHT: nEEWhich = EE_CHAR_WEIGHT; break; + case RES_CHRATR_CJK_WEIGHT: nEEWhich = EE_CHAR_WEIGHT_CJK; break; + case RES_CHRATR_CTL_WEIGHT: nEEWhich = EE_CHAR_WEIGHT_CTL; break; + case RES_BACKGROUND: + case RES_CHRATR_BACKGROUND: + { + const SvxBrushItem& rBrushItem = static_cast<const SvxBrushItem&>(rSwItem); + rEEItemSet.Put( XFillStyleItem(drawing::FillStyle_SOLID) ); + rEEItemSet.Put(XFillColorItem(OUString(), + rBrushItem.GetColor()) ); + } + break; + } + + if( nEEWhich ) + rEEItemSet.Put( rSwItem.CloneSetWhich(nEEWhich) ); +} + +void SwHTMLParser::NewMarquee( HTMLTable *pCurTable ) +{ + + OSL_ENSURE( !m_pMarquee, "Marquee in Marquee???" ); + m_aContents.clear(); + + OUString aId, aStyle, aClass; + + tools::Long nWidth=0, nHeight=0; + bool bPercentWidth = false, bDirection = false, bBGColor = false; + Size aSpace( 0, 0 ); + sal_Int16 eVertOri = text::VertOrientation::TOP; + sal_Int16 eHoriOri = text::HoriOrientation::NONE; + SdrTextAniKind eAniKind = SdrTextAniKind::Scroll; + SdrTextAniDirection eAniDir = SdrTextAniDirection::Left; + sal_uInt16 nCount = 0, nDelay = 60; + sal_Int16 nAmount = -6; + Color aBGColor; + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (const auto & rOption : rHTMLOptions) + { + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + + case HtmlOptionId::BEHAVIOR: + eAniKind = rOption.GetEnum( aHTMLMarqBehaviorTable, eAniKind ); + break; + + case HtmlOptionId::BGCOLOR: + rOption.GetColor( aBGColor ); + bBGColor = true; + break; + + case HtmlOptionId::DIRECTION: + eAniDir = rOption.GetEnum( aHTMLMarqDirectionTable, eAniDir ); + bDirection = true; + break; + + case HtmlOptionId::LOOP: + if (rOption.GetString(). + equalsIgnoreAsciiCase(OOO_STRING_SVTOOLS_HTML_LOOP_infinite)) + { + nCount = 0; + } + else + { + const sal_Int32 nLoop = rOption.GetSNumber(); + nCount = std::max<sal_Int32>(nLoop, 0); + } + break; + + case HtmlOptionId::SCROLLAMOUNT: + nAmount = - static_cast<sal_Int16>(rOption.GetNumber()); + break; + + case HtmlOptionId::SCROLLDELAY: + nDelay = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + break; + + case HtmlOptionId::WIDTH: + // first only save as pixel value! + nWidth = rOption.GetNumber(); + bPercentWidth = rOption.GetString().indexOf('%') != -1; + if( bPercentWidth && nWidth>100 ) + nWidth = 100; + break; + + case HtmlOptionId::HEIGHT: + // first only save as pixel value! + nHeight = rOption.GetNumber(); + if( rOption.GetString().indexOf('%') != -1 ) + nHeight = 0; + break; + + case HtmlOptionId::HSPACE: + // first only save as pixel value! + aSpace.setHeight( rOption.GetNumber() ); + break; + + case HtmlOptionId::VSPACE: + // first only save as pixel value! + aSpace.setWidth( rOption.GetNumber() ); + break; + + case HtmlOptionId::ALIGN: + eVertOri = + rOption.GetEnum( aHTMLImgVAlignTable, + text::VertOrientation::TOP ); + eHoriOri = + rOption.GetEnum( aHTMLImgHAlignTable ); + break; + default: break; + } + } + + // create a DrawTextobj + // #i52858# - method name changed + SwDrawModel* pModel = m_xDoc->getIDocumentDrawModelAccess().GetOrCreateDrawModel(); + SdrPage* pPg = pModel->GetPage( 0 ); + m_pMarquee = static_cast<SdrTextObj*>(SdrObjFactory::MakeNewObject( + *pModel, + SdrInventor::Default, + SdrObjKind::Text).get()); + + if( !m_pMarquee ) + return; + + pPg->InsertObject( m_pMarquee.get() ); + + if( !aId.isEmpty() ) + InsertBookmark( aId ); + + // (only) Alternate runs from left to right as default + if( SdrTextAniKind::Alternate==eAniKind && !bDirection ) + eAniDir = SdrTextAniDirection::Right; + + // re set the attributes needed for scrolling + SfxItemSetFixed< + XATTR_FILL_FIRST, XATTR_FILL_LAST, + SDRATTR_MISC_FIRST, SDRATTR_MISC_LAST, + EE_CHAR_START, EE_CHAR_END> + aItemSet( pModel->GetItemPool() ); + aItemSet.Put( makeSdrTextAutoGrowWidthItem( false ) ); + aItemSet.Put( makeSdrTextAutoGrowHeightItem( true ) ); + aItemSet.Put( SdrTextAniKindItem( eAniKind ) ); + aItemSet.Put( SdrTextAniDirectionItem( eAniDir ) ); + aItemSet.Put( SdrTextAniCountItem( nCount ) ); + aItemSet.Put( SdrTextAniDelayItem( nDelay ) ); + aItemSet.Put( SdrTextAniAmountItem( nAmount ) ); + if( SdrTextAniKind::Alternate==eAniKind ) + { + // (only) Alternate starts and ends Inside as default + aItemSet.Put( SdrTextAniStartInsideItem(true) ); + aItemSet.Put( SdrTextAniStopInsideItem(true) ); + if( SdrTextAniDirection::Left==eAniDir ) + aItemSet.Put( SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_RIGHT) ); + } + + // set the default colour (from the default template), so that a meaningful + // colour is set at all + const Color& rDfltColor = + m_pCSS1Parser->GetTextCollFromPool( RES_POOLCOLL_STANDARD ) + ->GetColor().GetValue(); + aItemSet.Put( SvxColorItem( rDfltColor, EE_CHAR_COLOR ) ); + + // set the attributes of the current paragraph style + sal_uInt16 nWhichIds[] = + { + RES_CHRATR_COLOR, RES_CHRATR_CROSSEDOUT, RES_CHRATR_ESCAPEMENT, + RES_CHRATR_FONT, RES_CHRATR_FONTSIZE, RES_CHRATR_KERNING, + RES_CHRATR_POSTURE, RES_CHRATR_UNDERLINE, RES_CHRATR_WEIGHT, + RES_CHRATR_BACKGROUND, + RES_CHRATR_CJK_FONT, RES_CHRATR_CJK_FONTSIZE, + RES_CHRATR_CJK_POSTURE, RES_CHRATR_CJK_WEIGHT, + RES_CHRATR_CTL_FONT, RES_CHRATR_CTL_FONTSIZE, + RES_CHRATR_CTL_POSTURE, RES_CHRATR_CTL_WEIGHT, + 0 + }; + SwTextNode const*const pTextNd = + m_pPam->GetPoint()->GetNode().GetTextNode(); + if( pTextNd ) + { + const SfxItemSet& rItemSet = pTextNd->GetAnyFormatColl().GetAttrSet(); + const SfxPoolItem *pItem; + for( int i=0; nWhichIds[i]; ++i ) + { + if( SfxItemState::SET == rItemSet.GetItemState( nWhichIds[i], true, &pItem ) ) + PutEEPoolItem( aItemSet, *pItem ); + } + } + + // set attribute of environment at the Draw object + HTMLAttr** pHTMLAttributes = reinterpret_cast<HTMLAttr**>(m_xAttrTab.get()); + for (auto nCnt = sizeof(HTMLAttrTable) / sizeof(HTMLAttr*); nCnt--; ++pHTMLAttributes) + { + HTMLAttr *pAttr = *pHTMLAttributes; + if( pAttr ) + PutEEPoolItem( aItemSet, pAttr->GetItem() ); + } + + if( bBGColor ) + { + aItemSet.Put( XFillStyleItem(drawing::FillStyle_SOLID) ); + aItemSet.Put(XFillColorItem(OUString(), aBGColor)); + } + + // parse styles (is here only possible for attributes, which also + // can be set at character object) + SfxItemSet aStyleItemSet( m_xDoc->GetAttrPool(), + m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + if( HasStyleOptions( aStyle, aId, aClass ) && + ParseStyleOptions( aStyle, aId, aClass, aStyleItemSet, aPropInfo ) ) + { + SfxItemIter aIter( aStyleItemSet ); + + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + PutEEPoolItem( aItemSet, *pItem ); + } + } + + // now set the size + Size aTwipSz( bPercentWidth ? 0 : nWidth, nHeight ); + if( aTwipSz.Width() || aTwipSz.Height() ) + { + aTwipSz = o3tl::convert(aTwipSz, o3tl::Length::px, o3tl::Length::twip); + } + + if( SVX_CSS1_LTYPE_TWIP== aPropInfo.m_eWidthType ) + { + aTwipSz.setWidth( aPropInfo.m_nWidth ); + nWidth = 1; // != 0; + bPercentWidth = false; + } + if( SVX_CSS1_LTYPE_TWIP== aPropInfo.m_eHeightType ) + aTwipSz.setHeight( aPropInfo.m_nHeight ); + + m_bFixMarqueeWidth = false; + if( !nWidth || bPercentWidth ) + { + if( m_xTable ) + { + if( !pCurTable ) + { + // The marquee is in a table, but not in a cell. Since now no + // reasonable mapping to a cell is possible, we adjust here the + // width to the content of the marquee. + m_bFixMarqueeWidth = true; + } + else if( !nWidth ) + { + // Because we know in which cell the marquee is, we also can + // adjust the width. No width specification is treated as + // 100 percent. + nWidth = 100; + bPercentWidth = true; + } + aTwipSz.setWidth( MINLAY ); + } + else + { + tools::Long nBrowseWidth = GetCurrentBrowseWidth(); + aTwipSz.setWidth( !nWidth ? nBrowseWidth + : (nWidth*nBrowseWidth) / 100 ); + } + } + + // The height is only minimum height + if( aTwipSz.Height() < MINFLY ) + aTwipSz.setHeight( MINFLY ); + aItemSet.Put( makeSdrTextMinFrameHeightItem( aTwipSz.Height() ) ); + + m_pMarquee->SetMergedItemSetAndBroadcast(aItemSet); + + if( aTwipSz.Width() < MINFLY ) + aTwipSz.setWidth( MINFLY ); + m_pMarquee->SetLogicRect( tools::Rectangle( 0, 0, aTwipSz.Width(), aTwipSz.Height() ) ); + + // and insert the object into the document + InsertDrawObject( m_pMarquee.get(), aSpace, eVertOri, eHoriOri, aStyleItemSet, + aPropInfo ); + + // Register the drawing object at the table. Is a little bit complicated, + // because it is done via the parser, although the table is known, but + // otherwise the table would have to be public and that also isn't pretty. + // The global pTable also can't be used, because the marquee can also be + // in a sub-table. + if( pCurTable && bPercentWidth) + RegisterDrawObjectToTable( pCurTable, m_pMarquee.get(), static_cast<sal_uInt8>(nWidth) ); +} + +void SwHTMLParser::EndMarquee() +{ + OSL_ENSURE( m_pMarquee && SdrObjKind::Text==m_pMarquee->GetObjIdentifier(), + "no marquee or wrong type" ); + + if( m_bFixMarqueeWidth ) + { + // Because there is no fixed height make the text object wider then + // the text, so that there is no line break. + const tools::Rectangle& rOldRect = m_pMarquee->GetLogicRect(); + m_pMarquee->SetLogicRect( tools::Rectangle( rOldRect.TopLeft(), + Size( USHRT_MAX, 240 ) ) ); + } + + // insert the collected text + m_pMarquee->SetText( m_aContents ); + m_pMarquee->SetMergedItemSetAndBroadcast( m_pMarquee->GetMergedItemSet() ); + + if (m_bFixMarqueeWidth && !bFuzzing) + { + // adjust the size to the text + m_pMarquee->FitFrameToTextSize(); + } + + m_aContents.clear(); + m_pMarquee = nullptr; +} + +void SwHTMLParser::InsertMarqueeText() +{ + OSL_ENSURE( m_pMarquee && SdrObjKind::Text==m_pMarquee->GetObjIdentifier(), + "no marquee or wrong type" ); + + // append the current text part to the text + m_aContents += aToken; +} + +void SwHTMLParser::ResizeDrawObject( SdrObject* pObj, SwTwips nWidth ) +{ + OSL_ENSURE( SdrObjKind::Text==pObj->GetObjIdentifier(), + "no marquee or wrong type" ); + + if( SdrObjKind::Text!=pObj->GetObjIdentifier() ) + return; + + // the old size + const tools::Rectangle& rOldRect = pObj->GetLogicRect(); + Size aNewSz( nWidth, rOldRect.GetSize().Height() ); + pObj->SetLogicRect( tools::Rectangle( rOldRect.TopLeft(), aNewSz ) ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmldrawwriter.cxx b/sw/source/filter/html/htmldrawwriter.cxx new file mode 100644 index 0000000000..cf37c1948b --- /dev/null +++ b/sw/source/filter/html/htmldrawwriter.cxx @@ -0,0 +1,284 @@ +/* -*- 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 <hintids.hxx> +#include <vcl/svapp.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdotext.hxx> +#include <svx/sdtacitm.hxx> +#include <svx/sdtayitm.hxx> +#include <svx/sdtaaitm.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/outliner.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xflclit.hxx> +#include <svl/whiter.hxx> +#include <svtools/htmlout.hxx> +#include <svtools/htmlkywd.hxx> +#include <osl/diagnose.h> + +#include <rtl/strbuf.hxx> + +#include <IDocumentDrawModelAccess.hxx> +#include <frmfmt.hxx> +#include <doc.hxx> +#include <dcontact.hxx> + +#include "wrthtml.hxx" + + +using namespace css; + +const HtmlFrmOpts HTML_FRMOPTS_MARQUEE = + HtmlFrmOpts::Align | + HtmlFrmOpts::Space; + +const HtmlFrmOpts HTML_FRMOPTS_MARQUEE_CSS1 = + HtmlFrmOpts::SAlign | + HtmlFrmOpts::SSpace; + +const SdrObject *SwHTMLWriter::GetMarqueeTextObj( const SwDrawFrameFormat& rFormat ) +{ + const SdrObject* pObj = rFormat.FindSdrObject(); + return (pObj && ::IsMarqueeTextObj( *pObj )) ? pObj : nullptr; +} + +void SwHTMLWriter::GetEEAttrsFromDrwObj( SfxItemSet& rItemSet, + const SdrObject *pObj ) +{ + // get the edit script::Engine attributes from object + const SfxItemSet& rObjItemSet = pObj->GetMergedItemSet(); + + // iterate over Edit script::Engine attributes and convert them + // into SW-Attrs resp. set default + SfxWhichIter aIter( rObjItemSet ); + sal_uInt16 nEEWhich = aIter.FirstWhich(); + while( nEEWhich ) + { + const SfxPoolItem *pEEItem; + bool bSet = SfxItemState::SET == aIter.GetItemState( false, &pEEItem ); + + sal_uInt16 nSwWhich = 0; + switch( nEEWhich ) + { + case EE_CHAR_COLOR: nSwWhich = RES_CHRATR_COLOR; break; + case EE_CHAR_STRIKEOUT: nSwWhich = RES_CHRATR_CROSSEDOUT; break; + case EE_CHAR_ESCAPEMENT: nSwWhich = RES_CHRATR_ESCAPEMENT; break; + case EE_CHAR_FONTINFO: nSwWhich = RES_CHRATR_FONT; break; + case EE_CHAR_FONTINFO_CJK: nSwWhich = RES_CHRATR_CJK_FONT; break; + case EE_CHAR_FONTINFO_CTL: nSwWhich = RES_CHRATR_CTL_FONT; break; + case EE_CHAR_FONTHEIGHT: nSwWhich = RES_CHRATR_FONTSIZE; break; + case EE_CHAR_FONTHEIGHT_CJK:nSwWhich = RES_CHRATR_CJK_FONTSIZE; break; + case EE_CHAR_FONTHEIGHT_CTL:nSwWhich = RES_CHRATR_CTL_FONTSIZE; break; + case EE_CHAR_KERNING: nSwWhich = RES_CHRATR_KERNING; break; + case EE_CHAR_ITALIC: nSwWhich = RES_CHRATR_POSTURE; break; + case EE_CHAR_ITALIC_CJK: nSwWhich = RES_CHRATR_CJK_POSTURE; break; + case EE_CHAR_ITALIC_CTL: nSwWhich = RES_CHRATR_CTL_POSTURE; break; + case EE_CHAR_UNDERLINE: nSwWhich = RES_CHRATR_UNDERLINE; break; + case EE_CHAR_WEIGHT: nSwWhich = RES_CHRATR_WEIGHT; break; + case EE_CHAR_WEIGHT_CJK: nSwWhich = RES_CHRATR_CJK_WEIGHT; break; + case EE_CHAR_WEIGHT_CTL: nSwWhich = RES_CHRATR_CTL_WEIGHT; break; + } + + if( nSwWhich ) + { + // if the item isn't set we maybe take the default item + if( !bSet ) + pEEItem = &rObjItemSet.GetPool()->GetDefaultItem(nEEWhich); + + // now we clone the item with the which id of the writer + rItemSet.Put( pEEItem->CloneSetWhich(nSwWhich) ); + } + + nEEWhich = aIter.NextWhich(); + } +} + +SwHTMLWriter& OutHTML_DrawFrameFormatAsMarquee( SwHTMLWriter& rWrt, + const SwDrawFrameFormat& rFormat, + const SdrObject& rSdrObject ) +{ + OSL_ENSURE( rWrt.m_pDoc->getIDocumentDrawModelAccess().GetDrawModel(), + "There is a Draw-Obj with no Draw-Model?" ); + const SdrTextObj *pTextObj = static_cast<const SdrTextObj *>(&rSdrObject); + + // Is there text to output + const OutlinerParaObject *pOutlinerParaObj = + pTextObj->GetOutlinerParaObject(); + if( !pOutlinerParaObj ) + return rWrt; + + OStringBuffer sOut("<" OOO_STRING_SVTOOLS_HTML_marquee); + + // get attributes of the object + const SfxItemSet& rItemSet = pTextObj->GetMergedItemSet(); + + // BEHAVIOUR + SdrTextAniKind eAniKind = pTextObj->GetTextAniKind(); + OSL_ENSURE( SdrTextAniKind::Scroll==eAniKind || + SdrTextAniKind::Alternate==eAniKind || + SdrTextAniKind::Slide==eAniKind, + "Text-Draw-Object not suitable for marquee" ); + + const char *pStr = nullptr; + switch( eAniKind ) + { + case SdrTextAniKind::Scroll: pStr = OOO_STRING_SVTOOLS_HTML_BEHAV_scroll; break; + case SdrTextAniKind::Slide: pStr = OOO_STRING_SVTOOLS_HTML_BEHAV_slide; break; + case SdrTextAniKind::Alternate: pStr = OOO_STRING_SVTOOLS_HTML_BEHAV_alternate; break; + default: + ; + } + + if( pStr ) + { + sOut.append(OString::Concat(" " OOO_STRING_SVTOOLS_HTML_O_behavior "=\"") + + pStr + "\""); + } + + // DIRECTION + pStr = nullptr; + SdrTextAniDirection eAniDir = pTextObj->GetTextAniDirection(); + switch( eAniDir ) + { + case SdrTextAniDirection::Left: pStr = OOO_STRING_SVTOOLS_HTML_AL_left; break; + case SdrTextAniDirection::Right: pStr = OOO_STRING_SVTOOLS_HTML_AL_right; break; + default: + ; + } + + if( pStr ) + { + sOut.append(OString::Concat(" " OOO_STRING_SVTOOLS_HTML_O_direction + "=\"") + pStr + "\""); + } + + // LOOP + sal_Int32 nCount = rItemSet.Get( SDRATTR_TEXT_ANICOUNT ).GetValue(); + if( 0==nCount ) + nCount = SdrTextAniKind::Slide==eAniKind ? 1 : -1; + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_loop "=\"" + + OString::number(nCount) + "\""); + + // SCROLLDELAY + sal_uInt16 nDelay = rItemSet.Get( SDRATTR_TEXT_ANIDELAY ).GetValue(); + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_scrolldelay + "=\"" + OString::number(nDelay) + "\""); + + // SCROLLAMOUNT + sal_Int16 nAmount = rItemSet.Get( SDRATTR_TEXT_ANIAMOUNT ).GetValue(); + if( nAmount < 0 ) + { + nAmount = -nAmount; + } + else + { + nAmount = SwHTMLWriter::ToPixel(nAmount); + } + if( nAmount ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_scrollamount + "=\"" + OString::number(nAmount) + "\""); + } + + Size aTwipSz( pTextObj->GetLogicRect().GetSize() ); + if( pTextObj->IsAutoGrowWidth() ) + aTwipSz.setWidth( 0 ); + // The height is at MS a minimum height, therefore we output the minimum + // height, if they exist. Because a minimum height MINFLY is coming with + // high probability from import, we aren't outputting it. You can't + // do anything wrong, because every font is higher. + if( pTextObj->IsAutoGrowHeight() ) + { + aTwipSz.setHeight( pTextObj->GetMinTextFrameHeight() ); + if( MINFLY==aTwipSz.Height() ) + aTwipSz.setHeight( 0 ); + } + + if( (aTwipSz.Width() || aTwipSz.Height()) && + Application::GetDefaultDevice() ) + { + Size aPixelSz = + Application::GetDefaultDevice()->LogicToPixel( aTwipSz, + MapMode(MapUnit::MapTwip) ); + if( !aPixelSz.Width() && aTwipSz.Width() ) + aPixelSz.setWidth( 1 ); + if( !aPixelSz.Height() && aTwipSz.Height() ) + aPixelSz.setHeight( 1 ); + + if( aPixelSz.Width() ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_width + "=\"" + OString::number(aPixelSz.Width()) + "\""); + } + + if( aPixelSz.Height() ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_height + "=\"" + OString::number(aPixelSz.Height()) + "\""); + } + } + + // BGCOLOR + drawing::FillStyle eFillStyle = + rItemSet.Get(XATTR_FILLSTYLE).GetValue(); + if( drawing::FillStyle_SOLID==eFillStyle ) + { + const Color& rFillColor = + rItemSet.Get(XATTR_FILLCOLOR).GetColorValue(); + + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_bgcolor "="); + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_Color( rWrt.Strm(), rFillColor ); + } + + if (!sOut.isEmpty()) + { + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + } + + // and now ALIGN, HSPACE and VSPACE + HtmlFrmOpts nFrameFlags = HTML_FRMOPTS_MARQUEE; + if( rWrt.IsHTMLMode( HTMLMODE_ABS_POS_DRAW ) ) + nFrameFlags |= HTML_FRMOPTS_MARQUEE_CSS1; + OString aEndTags = rWrt.OutFrameFormatOptions(rFormat, OUString(), nFrameFlags); + if( rWrt.IsHTMLMode( HTMLMODE_ABS_POS_DRAW ) ) + rWrt.OutCSS1_FrameFormatOptions( rFormat, nFrameFlags, &rSdrObject ); + + rWrt.Strm().WriteChar( '>' ); + + // What follows now is the counterpart of SdrTextObject::SetText() + Outliner aOutliner(nullptr, OutlinerMode::TextObject); + aOutliner.SetUpdateLayout( false ); + aOutliner.SetText( *pOutlinerParaObj ); + OUString aText( aOutliner.GetText( aOutliner.GetParagraph(0), + aOutliner.GetParagraphCount() ) ); + HTMLOutFuncs::Out_String( rWrt.Strm(), aText ); + + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_marquee), false ); + + if( !aEndTags.isEmpty() ) + rWrt.Strm().WriteOString( aEndTags ); + + return rWrt; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlfld.cxx b/sw/source/filter/html/htmlfld.cxx new file mode 100644 index 0000000000..c1eff7ef7c --- /dev/null +++ b/sw/source/filter/html/htmlfld.cxx @@ -0,0 +1,651 @@ +/* -*- 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/document/XDocumentProperties.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <o3tl/string_view.hxx> +#include <osl/diagnose.h> +#include <docsh.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <svtools/htmltokn.h> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <unotools/useroptions.hxx> +#include <fmtfld.hxx> +#include <ndtxt.hxx> +#include <doc.hxx> +#include <fldbas.hxx> +#include <docufld.hxx> +#include <flddat.hxx> +#include "htmlfld.hxx" +#include "swhtml.hxx" + +using namespace nsSwDocInfoSubType; +using namespace ::com::sun::star; + +namespace { + +struct HTMLNumFormatTableEntry +{ + std::string_view pName; + NfIndexTableOffset eFormat; +}; + +} + +HTMLOptionEnum<SwFieldIds> const aHTMLFieldTypeTable[] = +{ + { OOO_STRING_SW_HTML_FT_author, SwFieldIds::Author }, + { OOO_STRING_SW_HTML_FT_sender, SwFieldIds::ExtUser }, + { "DATE", SwFieldIds::Date }, + { "TIME", SwFieldIds::Time }, + { OOO_STRING_SW_HTML_FT_datetime, SwFieldIds::DateTime }, + { OOO_STRING_SW_HTML_FT_page, SwFieldIds::PageNumber }, + { OOO_STRING_SW_HTML_FT_docinfo, SwFieldIds::DocInfo }, + { OOO_STRING_SW_HTML_FT_docstat, SwFieldIds::DocStat }, + { OOO_STRING_SW_HTML_FT_filename, SwFieldIds::Filename }, + { nullptr, SwFieldIds(0) } +}; + +HTMLNumFormatTableEntry const aHTMLDateFieldFormatTable[] = +{ + { "SSYS", NF_DATE_SYSTEM_SHORT }, + { "LSYS", NF_DATE_SYSTEM_LONG }, + { "DMY", NF_DATE_SYS_DDMMYY, }, + { "DMYY", NF_DATE_SYS_DDMMYYYY, }, + { "DMMY", NF_DATE_SYS_DMMMYY, }, + { "DMMYY", NF_DATE_SYS_DMMMYYYY, }, + { "DMMMY", NF_DATE_DIN_DMMMMYYYY }, + { "DMMMYY", NF_DATE_DIN_DMMMMYYYY }, + { "DDMMY", NF_DATE_SYS_NNDMMMYY }, + { "DDMMMY", NF_DATE_SYS_NNDMMMMYYYY }, + { "DDMMMYY", NF_DATE_SYS_NNDMMMMYYYY }, + { "DDDMMMY", NF_DATE_SYS_NNNNDMMMMYYYY }, + { "DDDMMMYY", NF_DATE_SYS_NNNNDMMMMYYYY }, + { "MY", NF_DATE_SYS_MMYY }, + { "MD", NF_DATE_DIN_MMDD }, + { "YMD", NF_DATE_DIN_YYMMDD }, + { "YYMD", NF_DATE_DIN_YYYYMMDD }, + { {}, NF_NUMERIC_START } +}; + +HTMLNumFormatTableEntry const aHTMLTimeFieldFormatTable[] = +{ + { "SYS", NF_TIME_HHMMSS }, + { "SSMM24", NF_TIME_HHMM }, + { "SSMM12", NF_TIME_HHMMAMPM }, + { {}, NF_NUMERIC_START } +}; + +HTMLOptionEnum<SvxNumType> const aHTMLPageNumFieldFormatTable[] = +{ + { OOO_STRING_SW_HTML_FF_uletter, SVX_NUM_CHARS_UPPER_LETTER }, + { OOO_STRING_SW_HTML_FF_lletter, SVX_NUM_CHARS_LOWER_LETTER }, + { OOO_STRING_SW_HTML_FF_uroman, SVX_NUM_ROMAN_UPPER }, + { OOO_STRING_SW_HTML_FF_lroman, SVX_NUM_ROMAN_LOWER }, + { OOO_STRING_SW_HTML_FF_arabic, SVX_NUM_ARABIC }, + { OOO_STRING_SW_HTML_FF_none, SVX_NUM_NUMBER_NONE }, + { OOO_STRING_SW_HTML_FF_char, SVX_NUM_CHAR_SPECIAL }, + { OOO_STRING_SW_HTML_FF_page, SVX_NUM_PAGEDESC }, + { OOO_STRING_SW_HTML_FF_ulettern, SVX_NUM_CHARS_UPPER_LETTER_N }, + { OOO_STRING_SW_HTML_FF_llettern, SVX_NUM_CHARS_LOWER_LETTER_N }, + { nullptr, SvxNumType(0) } +}; + +HTMLOptionEnum<SwExtUserSubType> const aHTMLExtUsrFieldSubTable[] = +{ + { OOO_STRING_SW_HTML_FS_company, EU_COMPANY }, + { OOO_STRING_SW_HTML_FS_firstname, EU_FIRSTNAME }, + { OOO_STRING_SW_HTML_FS_name, EU_NAME }, + { OOO_STRING_SW_HTML_FS_shortcut, EU_SHORTCUT }, + { OOO_STRING_SW_HTML_FS_street, EU_STREET }, + { OOO_STRING_SW_HTML_FS_country, EU_COUNTRY }, + { OOO_STRING_SW_HTML_FS_zip, EU_ZIP }, + { OOO_STRING_SW_HTML_FS_city, EU_CITY }, + { OOO_STRING_SW_HTML_FS_title, EU_TITLE }, + { OOO_STRING_SW_HTML_FS_position, EU_POSITION }, + { OOO_STRING_SW_HTML_FS_pphone, EU_PHONE_PRIVATE }, + { OOO_STRING_SW_HTML_FS_cphone, EU_PHONE_COMPANY }, + { OOO_STRING_SW_HTML_FS_fax, EU_FAX }, + { OOO_STRING_SW_HTML_FS_email, EU_EMAIL }, + { OOO_STRING_SW_HTML_FS_state, EU_STATE }, + { nullptr, SwExtUserSubType(0) } +}; + +HTMLOptionEnum<SwAuthorFormat> const aHTMLAuthorFieldFormatTable[] = +{ + { OOO_STRING_SW_HTML_FF_name, AF_NAME }, + { OOO_STRING_SW_HTML_FF_shortcut, AF_SHORTCUT }, + { nullptr, SwAuthorFormat(0) } +}; + +HTMLOptionEnum<SwPageNumSubType> const aHTMLPageNumFieldSubTable[] = +{ + { OOO_STRING_SW_HTML_FS_random, PG_RANDOM }, + { OOO_STRING_SW_HTML_FS_next, PG_NEXT }, + { OOO_STRING_SW_HTML_FS_prev, PG_PREV }, + { nullptr, SwPageNumSubType(0) } +}; + +// UGLY: these are extensions of nsSwDocInfoSubType (in inc/docufld.hxx) +// these are necessary for importing document info fields written by +// older versions of OOo (< 3.0) which did not have DI_CUSTOM fields + const SwDocInfoSubType DI_INFO1 = DI_SUBTYPE_END + 1; + const SwDocInfoSubType DI_INFO2 = DI_SUBTYPE_END + 2; + const SwDocInfoSubType DI_INFO3 = DI_SUBTYPE_END + 3; + const SwDocInfoSubType DI_INFO4 = DI_SUBTYPE_END + 4; + +HTMLOptionEnum<sal_uInt16> const aHTMLDocInfoFieldSubTable[] = +{ + { OOO_STRING_SW_HTML_FS_title, DI_TITLE }, + { OOO_STRING_SW_HTML_FS_theme, DI_SUBJECT }, + { OOO_STRING_SW_HTML_FS_keys, DI_KEYS }, + { OOO_STRING_SW_HTML_FS_comment, DI_COMMENT }, + { "INFO1", DI_INFO1 }, + { "INFO2", DI_INFO2 }, + { "INFO3", DI_INFO3 }, + { "INFO4", DI_INFO4 }, + { OOO_STRING_SW_HTML_FS_custom, DI_CUSTOM }, + { OOO_STRING_SW_HTML_FS_create, DI_CREATE }, + { OOO_STRING_SW_HTML_FS_change, DI_CHANGE }, + { nullptr, 0 } +}; + +HTMLOptionEnum<sal_uInt16> const aHTMLDocInfoFieldFormatTable[] = +{ + { OOO_STRING_SW_HTML_FF_author, DI_SUB_AUTHOR }, + { OOO_STRING_SW_HTML_FF_time, DI_SUB_TIME }, + { OOO_STRING_SW_HTML_FF_date, DI_SUB_DATE }, + { nullptr, 0 } +}; + +HTMLOptionEnum<SwDocStatSubType> const aHTMLDocStatFieldSubTable[] = +{ + { OOO_STRING_SW_HTML_FS_page, DS_PAGE }, + { OOO_STRING_SW_HTML_FS_para, DS_PARA }, + { OOO_STRING_SW_HTML_FS_word, DS_WORD }, + { OOO_STRING_SW_HTML_FS_char, DS_CHAR }, + { OOO_STRING_SW_HTML_FS_tbl, DS_TBL }, + { OOO_STRING_SW_HTML_FS_grf, DS_GRF }, + { OOO_STRING_SW_HTML_FS_ole, DS_OLE }, + { nullptr, SwDocStatSubType(0) } +}; + +HTMLOptionEnum<SwFileNameFormat> const aHTMLFileNameFieldFormatTable[] = +{ + { OOO_STRING_SW_HTML_FF_name, FF_NAME }, + { OOO_STRING_SW_HTML_FF_pathname, FF_PATHNAME }, + { OOO_STRING_SW_HTML_FF_path, FF_PATH }, + { OOO_STRING_SW_HTML_FF_name_noext, FF_NAME_NOEXT }, + { nullptr, SwFileNameFormat(0) } +}; + +SvxNumType SwHTMLParser::GetNumType( std::u16string_view rStr, SvxNumType nDfltType ) +{ + const HTMLOptionEnum<SvxNumType> *pOptEnums = aHTMLPageNumFieldFormatTable; + while( pOptEnums->pName ) + { + if( o3tl::equalsIgnoreAsciiCase( rStr, pOptEnums->pName ) ) + return pOptEnums->nValue; + pOptEnums++; + } + return nDfltType; +} + +void SwHTMLParser::NewField() +{ + bool bKnownType = false, bFixed = false, + bHasNumFormat = false, bHasNumValue = false; + SwFieldIds nType = SwFieldIds::Database; + OUString aValue, aNumFormat, aNumValue, aName; + const HTMLOption *pSubOption=nullptr, *pFormatOption=nullptr; + + const HTMLOptions& rHTMLOptions = GetOptions(); + size_t i; + + for ( i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::TYPE: + bKnownType = rOption.GetEnum( nType, aHTMLFieldTypeTable ); + break; + case HtmlOptionId::SUBTYPE: + pSubOption = &rOption; + break; + case HtmlOptionId::FORMAT: + pFormatOption = &rOption; + break; + case HtmlOptionId::NAME: + aName = rOption.GetString(); + break; + case HtmlOptionId::VALUE: + aValue = rOption.GetString(); + break; + case HtmlOptionId::SDNUM: + aNumFormat = rOption.GetString(); + bHasNumFormat = true; + break; + case HtmlOptionId::SDVAL: + aNumValue = rOption.GetString(); + bHasNumValue = true; + break; + case HtmlOptionId::SDFIXED: + bFixed = true; + break; + default: break; + } + } + + if( !bKnownType ) + return; + + // Author and sender are only inserted as a variable field if the document + // was last changed by ourself or nobody changed it and it was created + // by ourself. Otherwise it will be a fixed field. + if( !bFixed && + (SwFieldIds::ExtUser == nType || + SwFieldIds::Author == nType) ) + { + SvtUserOptions aOpt; + const OUString& rUser = aOpt.GetFullName(); + SwDocShell *pDocShell(m_xDoc->GetDocShell()); + OSL_ENSURE(pDocShell, "no SwDocShell"); + if (pDocShell) { + uno::Reference<document::XDocumentPropertiesSupplier> xDPS( + pDocShell->GetModel(), uno::UNO_QUERY_THROW); + uno::Reference<document::XDocumentProperties> xDocProps( + xDPS->getDocumentProperties()); + OSL_ENSURE(xDocProps.is(), "Doc has no DocumentProperties"); + const OUString& rChanged = xDocProps->getModifiedBy(); + const OUString& rCreated = xDocProps->getAuthor(); + if( rUser.isEmpty() || + (!rChanged.isEmpty() ? rUser != rChanged : rUser != rCreated) ) + bFixed = true; + } + } + + SwFieldIds nWhich = nType; + if( SwFieldIds::Date==nType || SwFieldIds::Time==nType ) + nWhich = SwFieldIds::DateTime; + + SwFieldType* pType = m_xDoc->getIDocumentFieldsAccess().GetSysFieldType( nWhich ); + std::unique_ptr<SwField> xNewField; + bool bInsOnEndTag = false; + + switch( nType ) + { + case SwFieldIds::ExtUser: + if( pSubOption ) + { + SwExtUserSubType nSub; + sal_uLong nFormat = 0; + if( bFixed ) + { + nFormat |= AF_FIXED; + bInsOnEndTag = true; + } + if( pSubOption->GetEnum( nSub, aHTMLExtUsrFieldSubTable ) ) + xNewField.reset(new SwExtUserField(static_cast<SwExtUserFieldType*>(pType), nSub, nFormat)); + } + break; + + case SwFieldIds::Author: + { + SwAuthorFormat nFormat = AF_NAME; + if( pFormatOption ) + pFormatOption->GetEnum( nFormat, aHTMLAuthorFieldFormatTable ); + if( bFixed ) + { + nFormat = static_cast<SwAuthorFormat>(static_cast<int>(nFormat) | AF_FIXED); + bInsOnEndTag = true; + } + + xNewField.reset(new SwAuthorField(static_cast<SwAuthorFieldType*>(pType), nFormat)); + } + break; + + case SwFieldIds::Date: + case SwFieldIds::Time: + { + sal_uInt32 nNumFormat = 0; + DateTime aDateTime( DateTime::SYSTEM ); + sal_Int64 nTime = aDateTime.GetTime(); + sal_Int32 nDate = aDateTime.GetDate(); + sal_uInt16 nSub = 0; + bool bValidFormat = false; + HTMLNumFormatTableEntry const * pFormatTable; + + if( SwFieldIds::Date==nType ) + { + nSub = DATEFLD; + pFormatTable = aHTMLDateFieldFormatTable; + if( !aValue.isEmpty() ) + nDate = aValue.toInt32(); + } + else + { + nSub = TIMEFLD; + pFormatTable = aHTMLTimeFieldFormatTable; + if( !aValue.isEmpty() ) + nTime = static_cast<sal_uLong>(aValue.toInt32()); + } + if( !aValue.isEmpty() ) + nSub |= FIXEDFLD; + + SvNumberFormatter *pFormatter = m_xDoc->GetNumberFormatter(); + if( pFormatOption ) + { + const OUString& rFormat = pFormatOption->GetString(); + for( int k = 0; !pFormatTable[k].pName.empty(); ++k ) + { + if( rFormat.equalsIgnoreAsciiCaseAscii( pFormatTable[k].pName ) ) + { + nNumFormat = pFormatter->GetFormatIndex( + pFormatTable[k].eFormat, LANGUAGE_SYSTEM); + bValidFormat = true; + break; + } + } + } + if( !bValidFormat ) + nNumFormat = pFormatter->GetFormatIndex( pFormatTable[i].eFormat, + LANGUAGE_SYSTEM); + + xNewField.reset(new SwDateTimeField(static_cast<SwDateTimeFieldType *>(pType), nSub, nNumFormat)); + + if (nSub & FIXEDFLD) + static_cast<SwDateTimeField *>(xNewField.get())->SetDateTime(DateTime(Date(nDate), tools::Time(nTime))); + } + break; + + case SwFieldIds::DateTime: + if( bHasNumFormat ) + { + sal_uInt16 nSub = 0; + + SvNumberFormatter *pFormatter = m_xDoc->GetNumberFormatter(); + sal_uInt32 nNumFormat; + LanguageType eLang; + double dValue = GetTableDataOptionsValNum( + nNumFormat, eLang, aNumValue, aNumFormat, + *m_xDoc->GetNumberFormatter() ); + SvNumFormatType nFormatType = pFormatter->GetType( nNumFormat ); + switch( nFormatType ) + { + case SvNumFormatType::DATE: nSub = DATEFLD; break; + case SvNumFormatType::TIME: nSub = TIMEFLD; break; + default: break; + } + + if( nSub ) + { + if( bHasNumValue ) + nSub |= FIXEDFLD; + + xNewField.reset(new SwDateTimeField(static_cast<SwDateTimeFieldType *>(pType), nSub, nNumFormat)); + if (bHasNumValue) + static_cast<SwDateTimeField*>(xNewField.get())->SetValue(dValue); + } + } + break; + + case SwFieldIds::PageNumber: + if( pSubOption ) + { + SwPageNumSubType nSub; + if( pSubOption->GetEnum( nSub, aHTMLPageNumFieldSubTable ) ) + { + SvxNumType nFormat = SVX_NUM_PAGEDESC; + if( pFormatOption ) + pFormatOption->GetEnum( nFormat, aHTMLPageNumFieldFormatTable ); + + short nOff = 0; + + if( nFormat!=SVX_NUM_CHAR_SPECIAL && !aValue.isEmpty() ) + nOff = static_cast<short>(aValue.toInt32()); + else if( nSub == PG_NEXT ) + nOff = 1; + else if( nSub == PG_PREV ) + nOff = -1; + + if( nFormat==SVX_NUM_CHAR_SPECIAL && + nSub==PG_RANDOM ) + nFormat = SVX_NUM_PAGEDESC; + + xNewField.reset(new SwPageNumberField(static_cast<SwPageNumberFieldType*>(pType), nSub, nFormat, nOff)); + if (nFormat == SVX_NUM_CHAR_SPECIAL) + static_cast<SwPageNumberField*>(xNewField.get())->SetUserString(aValue); + } + } + break; + + case SwFieldIds::DocInfo: + if( pSubOption ) + { + sal_uInt16 nSub; + if( pSubOption->GetEnum( nSub, aHTMLDocInfoFieldSubTable ) ) + { + sal_uInt16 nExtSub = 0; + if( DI_CREATE==static_cast<SwDocInfoSubType>(nSub) || + DI_CHANGE==static_cast<SwDocInfoSubType>(nSub) ) + { + nExtSub = DI_SUB_AUTHOR; + if( pFormatOption ) + pFormatOption->GetEnum( nExtSub, aHTMLDocInfoFieldFormatTable ); + nSub |= nExtSub; + } + + sal_uInt32 nNumFormat = 0; + double dValue = 0; + if( bHasNumFormat && (DI_SUB_DATE==nExtSub || DI_SUB_TIME==nExtSub) ) + { + LanguageType eLang; + dValue = GetTableDataOptionsValNum( + nNumFormat, eLang, aNumValue, aNumFormat, + *m_xDoc->GetNumberFormatter() ); + bFixed &= bHasNumValue; + } + else + bHasNumValue = false; + + if( nSub >= DI_INFO1 && nSub <= DI_INFO4 && aName.isEmpty() ) + { + // backward compatibility for OOo 2: + // map to names stored in AddMetaUserDefined + aName = m_InfoNames[nSub - DI_INFO1]; + nSub = DI_CUSTOM; + } + + if( bFixed ) + { + nSub |= DI_SUB_FIXED; + bInsOnEndTag = true; + } + + xNewField.reset(new SwDocInfoField(static_cast<SwDocInfoFieldType *>(pType), nSub, aName, nNumFormat)); + if (bHasNumValue) + static_cast<SwDocInfoField*>(xNewField.get())->SetValue(dValue); + } + } + break; + + case SwFieldIds::DocStat: + if( pSubOption ) + { + SwDocStatSubType nSub; + if( pSubOption->GetEnum( nSub, aHTMLDocStatFieldSubTable ) ) + { + SvxNumType nFormat = SVX_NUM_ARABIC; + if( pFormatOption ) + pFormatOption->GetEnum( nFormat, aHTMLPageNumFieldFormatTable ); + xNewField.reset(new SwDocStatField(static_cast<SwDocStatFieldType*>(pType), nSub, nFormat)); + m_bUpdateDocStat |= (DS_PAGE != nSub); + } + } + break; + + case SwFieldIds::Filename: + { + SwFileNameFormat nFormat = FF_NAME; + if( pFormatOption ) + pFormatOption->GetEnum( nFormat, aHTMLFileNameFieldFormatTable ); + if( bFixed ) + { + nFormat = static_cast<SwFileNameFormat>(static_cast<int>(nFormat) | FF_FIXED); + bInsOnEndTag = true; + } + + xNewField.reset(new SwFileNameField(static_cast<SwFileNameFieldType*>(pType), nFormat)); + } + break; + default: + ; + } + + if (!xNewField) + return; + + if (bInsOnEndTag) + { + m_xField = std::move(xNewField); + } + else + { + m_xDoc->getIDocumentContentOperations().InsertPoolItem(*m_pPam, SwFormatField(*xNewField)); + xNewField.reset(); + } + m_bInField = true; +} + +void SwHTMLParser::EndField() +{ + if (m_xField) + { + switch (m_xField->Which()) + { + case SwFieldIds::DocInfo: + OSL_ENSURE( static_cast<SwDocInfoField*>(m_xField.get())->IsFixed(), + "Field DocInfo should not have been saved" ); + static_cast<SwDocInfoField*>(m_xField.get())->SetExpansion( m_aContents ); + break; + + case SwFieldIds::ExtUser: + OSL_ENSURE( static_cast<SwExtUserField*>(m_xField.get())->IsFixed(), + "Field ExtUser should not have been saved" ); + static_cast<SwExtUserField*>(m_xField.get())->SetExpansion( m_aContents ); + break; + + case SwFieldIds::Author: + OSL_ENSURE( static_cast<SwAuthorField*>(m_xField.get())->IsFixed(), + "Field Author should not have been saved" ); + static_cast<SwAuthorField*>(m_xField.get())->SetExpansion( m_aContents ); + break; + + case SwFieldIds::Filename: + OSL_ENSURE( static_cast<SwFileNameField*>(m_xField.get())->IsFixed(), + "Field FileName should not have been saved" ); + static_cast<SwFileNameField*>(m_xField.get())->SetExpansion( m_aContents ); + break; + default: break; + } + + m_xDoc->getIDocumentContentOperations().InsertPoolItem( *m_pPam, SwFormatField(*m_xField) ); + m_xField.reset(); + } + + m_bInField = false; + m_aContents.clear(); +} + +void SwHTMLParser::InsertFieldText() +{ + if (m_xField) + { + // append the current text part to the text + m_aContents += aToken; + } +} + +void SwHTMLParser::InsertCommentText( std::string_view pTag ) +{ + bool bEmpty = m_aContents.isEmpty(); + if( !bEmpty ) + m_aContents += "\n"; + + m_aContents += aToken; + if( bEmpty && !pTag.empty() ) + { + m_aContents = OUString::Concat("HTML: <") + OUString::createFromAscii(pTag) + ">" + m_aContents; + } +} + +void SwHTMLParser::InsertComment( const OUString& rComment, std::string_view pTag ) +{ + OUString aComment( rComment ); + if( !pTag.empty() ) + { + aComment += "</" + + OUString::createFromAscii(pTag) + + ">"; + } + + // MIB 24.06.97: If a PostIt should be insert after a space, we + // will insert before the space. Then there are less problems + // during formatting. (bug #40483#) + const sal_Int32 nPos = m_pPam->GetPoint()->GetContentIndex(); + SwTextNode *pTextNd = m_pPam->GetPointNode().GetTextNode(); + bool bMoveFwd = false; + if (nPos>0 && pTextNd && (' ' == pTextNd->GetText()[nPos-1])) + { + bMoveFwd = true; + + SwNodeOffset nNodeIdx = m_pPam->GetPoint()->GetNodeIndex(); + const sal_Int32 nIdx = m_pPam->GetPoint()->GetContentIndex(); + for( auto i = m_aSetAttrTab.size(); i > 0; ) + { + HTMLAttr *pAttr = m_aSetAttrTab[--i]; + if( pAttr->GetStartParagraphIdx() != nNodeIdx || + pAttr->GetStartContent() != nIdx ) + break; + + if( RES_TXTATR_FIELD == pAttr->m_pItem->Which() && + SwFieldIds::Script == static_cast<const SwFormatField *>(pAttr->m_pItem.get())->GetField()->GetTyp()->Which() ) + { + bMoveFwd = false; + break; + } + } + + if( bMoveFwd ) + m_pPam->Move( fnMoveBackward ); + } + + SwPostItField aPostItField( + static_cast<SwPostItFieldType*>(m_xDoc->getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::Postit )), + OUString(), aComment, OUString(), OUString(), DateTime(DateTime::SYSTEM)); + InsertAttr( SwFormatField( aPostItField ), false ); + + if( bMoveFwd ) + m_pPam->Move( fnMoveForward ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlfld.hxx b/sw/source/filter/html/htmlfld.hxx new file mode 100644 index 0000000000..80c50959e5 --- /dev/null +++ b/sw/source/filter/html/htmlfld.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 . + */ + +#ifndef INCLUDED_SW_SOURCE_FILTER_HTML_HTMLFLD_HXX +#define INCLUDED_SW_SOURCE_FILTER_HTML_HTMLFLD_HXX + +#define OOO_STRING_SW_HTML_FT_author "AUTHOR" +#define OOO_STRING_SW_HTML_FT_sender "SENDER" +#define OOO_STRING_SW_HTML_FT_datetime "DATETIME" +#define OOO_STRING_SW_HTML_FT_page "PAGE" +#define OOO_STRING_SW_HTML_FT_docinfo "DOCINFO" +#define OOO_STRING_SW_HTML_FT_docstat "DOCSTAT" +#define OOO_STRING_SW_HTML_FT_filename "FILENAME" +#define OOO_STRING_SW_HTML_FS_company "COMPANY" +#define OOO_STRING_SW_HTML_FS_firstname "FIRSTNAME" +#define OOO_STRING_SW_HTML_FS_name "NAME" +#define OOO_STRING_SW_HTML_FS_shortcut "SHORTCUT" +#define OOO_STRING_SW_HTML_FS_street "STREET" +#define OOO_STRING_SW_HTML_FS_country "COUNTRY" +#define OOO_STRING_SW_HTML_FS_zip "ZIP" +#define OOO_STRING_SW_HTML_FS_city "CITY" +#define OOO_STRING_SW_HTML_FS_title "TITLE" +#define OOO_STRING_SW_HTML_FS_position "POSITION" +#define OOO_STRING_SW_HTML_FS_pphone "PPHONE" +#define OOO_STRING_SW_HTML_FS_cphone "CPHONE" +#define OOO_STRING_SW_HTML_FS_fax "FAX" +#define OOO_STRING_SW_HTML_FS_email "EMAIL" +#define OOO_STRING_SW_HTML_FS_state "STATE" +#define OOO_STRING_SW_HTML_FS_random "RANDOM" +#define OOO_STRING_SW_HTML_FS_next "NEXT" +#define OOO_STRING_SW_HTML_FS_prev "PREV" +#define OOO_STRING_SW_HTML_FS_theme "THEME" +#define OOO_STRING_SW_HTML_FS_keys "KEYS" +#define OOO_STRING_SW_HTML_FS_comment "COMMENT" +#define OOO_STRING_SW_HTML_FS_custom "CUSTOM" +#define OOO_STRING_SW_HTML_FS_create "CREATE" +#define OOO_STRING_SW_HTML_FS_change "CHANGE" +#define OOO_STRING_SW_HTML_FS_page "PAGE" +#define OOO_STRING_SW_HTML_FS_para "PARAGRAPH" +#define OOO_STRING_SW_HTML_FS_word "WORD" +#define OOO_STRING_SW_HTML_FS_char "CHAR" +#define OOO_STRING_SW_HTML_FS_tbl "TABLE" +#define OOO_STRING_SW_HTML_FS_grf "GRAPHIC" +#define OOO_STRING_SW_HTML_FS_ole "OLE" +#define OOO_STRING_SW_HTML_FF_name "NAME" +#define OOO_STRING_SW_HTML_FF_shortcut "SHORTCUT" +#define OOO_STRING_SW_HTML_FF_uletter "ULETTER" +#define OOO_STRING_SW_HTML_FF_lletter "LLETTER" +#define OOO_STRING_SW_HTML_FF_uroman "UROMAN" +#define OOO_STRING_SW_HTML_FF_lroman "LROMAN" +#define OOO_STRING_SW_HTML_FF_arabic "ARABIC" +#define OOO_STRING_SW_HTML_FF_none "NONE" +#define OOO_STRING_SW_HTML_FF_char "CHAR" +#define OOO_STRING_SW_HTML_FF_page "PAGE" +#define OOO_STRING_SW_HTML_FF_ulettern "ULETTERN" +#define OOO_STRING_SW_HTML_FF_llettern "LLETTERN" +#define OOO_STRING_SW_HTML_FF_author "AUTHOR" +#define OOO_STRING_SW_HTML_FF_time "TIME" +#define OOO_STRING_SW_HTML_FF_date "DATE" +#define OOO_STRING_SW_HTML_FF_pathname "PATHNAME" +#define OOO_STRING_SW_HTML_FF_path "PATH" +#define OOO_STRING_SW_HTML_FF_name_noext "NAME-NOEXT" + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlfldw.cxx b/sw/source/filter/html/htmlfldw.cxx new file mode 100644 index 0000000000..e8d38608b4 --- /dev/null +++ b/sw/source/filter/html/htmlfldw.cxx @@ -0,0 +1,582 @@ +/* -*- 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/i18n/XBreakIterator.hpp> +#include <comphelper/string.hxx> +#include <comphelper/xmlencode.hxx> +#include <svtools/htmlkywd.hxx> +#include <svtools/htmlout.hxx> +#include <osl/diagnose.h> +#include <o3tl/string_view.hxx> +#include <fmtfld.hxx> +#include <doc.hxx> +#include <docsh.hxx> +#include <view.hxx> +#include <wrtsh.hxx> +#include <breakit.hxx> +#include <ndtxt.hxx> +#include <txtfld.hxx> +#include <fldbas.hxx> +#include <docufld.hxx> +#include <flddat.hxx> +#include <viewopt.hxx> +#include "htmlfld.hxx" +#include "wrthtml.hxx" +#include <rtl/strbuf.hxx> +#include "css1atr.hxx" +#include "css1kywd.hxx" + +using namespace nsSwDocInfoSubType; + +const char *SwHTMLWriter::GetNumFormat( sal_uInt16 nFormat ) +{ + const char *pFormatStr = nullptr; + + switch( static_cast<SvxNumType>(nFormat) ) + { + case SVX_NUM_CHARS_UPPER_LETTER: pFormatStr = OOO_STRING_SW_HTML_FF_uletter; break; + case SVX_NUM_CHARS_LOWER_LETTER: pFormatStr = OOO_STRING_SW_HTML_FF_lletter; break; + case SVX_NUM_ROMAN_UPPER: pFormatStr = OOO_STRING_SW_HTML_FF_uroman; break; + case SVX_NUM_ROMAN_LOWER: pFormatStr = OOO_STRING_SW_HTML_FF_lroman; break; + case SVX_NUM_ARABIC: pFormatStr = OOO_STRING_SW_HTML_FF_arabic; break; + case SVX_NUM_NUMBER_NONE: pFormatStr = OOO_STRING_SW_HTML_FF_none; break; + case SVX_NUM_CHAR_SPECIAL: pFormatStr = OOO_STRING_SW_HTML_FF_char; break; + case SVX_NUM_PAGEDESC: pFormatStr = OOO_STRING_SW_HTML_FF_page; break; + case SVX_NUM_CHARS_UPPER_LETTER_N: pFormatStr = OOO_STRING_SW_HTML_FF_ulettern; break; + case SVX_NUM_CHARS_LOWER_LETTER_N: pFormatStr = OOO_STRING_SW_HTML_FF_llettern; break; + default: + ; + } + + return pFormatStr; +} + +static SwHTMLWriter& OutHTML_SwField( SwHTMLWriter& rWrt, const SwField* pField, + const SwTextNode& rTextNd, sal_Int32 nFieldPos ) +{ + const SwFieldType* pFieldTyp = pField->GetTyp(); + SwFieldIds nField = pFieldTyp->Which(); + sal_uLong nFormat = pField->GetFormat(); + + const char *pTypeStr=nullptr, // TYPE + *pSubStr=nullptr, // SUBTYPE + *pFormatStr=nullptr; // FORMAT (SW) + OUString aValue; // VALUE (SW) + bool bNumFormat=false; // SDNUM (Number-Formatter-Format) + bool bNumValue=false; // SDVAL (Number-Formatter-Value) + double dNumValue = 0.0; // SDVAL (Number-Formatter-Value) + bool bFixed=false; // SDFIXED + OUString aName; // NAME (CUSTOM) + + switch( nField ) + { + case SwFieldIds::ExtUser: + pTypeStr = OOO_STRING_SW_HTML_FT_sender; + switch( static_cast<SwExtUserSubType>(pField->GetSubType()) ) + { + case EU_COMPANY: pSubStr = OOO_STRING_SW_HTML_FS_company; break; + case EU_FIRSTNAME: pSubStr = OOO_STRING_SW_HTML_FS_firstname; break; + case EU_NAME: pSubStr = OOO_STRING_SW_HTML_FS_name; break; + case EU_SHORTCUT: pSubStr = OOO_STRING_SW_HTML_FS_shortcut; break; + case EU_STREET: pSubStr = OOO_STRING_SW_HTML_FS_street; break; + case EU_COUNTRY: pSubStr = OOO_STRING_SW_HTML_FS_country; break; + case EU_ZIP: pSubStr = OOO_STRING_SW_HTML_FS_zip; break; + case EU_CITY: pSubStr = OOO_STRING_SW_HTML_FS_city; break; + case EU_TITLE: pSubStr = OOO_STRING_SW_HTML_FS_title; break; + case EU_POSITION: pSubStr = OOO_STRING_SW_HTML_FS_position; break; + case EU_PHONE_PRIVATE: pSubStr = OOO_STRING_SW_HTML_FS_pphone; break; + case EU_PHONE_COMPANY: pSubStr = OOO_STRING_SW_HTML_FS_cphone; break; + case EU_FAX: pSubStr = OOO_STRING_SW_HTML_FS_fax; break; + case EU_EMAIL: pSubStr = OOO_STRING_SW_HTML_FS_email; break; + case EU_STATE: pSubStr = OOO_STRING_SW_HTML_FS_state; break; + default: + ; + } + OSL_ENSURE( pSubStr, "unknown sub type for SwExtUserField" ); + bFixed = static_cast<const SwExtUserField*>(pField)->IsFixed(); + break; + + case SwFieldIds::Author: + pTypeStr = OOO_STRING_SW_HTML_FT_author; + switch( static_cast<SwAuthorFormat>(nFormat) & 0xff) + { + case AF_NAME: pFormatStr = OOO_STRING_SW_HTML_FF_name; break; + case AF_SHORTCUT: pFormatStr = OOO_STRING_SW_HTML_FF_shortcut; break; + } + OSL_ENSURE( pFormatStr, "unknown format for SwAuthorField" ); + bFixed = static_cast<const SwAuthorField*>(pField)->IsFixed(); + break; + + case SwFieldIds::DateTime: + pTypeStr = OOO_STRING_SW_HTML_FT_datetime; + bNumFormat = true; + if( static_cast<const SwDateTimeField*>(pField)->IsFixed() ) + { + bNumValue = true; + dNumValue = static_cast<const SwDateTimeField*>(pField)->GetValue(); + } + break; + + case SwFieldIds::PageNumber: + { + pTypeStr = OOO_STRING_SW_HTML_FT_page; + SwPageNumSubType eSubType = static_cast<SwPageNumSubType>(pField->GetSubType()); + switch( eSubType ) + { + case PG_RANDOM: pSubStr = OOO_STRING_SW_HTML_FS_random; break; + case PG_NEXT: pSubStr = OOO_STRING_SW_HTML_FS_next; break; + case PG_PREV: pSubStr = OOO_STRING_SW_HTML_FS_prev; break; + } + OSL_ENSURE( pSubStr, "unknown sub type for SwPageNumberField" ); + pFormatStr = SwHTMLWriter::GetNumFormat( static_cast< sal_uInt16 >(nFormat) ); + + if( static_cast<SvxNumType>(nFormat)==SVX_NUM_CHAR_SPECIAL ) + { + aValue = static_cast<const SwPageNumberField *>(pField)->GetUserString(); + } + else + { + const OUString& rValue = pField->GetPar2(); + short nValue = static_cast<short>(rValue.toInt32()); + if( (eSubType == PG_NEXT && nValue!=1) || + (eSubType == PG_PREV && nValue!=-1) || + (eSubType == PG_RANDOM && nValue!=0) ) + { + aValue = rValue; + } + } + } + break; + case SwFieldIds::DocInfo: + { + sal_uInt16 nSubType = pField->GetSubType(); + pTypeStr = OOO_STRING_SW_HTML_FT_docinfo; + sal_uInt16 nExtSubType = nSubType & 0x0f00; + nSubType &= 0x00ff; + + switch( nSubType ) + { + case DI_TITLE: pSubStr = OOO_STRING_SW_HTML_FS_title; break; + case DI_SUBJECT: pSubStr = OOO_STRING_SW_HTML_FS_theme; break; + case DI_KEYS: pSubStr = OOO_STRING_SW_HTML_FS_keys; break; + case DI_COMMENT: pSubStr = OOO_STRING_SW_HTML_FS_comment; break; + case DI_CREATE: pSubStr = OOO_STRING_SW_HTML_FS_create; break; + case DI_CHANGE: pSubStr = OOO_STRING_SW_HTML_FS_change; break; + case DI_CUSTOM: pSubStr = OOO_STRING_SW_HTML_FS_custom; break; + default: pTypeStr = nullptr; break; + } + + if( DI_CUSTOM == nSubType ) { + aName = static_cast<const SwDocInfoField*>(pField)->GetName(); + } + + if( DI_CREATE == nSubType || DI_CHANGE == nSubType ) + { + switch( nExtSubType ) + { + case DI_SUB_AUTHOR: + pFormatStr = OOO_STRING_SW_HTML_FF_author; + break; + case DI_SUB_TIME: + pFormatStr = OOO_STRING_SW_HTML_FF_time; + bNumFormat = true; + break; + case DI_SUB_DATE: + pFormatStr = OOO_STRING_SW_HTML_FF_date; + bNumFormat = true; + break; + } + } + bFixed = static_cast<const SwDocInfoField*>(pField)->IsFixed(); + if( bNumFormat ) + { + if( bFixed ) + { + // For a fixed field output the num value too. + // Fixed fields without number format shouldn't + // exist. See below for OSL_ENSURE(). + dNumValue = static_cast<const SwDocInfoField*>(pField)->GetValue(); + bNumValue = true; + } + else if( !nFormat ) + { + // Non-fixed fields may not have a number format, when + // they come from a 4.0-document. + bNumFormat = false; + } + } + } + break; + + case SwFieldIds::DocStat: + { + pTypeStr = OOO_STRING_SW_HTML_FT_docstat; + sal_uInt16 nSubType = pField->GetSubType(); + switch( nSubType ) + { + case DS_PAGE: pSubStr = OOO_STRING_SW_HTML_FS_page; break; + case DS_PARA: pSubStr = OOO_STRING_SW_HTML_FS_para; break; + case DS_WORD: pSubStr = OOO_STRING_SW_HTML_FS_word; break; + case DS_CHAR: pSubStr = OOO_STRING_SW_HTML_FS_char; break; + case DS_TBL: pSubStr = OOO_STRING_SW_HTML_FS_tbl; break; + case DS_GRF: pSubStr = OOO_STRING_SW_HTML_FS_grf; break; + case DS_OLE: pSubStr = OOO_STRING_SW_HTML_FS_ole; break; + default: pTypeStr = nullptr; break; + } + pFormatStr = SwHTMLWriter::GetNumFormat( static_cast< sal_uInt16 >(nFormat) ); + } + break; + + case SwFieldIds::Filename: + pTypeStr = OOO_STRING_SW_HTML_FT_filename; + switch( static_cast<SwFileNameFormat>(nFormat & ~FF_FIXED) ) + { + case FF_NAME: pFormatStr = OOO_STRING_SW_HTML_FF_name; break; + case FF_PATHNAME: pFormatStr = OOO_STRING_SW_HTML_FF_pathname; break; + case FF_PATH: pFormatStr = OOO_STRING_SW_HTML_FF_path; break; + case FF_NAME_NOEXT: pFormatStr = OOO_STRING_SW_HTML_FF_name_noext; break; + default: + ; + } + bFixed = static_cast<const SwFileNameField*>(pField)->IsFixed(); + OSL_ENSURE( pFormatStr, "unknown format for SwFileNameField" ); + break; + default: break; + } + + // ReqIF-XHTML doesn't allow <sdfield>. + if (rWrt.mbReqIF && pTypeStr) + { + pTypeStr = nullptr; + } + + // Output the <sdfield> tag. + if( pTypeStr ) + { + OStringBuffer sOut("<" + + rWrt.GetNamespace() + + OOO_STRING_SVTOOLS_HTML_sdfield + " " + OOO_STRING_SVTOOLS_HTML_O_type + "=" + + pTypeStr); + if( pSubStr ) + { + sOut.append(OString::Concat(" " OOO_STRING_SVTOOLS_HTML_O_subtype "=") + + pSubStr); + } + if( pFormatStr ) + { + sOut.append(OString::Concat(" " OOO_STRING_SVTOOLS_HTML_O_format "=") + + pFormatStr); + } + if( !aName.isEmpty() ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_name "=\""); + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( rWrt.Strm(), aName ); + sOut.append('\"'); + } + if( !aValue.isEmpty() ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_value "=\""); + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( rWrt.Strm(), aValue ); + sOut.append('\"'); + } + if( bNumFormat ) + { + OSL_ENSURE( nFormat, "number format is 0" ); + sOut.append(HTMLOutFuncs::CreateTableDataOptionsValNum( + bNumValue, dNumValue, nFormat, + *rWrt.m_pDoc->GetNumberFormatter())); + } + if( bFixed ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_sdfixed); + } + sOut.append('>'); + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + } + + // output content of the field + OUString const sExpand( pField->ExpandField(true, nullptr) ); + bool bNeedsCJKProcessing = false; + if( !sExpand.isEmpty() ) + { + sal_uInt16 nScriptType = g_pBreakIt->GetBreakIter()->getScriptType( sExpand, 0 ); + sal_Int32 nPos = g_pBreakIt->GetBreakIter()->endOfScript( sExpand, 0, + nScriptType ); + + sal_uInt16 nScript = + SwHTMLWriter::GetCSS1ScriptForScriptType( nScriptType ); + if( (nPos < sExpand.getLength() && nPos >= 0) || nScript != rWrt.m_nCSS1Script ) + { + bNeedsCJKProcessing = true; + } + } + + if( bNeedsCJKProcessing ) + { + //sequence of (start, end) property ranges we want to + //query + SfxItemSetFixed<RES_CHRATR_FONT, RES_CHRATR_FONTSIZE, + RES_CHRATR_POSTURE, RES_CHRATR_POSTURE, + RES_CHRATR_WEIGHT, RES_CHRATR_WEIGHT, + RES_CHRATR_CJK_FONT, RES_CHRATR_CTL_WEIGHT> + aScriptItemSet( rWrt.m_pDoc->GetAttrPool() ); + rTextNd.GetParaAttr(aScriptItemSet, nFieldPos, nFieldPos+1); + + sal_uInt16 aWesternWhichIds[4] = + { RES_CHRATR_FONT, RES_CHRATR_FONTSIZE, + RES_CHRATR_POSTURE, RES_CHRATR_WEIGHT }; + sal_uInt16 aCJKWhichIds[4] = + { RES_CHRATR_CJK_FONT, RES_CHRATR_CJK_FONTSIZE, + RES_CHRATR_CJK_POSTURE, RES_CHRATR_CJK_WEIGHT }; + sal_uInt16 aCTLWhichIds[4] = + { RES_CHRATR_CTL_FONT, RES_CHRATR_CTL_FONTSIZE, + RES_CHRATR_CTL_POSTURE, RES_CHRATR_CTL_WEIGHT }; + + sal_uInt16 *pRefWhichIds = nullptr; + switch( rWrt.m_nCSS1Script ) + { + case CSS1_OUTMODE_WESTERN: + pRefWhichIds = aWesternWhichIds; + break; + case CSS1_OUTMODE_CJK: + pRefWhichIds = aCJKWhichIds; + break; + case CSS1_OUTMODE_CTL: + pRefWhichIds = aCTLWhichIds; + break; + } + + sal_Int32 nPos = 0; + do + { + sal_uInt16 nScriptType = g_pBreakIt->GetBreakIter()->getScriptType( sExpand, nPos ); + sal_uInt16 nScript = + SwHTMLWriter::GetCSS1ScriptForScriptType( nScriptType ); + sal_Int32 nEndPos = g_pBreakIt->GetBreakIter()->endOfScript( + sExpand, nPos, nScriptType ); + sal_Int32 nChunkLen = nEndPos - nPos; + if( nScript != CSS1_OUTMODE_ANY_SCRIPT && + /* #108791# */ nScript != rWrt.m_nCSS1Script ) + { + sal_uInt16 *pWhichIds = nullptr; + switch( nScript ) + { + case CSS1_OUTMODE_WESTERN: pWhichIds = aWesternWhichIds; break; + case CSS1_OUTMODE_CJK: pWhichIds = aCJKWhichIds; break; + case CSS1_OUTMODE_CTL: pWhichIds = aCTLWhichIds; break; + } + + rWrt.m_bTagOn = true; + + const SfxPoolItem *aItems[5]; + int nItems = 0; + + assert(pWhichIds && pRefWhichIds); + if (pWhichIds && pRefWhichIds) + { + for( int i=0; i<4; i++ ) + { + const SfxPoolItem *pRefItem = + aScriptItemSet.GetItem( pRefWhichIds[i] ); + const SfxPoolItem *pItem = + aScriptItemSet.GetItem( pWhichIds[i] ); + if( pRefItem && pItem && + !(0==i ? swhtml_css1atr_equalFontItems( *pRefItem, *pItem ) + : *pRefItem == *pItem) ) + { + Out( aHTMLAttrFnTab, *pItem, rWrt ); + aItems[nItems++] = pItem; + } + } + } + + HTMLOutFuncs::Out_String( rWrt.Strm(), sExpand.copy( nPos, nChunkLen ) ); + + rWrt.m_bTagOn = false; + while( nItems ) + Out( aHTMLAttrFnTab, *aItems[--nItems], rWrt ); + + } + else + { + HTMLOutFuncs::Out_String( rWrt.Strm(), sExpand.copy( nPos, nChunkLen ) ); + } + nPos = nEndPos; + } + while( nPos < sExpand.getLength() ); + } + else + { + HTMLOutFuncs::Out_String( rWrt.Strm(), sExpand ); + } + + // Output the closing tag. + if( pTypeStr ) + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_sdfield), false ); + + return rWrt; +} + +SwHTMLWriter& OutHTML_SwFormatField( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + const SwFormatField & rField = static_cast<const SwFormatField&>(rHt); + const SwField* pField = rField.GetField(); + const SwFieldType* pFieldTyp = pField->GetTyp(); + + if( SwFieldIds::SetExp == pFieldTyp->Which() && + (nsSwGetSetExpType::GSE_STRING & pField->GetSubType()) ) + { + const bool bOn = pFieldTyp->GetName() == "HTML_ON"; + if (!bOn && pFieldTyp->GetName() != "HTML_OFF") + return rWrt; + + OUString rText(comphelper::string::strip(pField->GetPar2(), ' ')); + rWrt.Strm().WriteChar( '<' ); + if( !bOn ) + rWrt.Strm().WriteChar( '/' ); + // TODO: HTML-Tags are written without entities, that for, characters + // not contained in the destination encoding are lost! + OString sTmp(OUStringToOString(rText, + RTL_TEXTENCODING_UTF8)); + rWrt.Strm().WriteOString( sTmp ).WriteChar( '>' ); + } + else if( SwFieldIds::Postit == pFieldTyp->Which() ) + { + // Comments will be written in ANSI character set, but with system + // line breaks. + const OUString& rComment = pField->GetPar2(); + bool bWritten = false; + + if( (rComment.getLength() >= 6 && rComment.startsWith("<") && rComment.endsWith(">") && + o3tl::equalsIgnoreAsciiCase(rComment.subView( 1, 4 ), u"" OOO_STRING_SVTOOLS_HTML_meta) ) || + (rComment.getLength() >= 7 && + rComment.startsWith( "<!--" ) && + rComment.endsWith( "-->" )) ) + { + // directly output META tags + OUString sComment(convertLineEnd(rComment, GetSystemLineEnd())); + // TODO: HTML-Tags are written without entities, that for, + // characters not contained in the destination encoding are lost! + OString sTmp(OUStringToOString(sComment, + RTL_TEXTENCODING_UTF8)); + rWrt.Strm().WriteOString( sTmp ); + bWritten = true; + } + else if( rComment.getLength() >= 7 && + rComment.endsWith(">") && + rComment.startsWithIgnoreAsciiCase( "HTML:" ) ) + { + OUString sComment(comphelper::string::stripStart(rComment.subView(5), ' ')); + if( '<' == sComment[0] ) + { + sComment = convertLineEnd(sComment, GetSystemLineEnd()); + // TODO: HTML-Tags are written without entities, that for, + // characters not contained in the destination encoding are + // lost! + OString sTmp(OUStringToOString(sComment, + RTL_TEXTENCODING_UTF8)); + rWrt.Strm().WriteOString( sTmp ); + bWritten = true; + } + + } + + if( !bWritten ) + { + OUString sComment(convertLineEnd(rComment, GetSystemLineEnd())); + // TODO: ??? + OString sOut = + "<" OOO_STRING_SVTOOLS_HTML_comment + " " + + OUStringToOString(comphelper::string::encodeForXml(sComment), RTL_TEXTENCODING_UTF8) + + " -->"; + rWrt.Strm().WriteOString( sOut ); + } + } + else if( SwFieldIds::Script == pFieldTyp->Which() ) + { + if (rWrt.IsLFPossible()) + rWrt.OutNewLine( true ); + + bool bURL = static_cast<const SwScriptField *>(pField)->IsCodeURL(); + const OUString& rType = pField->GetPar1(); + OUString aContents, aURL; + if(bURL) + aURL = pField->GetPar2(); + else + aContents = pField->GetPar2(); + + // otherwise is the script content itself. Since only JavaScript + // is in fields, it must be JavaScript ...:) + HTMLOutFuncs::OutScript( rWrt.Strm(), rWrt.GetBaseURL(), aContents, rType, JAVASCRIPT, + aURL, nullptr, nullptr ); + + if (rWrt.IsLFPossible()) + rWrt.OutNewLine( true ); + } + else + { + const SwTextField *pTextField = rField.GetTextField(); + OSL_ENSURE( pTextField, "Where is the txt fld?" ); + if (pTextField && rWrt.m_pDoc->GetDocShell() && rWrt.m_pDoc->GetDocShell()->GetView()) + { + // ReqIF-XHTML doesn't allow specifying a background color. + const SwViewOption* pViewOptions = rWrt.m_pDoc->GetDocShell()->GetView()->GetWrtShell().GetViewOptions(); + bool bFieldShadings = pViewOptions->IsFieldShadings() && !rWrt.mbReqIF; + if (bFieldShadings) + { + // If there is a text portion background started already, that should have priority. + auto it = rWrt.maStartedAttributes.find(RES_CHRATR_BACKGROUND); + if (it != rWrt.maStartedAttributes.end()) + bFieldShadings = it->second <= 0; + } + + if (bFieldShadings) + { + const Color& rColor = pViewOptions->GetFieldShadingsColor(); + OString sOut( + "<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_span + " " OOO_STRING_SVTOOLS_HTML_O_style "=\"" + + sCSS1_P_background + + ": " + + GetCSS1_Color(rColor) + + "\">"); + rWrt.Strm().WriteOString(sOut); + } + + OutHTML_SwField( rWrt, pField, pTextField->GetTextNode(), + pTextField->GetStart() ); + + if (bFieldShadings) + HTMLOutFuncs::Out_AsciiTag( + rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_span), false); + } + } + return rWrt; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlfly.cxx b/sw/source/filter/html/htmlfly.cxx new file mode 100644 index 0000000000..c4fe7dfa10 --- /dev/null +++ b/sw/source/filter/html/htmlfly.cxx @@ -0,0 +1,84 @@ +/* -*- 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 "htmlfly.hxx" + +#include <fmtanchr.hxx> +#include <fmtornt.hxx> +#include <flypos.hxx> + +#include <frmfmt.hxx> +#include <ndindex.hxx> +#include <pam.hxx> +#include <osl/diagnose.h> + +using namespace css; + +SwHTMLPosFlyFrame::SwHTMLPosFlyFrame( const SwPosFlyFrame& rPosFly, + const SdrObject *pSdrObj, + AllHtmlFlags nFlags ) : + m_pFrameFormat( &rPosFly.GetFormat() ), + m_pSdrObject( pSdrObj ), + m_aNodeIndex( rPosFly.GetNode() ), + m_nOrdNum( rPosFly.GetOrdNum() ), + m_nContentIndex( 0 ), + m_nAllFlags( nFlags ) +{ + const SwFormatAnchor& rAnchor = rPosFly.GetFormat().GetAnchor(); + if ((RndStdIds::FLY_AT_CHAR != rAnchor.GetAnchorId()) || + HtmlPosition::Inside != GetOutPos()) + return; + + // Output of auto-bound frames will be a character farther back, + // because then the position aligns with Netscape. + OSL_ENSURE( rAnchor.GetAnchorNode(), "No anchor position?" ); + if( !rAnchor.GetAnchorNode() ) + return; + + m_nContentIndex = rAnchor.GetAnchorContentOffset(); + sal_Int16 eHoriRel = rPosFly.GetFormat().GetHoriOrient(). + GetRelationOrient(); + if( text::RelOrientation::FRAME == eHoriRel || text::RelOrientation::PRINT_AREA == eHoriRel ) + { + const SwContentNode *pCNd = m_aNodeIndex.GetNode().GetContentNode(); + OSL_ENSURE( pCNd, "No Content-Node at PaM position" ); + if( pCNd && m_nContentIndex < pCNd->Len() ) + m_nContentIndex++; + } +} + +bool SwHTMLPosFlyFrame::operator<( const SwHTMLPosFlyFrame& rFrame ) const +{ + if( m_aNodeIndex.GetIndex() == rFrame.m_aNodeIndex.GetIndex() ) + { + if( m_nContentIndex == rFrame.m_nContentIndex ) + { + if( GetOutPos() == rFrame.GetOutPos() ) + return m_nOrdNum < rFrame.m_nOrdNum; + else + return GetOutPos() < rFrame.GetOutPos(); + } + else + return m_nContentIndex < rFrame.m_nContentIndex; + } + else + return m_aNodeIndex.GetIndex() < rFrame.m_aNodeIndex.GetIndex(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlfly.hxx b/sw/source/filter/html/htmlfly.hxx new file mode 100644 index 0000000000..666f7730ec --- /dev/null +++ b/sw/source/filter/html/htmlfly.hxx @@ -0,0 +1,128 @@ +/* -*- 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 <o3tl/sorted_vector.hxx> +#include <o3tl/typed_flags_set.hxx> +#include <sal/types.h> +#include <ndindex.hxx> +#include <memory> + +class SdrObject; +class SwFrameFormat; +class SwPosFlyFrame; + +// ATTENTION: The values of this enum are used directly in the output table!!! +enum SwHTMLFrameType +{ + HTML_FRMTYPE_TABLE, + HTML_FRMTYPE_TABLE_CAP, + HTML_FRMTYPE_MULTICOL, + HTML_FRMTYPE_EMPTY, + HTML_FRMTYPE_TEXT, + HTML_FRMTYPE_GRF, + HTML_FRMTYPE_PLUGIN, + HTML_FRMTYPE_APPLET, + HTML_FRMTYPE_IFRAME, + HTML_FRMTYPE_OLE, + HTML_FRMTYPE_MARQUEE, + HTML_FRMTYPE_CONTROL, + HTML_FRMTYPE_DRAW, + HTML_FRMTYPE_END +}; + +enum class HtmlOut { + TableNode, + GraphicNode, + OleNode, + Div, + MultiCol, + Spacer, + Control, + AMarquee, + Marquee, + GraphicFrame, + OleGraphic, + Span +}; + +enum class HtmlPosition { + Prefix, + Before, + Inside, + Any +}; + +enum class HtmlContainerFlags { + NONE = 0x00, + Span = 0x01, + Div = 0x02, +}; +namespace o3tl { + template<> struct typed_flags<HtmlContainerFlags> : is_typed_flags<HtmlContainerFlags, 0x03> {}; +} + +struct AllHtmlFlags { + HtmlOut nOut; + HtmlPosition nPosition; + HtmlContainerFlags nContainer; +}; + +AllHtmlFlags getHTMLOutFramePageFlyTable(SwHTMLFrameType eFrameType, sal_uInt16 nExportMode); +AllHtmlFlags getHTMLOutFrameParaFrameTable(SwHTMLFrameType eFrameType, sal_uInt16 nExportMode); +AllHtmlFlags getHTMLOutFrameParaPrtAreaTable(SwHTMLFrameType eFrameType, sal_uInt16 nExportMode); +AllHtmlFlags getHTMLOutFrameParaOtherTable(SwHTMLFrameType eFrameType, sal_uInt16 nExportMode); +AllHtmlFlags getHTMLOutFrameAsCharTable(SwHTMLFrameType eFrameType, sal_uInt16 nExportMode); + +class SwHTMLPosFlyFrame +{ + const SwFrameFormat *m_pFrameFormat; // the frame + const SdrObject *m_pSdrObject; // maybe Sdr-Object + SwNodeIndex m_aNodeIndex; // Node-Index + sal_uInt32 m_nOrdNum; // from SwPosFlyFrame + sal_Int32 m_nContentIndex; // its position in content + AllHtmlFlags m_nAllFlags; + + SwHTMLPosFlyFrame(const SwHTMLPosFlyFrame&) = delete; + SwHTMLPosFlyFrame& operator=(const SwHTMLPosFlyFrame&) = delete; + +public: + + SwHTMLPosFlyFrame( const SwPosFlyFrame& rPosFly, + const SdrObject *pSdrObj, AllHtmlFlags nAllFlags ); + + bool operator<( const SwHTMLPosFlyFrame& ) const; + + const SwFrameFormat& GetFormat() const { return *m_pFrameFormat; } + const SdrObject* GetSdrObject() const { return m_pSdrObject; } + const SwNodeIndex& GetNdIndex() const { return m_aNodeIndex; } + sal_Int32 GetContentIndex() const { return m_nContentIndex; } + AllHtmlFlags const & GetOutMode() const { return m_nAllFlags; } + HtmlOut GetOutFn() const { return m_nAllFlags.nOut; } + HtmlPosition GetOutPos() const { return m_nAllFlags.nPosition; } +}; + +class SwHTMLPosFlyFrames + : public o3tl::sorted_vector<std::unique_ptr<SwHTMLPosFlyFrame>, + o3tl::less_uniqueptr_to<SwHTMLPosFlyFrame>, + o3tl::find_partialorder_ptrequals> +{}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlflyt.cxx b/sw/source/filter/html/htmlflyt.cxx new file mode 100644 index 0000000000..9653c0bb06 --- /dev/null +++ b/sw/source/filter/html/htmlflyt.cxx @@ -0,0 +1,487 @@ +/* -*- 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 "htmlfly.hxx" +#include <svtools/htmlcfg.hxx> + +constexpr sal_uInt16 MAX_FRMTYPES = HTML_FRMTYPE_END; +constexpr sal_uInt16 MAX_BROWSERS = HTML_CFG_MAX + 1; + +constexpr AllHtmlFlags aHTMLOutFramePageFlyTable[][MAX_BROWSERS] = +{ + { + // text frame with table + { HtmlOut::Div, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::Div, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // SW + { HtmlOut::Div, HtmlPosition::Prefix, HtmlContainerFlags::NONE } // Netscape 4! + }, + { + // text frame with table and headline + { HtmlOut::Div, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::Div, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // SW + { HtmlOut::Div, HtmlPosition::Prefix, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // column frame + { HtmlOut::GraphicFrame, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::MultiCol, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // SW + { HtmlOut::MultiCol, HtmlPosition::Prefix, HtmlContainerFlags::Div } // Netscape 4 + }, + { + // empty text frame + { HtmlOut::Div, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::Div, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // SW + { HtmlOut::Div, HtmlPosition::Prefix, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // other text frame + { HtmlOut::Div, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::Div, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // SW + { HtmlOut::Div, HtmlPosition::Prefix, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // graphic node + { HtmlOut::GraphicNode, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::GraphicNode, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // SW + { HtmlOut::GraphicNode, HtmlPosition::Prefix, HtmlContainerFlags::Span } // Netscape 4 + }, + { + // plug-in + { HtmlOut::OleNode, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::OleNode, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // SW + { HtmlOut::OleNode, HtmlPosition::Prefix, HtmlContainerFlags::Span } // Netscape 4 + }, + { + // applet + { HtmlOut::OleNode, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::OleNode, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // SW + { HtmlOut::OleNode, HtmlPosition::Prefix, HtmlContainerFlags::Span } // Netscape 4 + }, + { + // floating frame + { HtmlOut::OleNode, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::OleNode, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // SW + { HtmlOut::OleGraphic, HtmlPosition::Prefix, HtmlContainerFlags::Span } // Netscape 4 + }, + { + // other OLE objects + { HtmlOut::OleGraphic, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::OleGraphic, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // SW + { HtmlOut::OleGraphic, HtmlPosition::Prefix, HtmlContainerFlags::Span } // Netscape 4 + }, + { + // marquee + { HtmlOut::AMarquee, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::AMarquee, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // SW + { HtmlOut::GraphicFrame, HtmlPosition::Prefix, HtmlContainerFlags::Span } // Netscape 4 + }, + { + // controls + { HtmlOut::Control, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::Control, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // SW + // Netscape disables FROM at controls in absolute position span. + { HtmlOut::Control, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // other character objects + { HtmlOut::GraphicFrame, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::GraphicFrame, HtmlPosition::Prefix, HtmlContainerFlags::NONE }, // SW + { HtmlOut::GraphicFrame, HtmlPosition::Prefix, HtmlContainerFlags::Span } // Netscape 4 + } +}; + +AllHtmlFlags getHTMLOutFramePageFlyTable(SwHTMLFrameType eFrameType, sal_uInt16 nExportMode) +{ + static_assert(std::size(aHTMLOutFramePageFlyTable) == MAX_FRMTYPES); + assert(eFrameType < HTML_FRMTYPE_END); + assert(nExportMode <= HTML_CFG_MAX); + + return aHTMLOutFramePageFlyTable[eFrameType][nExportMode]; +} + +constexpr AllHtmlFlags aHTMLOutFrameParaFrameTable[][MAX_BROWSERS] = +{ + { + // text frame with table + { HtmlOut::TableNode, HtmlPosition::Before, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::TableNode, HtmlPosition::Before, HtmlContainerFlags::NONE }, // SW + { HtmlOut::TableNode, HtmlPosition::Before, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // text frame with table and headline + { HtmlOut::Div, HtmlPosition::Before, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::Div, HtmlPosition::Before, HtmlContainerFlags::NONE }, // SW + { HtmlOut::TableNode, HtmlPosition::Before, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // column frame + { HtmlOut::GraphicFrame, HtmlPosition::Before, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::MultiCol, HtmlPosition::Before, HtmlContainerFlags::NONE }, // SW + { HtmlOut::MultiCol, HtmlPosition::Before, HtmlContainerFlags::Div } // Netscape 4 + }, + { + // empty text frame + { HtmlOut::Div, HtmlPosition::Before, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::Spacer, HtmlPosition::Before, HtmlContainerFlags::NONE }, // SW + { HtmlOut::Spacer, HtmlPosition::Before, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // other text frame + { HtmlOut::Div, HtmlPosition::Before, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::Div, HtmlPosition::Before, HtmlContainerFlags::NONE }, // SW + { HtmlOut::Div, HtmlPosition::Before, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // graphic node + { HtmlOut::GraphicNode, HtmlPosition::Before, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::GraphicNode, HtmlPosition::Before, HtmlContainerFlags::NONE }, // SW + { HtmlOut::GraphicNode, HtmlPosition::Before, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // plug-in + { HtmlOut::OleNode, HtmlPosition::Before, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::OleNode, HtmlPosition::Before, HtmlContainerFlags::NONE }, // SW + { HtmlOut::OleNode, HtmlPosition::Before, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // applet + { HtmlOut::OleNode, HtmlPosition::Before, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::OleNode, HtmlPosition::Before, HtmlContainerFlags::NONE }, // SW + { HtmlOut::OleNode, HtmlPosition::Before, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // floating frame + { HtmlOut::OleNode, HtmlPosition::Before, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::OleNode, HtmlPosition::Before, HtmlContainerFlags::NONE }, // SW + { HtmlOut::OleGraphic, HtmlPosition::Before, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // other OLE objects + { HtmlOut::OleGraphic, HtmlPosition::Before, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::OleGraphic, HtmlPosition::Before, HtmlContainerFlags::NONE }, // SW + { HtmlOut::OleGraphic, HtmlPosition::Before, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // marquee (for Netscape 4 in container, so that + // the marquee appears at the right spot) + { HtmlOut::AMarquee, HtmlPosition::Before, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::AMarquee, HtmlPosition::Before, HtmlContainerFlags::NONE }, // SW + { HtmlOut::GraphicFrame, HtmlPosition::Before, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // controls + { HtmlOut::Control, HtmlPosition::Before, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::Control, HtmlPosition::Before, HtmlContainerFlags::NONE }, // SW + // here you could make container out if it (import is missing) + { HtmlOut::Control, HtmlPosition::Before, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // other character objects + { HtmlOut::GraphicFrame, HtmlPosition::Before, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::GraphicFrame, HtmlPosition::Before, HtmlContainerFlags::NONE }, // SW + { HtmlOut::GraphicFrame, HtmlPosition::Before, HtmlContainerFlags::NONE } // Netscape 4 + } +}; + +AllHtmlFlags getHTMLOutFrameParaFrameTable(SwHTMLFrameType eFrameType, sal_uInt16 nExportMode) +{ + static_assert(std::size(aHTMLOutFrameParaFrameTable) == MAX_FRMTYPES); + assert(eFrameType < HTML_FRMTYPE_END); + assert(nExportMode <= HTML_CFG_MAX); + + return aHTMLOutFrameParaFrameTable[eFrameType][nExportMode]; +} + +constexpr AllHtmlFlags aHTMLOutFrameParaPrtAreaTable[][MAX_BROWSERS] = +{ + { + // text frame with table + { HtmlOut::TableNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::TableNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::TableNode, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // text frame with table and headline + { HtmlOut::Span, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::Span, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::Span, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // column frame + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::MultiCol, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::MultiCol, HtmlPosition::Inside, HtmlContainerFlags::Span } // Netscape 4 + }, + { + // empty text frame + { HtmlOut::Span, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::Spacer, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::Spacer, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // other text frame + { HtmlOut::Span, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::Span, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::Span, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // graphic node + { HtmlOut::GraphicNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::GraphicNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::GraphicNode, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // plug-in + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // applet + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // floating frame + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::OleGraphic, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // other OLE objects + { HtmlOut::OleGraphic, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::OleGraphic, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::OleGraphic, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // marquee + { HtmlOut::AMarquee, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::AMarquee, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // controls + { HtmlOut::Control, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::Control, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + // here you could make container out if it (import is missing) + { HtmlOut::Control, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // other character objects + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + } +}; + +AllHtmlFlags getHTMLOutFrameParaPrtAreaTable(SwHTMLFrameType eFrameType, sal_uInt16 nExportMode) +{ + static_assert(std::size(aHTMLOutFrameParaPrtAreaTable) == MAX_FRMTYPES); + assert(eFrameType < HTML_FRMTYPE_END); + assert(nExportMode <= HTML_CFG_MAX); + + return aHTMLOutFrameParaPrtAreaTable[eFrameType][nExportMode]; +} + +constexpr AllHtmlFlags aHTMLOutFrameParaOtherTable[][MAX_BROWSERS] = +{ + { + // text frame with table + { HtmlOut::Span, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::Span, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::Span, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // text frame with table and headline + { HtmlOut::Span, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::Span, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::Span, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // column frame + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::MultiCol, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::MultiCol, HtmlPosition::Inside, HtmlContainerFlags::Span } // Netscape 4 + }, + { + // empty text frame + { HtmlOut::Span, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::Span, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::Span, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // other text frame + { HtmlOut::Span, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::Span, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::Span, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // graphic node + { HtmlOut::GraphicNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::GraphicNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::GraphicNode, HtmlPosition::Inside, HtmlContainerFlags::Span } // Netscape 4 + }, + { + // plug-in + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::Span } // Netscape 4 + }, + { + // applet + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::Span } // Netscape 4 + }, + { + // floating frame + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::OleGraphic, HtmlPosition::Inside, HtmlContainerFlags::Span } // Netscape 4 + }, + { + // other OLE objects + { HtmlOut::OleGraphic, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::OleGraphic, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::OleGraphic, HtmlPosition::Inside, HtmlContainerFlags::Span } // Netscape 4 + }, + { + // marquee + { HtmlOut::AMarquee, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::AMarquee, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::Span } // Netscape 4 + }, + { + // controls + { HtmlOut::Control, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::Control, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + // Netscape disables FROM at controls in absolute position span. + { HtmlOut::Control, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // other character objects + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::Span } // Netscape 4 + } +}; + +AllHtmlFlags getHTMLOutFrameParaOtherTable(SwHTMLFrameType eFrameType, sal_uInt16 nExportMode) +{ + static_assert(std::size(aHTMLOutFrameParaOtherTable) == MAX_FRMTYPES); + assert(eFrameType < HTML_FRMTYPE_END); + assert(nExportMode <= HTML_CFG_MAX); + + return aHTMLOutFrameParaOtherTable[eFrameType][nExportMode]; +} + +constexpr AllHtmlFlags aHTMLOutFrameAsCharTable[][MAX_BROWSERS] = +{ + { + // text frame with table + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // text frame with table and headline + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // column frame + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::MultiCol, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::MultiCol, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // empty text frame + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::Spacer, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::Spacer, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // other text frame + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // graphic node + { HtmlOut::GraphicNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::GraphicNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::GraphicNode, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // plug-in + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // applet + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // floating frame + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::OleNode, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::OleGraphic, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // other OLE objects + { HtmlOut::OleGraphic, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::OleGraphic, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::OleGraphic, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // marquee (can always exported as marquee, because + // the content shows up at the right spot + { HtmlOut::Marquee, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::Marquee, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::Marquee, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // controls + { HtmlOut::Control, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::Control, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::Control, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + }, + { + // other character objects + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // IE 4 + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::NONE }, // SW + { HtmlOut::GraphicFrame, HtmlPosition::Inside, HtmlContainerFlags::NONE } // Netscape 4 + } +}; + +AllHtmlFlags getHTMLOutFrameAsCharTable(SwHTMLFrameType eFrameType, sal_uInt16 nExportMode) +{ + static_assert(std::size(aHTMLOutFrameAsCharTable) == MAX_FRMTYPES); + assert(eFrameType < HTML_FRMTYPE_END); + assert(nExportMode <= HTML_CFG_MAX); + + return aHTMLOutFrameAsCharTable[eFrameType][nExportMode]; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlflywriter.cxx b/sw/source/filter/html/htmlflywriter.cxx new file mode 100644 index 0000000000..ef3e4b19c3 --- /dev/null +++ b/sw/source/filter/html/htmlflywriter.cxx @@ -0,0 +1,2257 @@ +/* -*- 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/HoriOrientation.hpp> +#include <com/sun/star/text/VertOrientation.hpp> +#include <com/sun/star/text/RelOrientation.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <hintids.hxx> +#include <tools/fract.hxx> +#include <svl/urihelper.hxx> +#include <vcl/svapp.hxx> +#include <sfx2/event.hxx> +#include <svtools/htmlkywd.hxx> +#include <svtools/htmlout.hxx> +#include <svtools/htmltokn.h> +#include <vcl/imap.hxx> +#include <vcl/imapobj.hxx> +#include <svtools/htmlcfg.hxx> +#include <svtools/HtmlWriter.hxx> +#include <svx/svdouno.hxx> +#include <svx/xoutbmp.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/brushitem.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <svx/svdograf.hxx> +#include <comphelper/xmlencode.hxx> + +#include <fmtanchr.hxx> +#include <fmtornt.hxx> +#include <fmturl.hxx> +#include <fmtfsize.hxx> +#include <fmtclds.hxx> +#include <fmtcntnt.hxx> +#include <fmtsrnd.hxx> +#include <fmtinfmt.hxx> +#include <txtinet.hxx> +#include <frmatr.hxx> +#include <grfatr.hxx> +#include <flypos.hxx> +#include <ndgrf.hxx> + +#include <doc.hxx> +#include <ndtxt.hxx> +#include <pam.hxx> +#include <swerror.h> +#include <frmfmt.hxx> +#include "wrthtml.hxx" +#include "htmlatr.hxx" +#include "htmlfly.hxx" +#include "htmlreqifreader.hxx" + +using namespace css; + +const HtmlFrmOpts HTML_FRMOPTS_IMG_ALL = + HtmlFrmOpts::Alt | + HtmlFrmOpts::Size | + HtmlFrmOpts::AnySize | + HtmlFrmOpts::Border | + HtmlFrmOpts::Name; +const HtmlFrmOpts HTML_FRMOPTS_IMG_CNTNR = + HTML_FRMOPTS_IMG_ALL | + HtmlFrmOpts::AbsSize; +const HtmlFrmOpts HTML_FRMOPTS_IMG = + HTML_FRMOPTS_IMG_ALL | + HtmlFrmOpts::Align | + HtmlFrmOpts::Space | + HtmlFrmOpts::BrClear; +const HtmlFrmOpts HTML_FRMOPTS_IMG_CSS1 = + HtmlFrmOpts::SAlign | + HtmlFrmOpts::SSpace; + +const HtmlFrmOpts HTML_FRMOPTS_DIV = + HtmlFrmOpts::Id | + HtmlFrmOpts::SAlign | + HtmlFrmOpts::SSize | + HtmlFrmOpts::AnySize | + HtmlFrmOpts::AbsSize | + HtmlFrmOpts::SSpace | + HtmlFrmOpts::SBorder | + HtmlFrmOpts::SBackground | + HtmlFrmOpts::BrClear | + HtmlFrmOpts::Dir; + +const HtmlFrmOpts HTML_FRMOPTS_MULTICOL = + HtmlFrmOpts::Id | + HtmlFrmOpts::Width | + HtmlFrmOpts::AnySize | + HtmlFrmOpts::AbsSize | + HtmlFrmOpts::Dir; + +const HtmlFrmOpts HTML_FRMOPTS_MULTICOL_CSS1 = + HtmlFrmOpts::SAlign | + HtmlFrmOpts::SSize | + HtmlFrmOpts::SSpace | + HtmlFrmOpts::SBorder| + HtmlFrmOpts::SBackground; + +const HtmlFrmOpts HTML_FRMOPTS_SPACER = + HtmlFrmOpts::Align | + HtmlFrmOpts::Size | + HtmlFrmOpts::AnySize | + HtmlFrmOpts::BrClear | + HtmlFrmOpts::MarginSize | + HtmlFrmOpts::AbsSize; + +const HtmlFrmOpts HTML_FRMOPTS_CNTNR = + HtmlFrmOpts::SAlign | + HtmlFrmOpts::SSpace | + HtmlFrmOpts::SWidth | + HtmlFrmOpts::AnySize | + HtmlFrmOpts::AbsSize | + HtmlFrmOpts::SPixSize; + +static SwHTMLWriter& OutHTML_FrameFormatTableNode( SwHTMLWriter& rWrt, const SwFrameFormat& rFrameFormat ); +static SwHTMLWriter& OutHTML_FrameFormatAsMulticol( SwHTMLWriter& rWrt, const SwFrameFormat& rFormat, + bool bInCntnr ); +static SwHTMLWriter& OutHTML_FrameFormatAsSpacer( SwHTMLWriter& rWrt, const SwFrameFormat& rFormat ); +static SwHTMLWriter& OutHTML_FrameFormatAsDivOrSpan( SwHTMLWriter& rWrt, + const SwFrameFormat& rFrameFormat, bool bSpan ); +static SwHTMLWriter& OutHTML_FrameFormatAsImage( SwHTMLWriter& rWrt, const SwFrameFormat& rFormat, bool bPNGFallback ); + +static SwHTMLWriter& OutHTML_FrameFormatGrfNode( SwHTMLWriter& rWrt, const SwFrameFormat& rFormat, + bool bInCntnr, bool bPNGFallback ); + +static SwHTMLWriter& OutHTML_FrameFormatAsMarquee( SwHTMLWriter& rWrt, const SwFrameFormat& rFrameFormat, + const SdrObject& rSdrObj ); + +HTMLOutEvent const aImageEventTable[] = +{ + { OOO_STRING_SVTOOLS_HTML_O_SDonload, OOO_STRING_SVTOOLS_HTML_O_onload, SvMacroItemId::OnImageLoadDone }, + { OOO_STRING_SVTOOLS_HTML_O_SDonabort, OOO_STRING_SVTOOLS_HTML_O_onabort, SvMacroItemId::OnImageLoadCancel }, + { OOO_STRING_SVTOOLS_HTML_O_SDonerror, OOO_STRING_SVTOOLS_HTML_O_onerror, SvMacroItemId::OnImageLoadError }, + { nullptr, nullptr, SvMacroItemId::NONE } +}; + +HTMLOutEvent const aIMapEventTable[] = +{ + { OOO_STRING_SVTOOLS_HTML_O_SDonmouseover, OOO_STRING_SVTOOLS_HTML_O_onmouseover, SvMacroItemId::OnMouseOver }, + { OOO_STRING_SVTOOLS_HTML_O_SDonmouseout, OOO_STRING_SVTOOLS_HTML_O_onmouseout, SvMacroItemId::OnMouseOut }, + { nullptr, nullptr, SvMacroItemId::NONE } +}; + +SwHTMLFrameType SwHTMLWriter::GuessFrameType( const SwFrameFormat& rFrameFormat, + const SdrObject*& rpSdrObj ) +{ + SwHTMLFrameType eType; + + if( RES_DRAWFRMFMT == rFrameFormat.Which() ) + { + // use an arbitrary draw object as the default value + eType = HTML_FRMTYPE_DRAW; + + const SdrObject *pObj = + SwHTMLWriter::GetMarqueeTextObj( static_cast<const SwDrawFrameFormat &>(rFrameFormat) ); + if( pObj ) + { + // scrolling text + rpSdrObj = pObj; + eType = HTML_FRMTYPE_MARQUEE; + } + else + { + pObj = GetHTMLControl( static_cast<const SwDrawFrameFormat &>(rFrameFormat) ); + + if( pObj ) + { + // Form control + rpSdrObj = pObj; + eType = HTML_FRMTYPE_CONTROL; + } + } + } + else + { + // use a text frame as the default value + eType = HTML_FRMTYPE_TEXT; + + const SwFormatContent& rFlyContent = rFrameFormat.GetContent(); + SwNodeOffset nStt = rFlyContent.GetContentIdx()->GetIndex()+1; + const SwNode* pNd = m_pDoc->GetNodes()[ nStt ]; + + if( pNd->IsGrfNode() ) + { + // graphic node + eType = HTML_FRMTYPE_GRF; + } + else if( pNd->IsOLENode() ) + { + // applet, plugin, floating frame + eType = GuessOLENodeFrameType( *pNd ); + } + else + { + SwNodeOffset nEnd = m_pDoc->GetNodes()[nStt-1]->EndOfSectionIndex(); + + const SfxItemSet& rItemSet = rFrameFormat.GetAttrSet(); + const SwFormatCol* pFormatCol = rItemSet.GetItemIfSet( RES_COL ); + if( pFormatCol && pFormatCol->GetNumCols() > 1 ) + { + // frame with columns + eType = HTML_FRMTYPE_MULTICOL; + } + else if( pNd->IsTableNode() ) + { + const SwTableNode *pTableNd = pNd->GetTableNode(); + SwNodeOffset nTableEnd = pTableNd->EndOfSectionIndex(); + + if( nTableEnd+1 == nEnd ) + { + // table + eType = HTML_FRMTYPE_TABLE; + } + else if( nTableEnd+2 == nEnd ) + { + // table with caption + eType = HTML_FRMTYPE_TABLE_CAP; + } + } + else if( pNd->IsTextNode() ) + { + const SwTextNode *pTextNd = pNd->GetTextNode(); + + bool bEmpty = false; + if( nStt==nEnd-1 && !pTextNd->Len() ) + { + // empty frame? Only if no frame is + // anchored to the text or start node. + bEmpty = true; + for( auto & pHTMLPosFlyFrame : m_aHTMLPosFlyFrames ) + { + SwNodeOffset nIdx = pHTMLPosFlyFrame->GetNdIndex().GetIndex(); + bEmpty = (nIdx != nStt) && (nIdx != nStt-1); + if( !bEmpty || nIdx > nStt ) + break; + } + } + if( bEmpty ) + { + std::unique_ptr<SvxBrushItem> aBrush = rFrameFormat.makeBackgroundBrushItem(); + /// background is not empty, if it has a background graphic + /// or its background color is not "no fill"/"auto fill". + if( GPOS_NONE != aBrush->GetGraphicPos() || + aBrush->GetColor() != COL_TRANSPARENT ) + { + bEmpty = false; + } + } + if( bEmpty ) + { + // empty frame + eType = HTML_FRMTYPE_EMPTY; + } + else if( m_pDoc->GetNodes()[nStt+1]->IsTableNode() ) + { + const SwTableNode *pTableNd = + m_pDoc->GetNodes()[nStt+1]->GetTableNode(); + if( pTableNd->EndOfSectionIndex()+1 == nEnd ) + { + // table with heading + eType = HTML_FRMTYPE_TABLE_CAP; + } + } + } + } + } + + return eType; +} + +void SwHTMLWriter::CollectFlyFrames() +{ + SwPosFlyFrames aFlyPos( + m_pDoc->GetAllFlyFormats(m_bWriteAll ? nullptr : m_pCurrentPam.get(), true)); + + for(const SwPosFlyFrame& rItem : aFlyPos) + { + const SwFrameFormat& rFrameFormat = rItem.GetFormat(); + const SdrObject *pSdrObj = nullptr; + const SwNode *pAnchorNode; + const SwContentNode *pACNd; + SwHTMLFrameType eType = GuessFrameType( rFrameFormat, pSdrObj ); + + AllHtmlFlags nMode; + const SwFormatAnchor& rAnchor = rFrameFormat.GetAnchor(); + sal_Int16 eHoriRel = rFrameFormat.GetHoriOrient().GetRelationOrient(); + switch( rAnchor.GetAnchorId() ) + { + case RndStdIds::FLY_AT_PAGE: + case RndStdIds::FLY_AT_FLY: + nMode = getHTMLOutFramePageFlyTable(eType, m_nExportMode); + break; + + case RndStdIds::FLY_AT_PARA: + // frames that are anchored to a paragraph are only placed + // before the paragraph, if the paragraph has a + // spacing. + if( text::RelOrientation::FRAME == eHoriRel && + (pAnchorNode = rAnchor.GetAnchorNode()) != nullptr && + (pACNd = pAnchorNode->GetContentNode()) != nullptr ) + { + const SvxTextLeftMarginItem& rTextLeftMargin = + pACNd->GetAttr(RES_MARGIN_TEXTLEFT); + const SvxRightMarginItem& rRightMargin = + pACNd->GetAttr(RES_MARGIN_RIGHT); + if (rTextLeftMargin.GetTextLeft() || rRightMargin.GetRight()) + { + nMode = getHTMLOutFrameParaFrameTable(eType, m_nExportMode); + break; + } + } + nMode = getHTMLOutFrameParaPrtAreaTable(eType, m_nExportMode); + break; + + case RndStdIds::FLY_AT_CHAR: + if( text::RelOrientation::FRAME == eHoriRel || text::RelOrientation::PRINT_AREA == eHoriRel ) + nMode = getHTMLOutFrameParaPrtAreaTable(eType, m_nExportMode); + else + nMode = getHTMLOutFrameParaOtherTable(eType, m_nExportMode); + break; + + default: + nMode = getHTMLOutFrameParaPrtAreaTable(eType, m_nExportMode); + break; + } + + m_aHTMLPosFlyFrames.insert( std::make_unique<SwHTMLPosFlyFrame>(rItem, pSdrObj, nMode) ); + } +} + +bool SwHTMLWriter::OutFlyFrame( SwNodeOffset nNdIdx, sal_Int32 nContentIdx, HtmlPosition nPos ) +{ + bool bFlysLeft = false; // Are there still Flys left at the current node position? + + // OutFlyFrame can be called recursively. Thus, sometimes it is + // necessary to start over after a Fly was returned. + bool bRestart = true; + while( !m_aHTMLPosFlyFrames.empty() && bRestart ) + { + bFlysLeft = bRestart = false; + + // search for the beginning of the FlyFrames + size_t i {0}; + + for( ; i < m_aHTMLPosFlyFrames.size() && + m_aHTMLPosFlyFrames[i]->GetNdIndex().GetIndex() < nNdIdx; i++ ) + ; + for( ; !bRestart && i < m_aHTMLPosFlyFrames.size() && + m_aHTMLPosFlyFrames[i]->GetNdIndex().GetIndex() == nNdIdx; i++ ) + { + SwHTMLPosFlyFrame *pPosFly = m_aHTMLPosFlyFrames[i].get(); + if( ( HtmlPosition::Any == nPos || + pPosFly->GetOutPos() == nPos ) && + pPosFly->GetContentIndex() == nContentIdx ) + { + // It is important to remove it first, because additional + // elements or the whole array could be deleted on + // deeper recursion levels. + std::unique_ptr<SwHTMLPosFlyFrame> flyHolder = m_aHTMLPosFlyFrames.erase_extract(i); + i--; + if( m_aHTMLPosFlyFrames.empty() ) + { + bRestart = true; // not really, only exit the loop + } + + HTMLOutFuncs::FlushToAscii(Strm()); // it was one time only; do we still need it? + + OutFrameFormat( pPosFly->GetOutMode(), pPosFly->GetFormat(), + pPosFly->GetSdrObject() ); + switch( pPosFly->GetOutFn() ) + { + case HtmlOut::Div: + case HtmlOut::Span: + case HtmlOut::MultiCol: + case HtmlOut::TableNode: + bRestart = true; // It could become recursive here + break; + default: break; + } + } + else + { + bFlysLeft = true; + } + } + } + + return bFlysLeft; +} + +void SwHTMLWriter::OutFrameFormat( AllHtmlFlags nMode, const SwFrameFormat& rFrameFormat, + const SdrObject *pSdrObject ) +{ + HtmlContainerFlags nCntnrMode = nMode.nContainer; + HtmlOut nOutMode = nMode.nOut; + OString aContainerStr; + if( HtmlContainerFlags::NONE != nCntnrMode ) + { + + if (IsLFPossible() && HtmlContainerFlags::Div == nCntnrMode) + OutNewLine(); + + OStringBuffer sOut; + aContainerStr = (HtmlContainerFlags::Div == nCntnrMode) + ? OOO_STRING_SVTOOLS_HTML_division + : OOO_STRING_SVTOOLS_HTML_span; + sOut.append("<" + GetNamespace() + aContainerStr + " " + OOO_STRING_SVTOOLS_HTML_O_class "=\"" + "sd-abs-pos\""); + Strm().WriteOString( sOut ); + sOut.setLength(0); + + // Output a width for non-draw objects + HtmlFrmOpts nFrameFlags = HTML_FRMOPTS_CNTNR; + + // For frames with columns we can also output the background + if( HtmlOut::MultiCol == nOutMode ) + nFrameFlags |= HtmlFrmOpts::SBackground|HtmlFrmOpts::SBorder; + + if( IsHTMLMode( HTMLMODE_BORDER_NONE ) ) + nFrameFlags |= HtmlFrmOpts::SNoBorder; + OutCSS1_FrameFormatOptions( rFrameFormat, nFrameFlags, pSdrObject ); + Strm().WriteChar( '>' ); + + if( HtmlContainerFlags::Div == nCntnrMode ) + { + IncIndentLevel(); + SetLFPossible(true); + } + } + + switch( nOutMode ) + { + case HtmlOut::TableNode: // OK + OSL_ENSURE( aContainerStr.isEmpty(), "Table: Container is not supposed to be here" ); + OutHTML_FrameFormatTableNode( *this, rFrameFormat ); + break; + case HtmlOut::GraphicNode: // OK + OutHTML_FrameFormatGrfNode( *this, rFrameFormat, !aContainerStr.isEmpty(), /*bPNGFallback=*/true ); + break; + case HtmlOut::OleNode: // OK + OutHTML_FrameFormatOLENode( *this, rFrameFormat, !aContainerStr.isEmpty() ); + break; + case HtmlOut::OleGraphic: // OK + OutHTML_FrameFormatOLENodeGrf( *this, rFrameFormat, !aContainerStr.isEmpty() ); + break; + case HtmlOut::Div: + case HtmlOut::Span: + OSL_ENSURE( aContainerStr.isEmpty(), "Div: Container is not supposed to be here" ); + OutHTML_FrameFormatAsDivOrSpan( *this, rFrameFormat, HtmlOut::Span==nOutMode ); + break; + case HtmlOut::MultiCol: // OK + OutHTML_FrameFormatAsMulticol( *this, rFrameFormat, !aContainerStr.isEmpty() ); + break; + case HtmlOut::Spacer: // OK + OSL_ENSURE( aContainerStr.isEmpty(), "Spacer: Container is not supposed to be here" ); + OutHTML_FrameFormatAsSpacer( *this, rFrameFormat ); + break; + case HtmlOut::Control: // OK + OutHTML_DrawFrameFormatAsControl( *this, + static_cast<const SwDrawFrameFormat &>(rFrameFormat), dynamic_cast<const SdrUnoObj&>(*pSdrObject), + !aContainerStr.isEmpty() ); + break; + case HtmlOut::AMarquee: + OutHTML_FrameFormatAsMarquee( *this, rFrameFormat, *pSdrObject ); + break; + case HtmlOut::Marquee: + OSL_ENSURE( aContainerStr.isEmpty(), "Marquee: Container is not supposed to be here" ); + OutHTML_DrawFrameFormatAsMarquee( *this, + static_cast<const SwDrawFrameFormat &>(rFrameFormat), *pSdrObject ); + break; + case HtmlOut::GraphicFrame: + OutHTML_FrameFormatAsImage( *this, rFrameFormat, /*bPNGFallback=*/true ); + break; + } + + if( HtmlContainerFlags::Div == nCntnrMode ) + { + DecIndentLevel(); + if (IsLFPossible()) + OutNewLine(); + HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + OOO_STRING_SVTOOLS_HTML_division), false ); + SetLFPossible(true); + } + else if( HtmlContainerFlags::Span == nCntnrMode ) + HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + OOO_STRING_SVTOOLS_HTML_span), false ); +} + +OString SwHTMLWriter::OutFrameFormatOptions( const SwFrameFormat &rFrameFormat, + const OUString& rAlternateText, + HtmlFrmOpts nFrameOpts ) +{ + OString sRetEndTags; + OStringBuffer sOut; + const SfxItemSet& rItemSet = rFrameFormat.GetAttrSet(); + + // Name + if( (nFrameOpts & (HtmlFrmOpts::Id|HtmlFrmOpts::Name)) && + !rFrameFormat.GetName().isEmpty() ) + { + const char *pStr = + (nFrameOpts & HtmlFrmOpts::Id) ? OOO_STRING_SVTOOLS_HTML_O_id : OOO_STRING_SVTOOLS_HTML_O_name; + sOut.append(OString::Concat(" ") + pStr + "=\""); + Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( Strm(), rFrameFormat.GetName() ); + sOut.append('\"'); + } + + // Name + if( nFrameOpts & HtmlFrmOpts::Dir ) + { + SvxFrameDirection nDir = GetHTMLDirection( rItemSet ); + Strm().WriteOString( sOut ); + sOut.setLength(0); + OutDirection( nDir ); + } + + // ALT + if( (nFrameOpts & HtmlFrmOpts::Alt) && !rAlternateText.isEmpty() ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_alt "=\""); + Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( Strm(), rAlternateText ); + sOut.append('\"'); + } + + // ALIGN + const char *pStr = nullptr; + RndStdIds eAnchorId = rFrameFormat.GetAnchor().GetAnchorId(); + if( (nFrameOpts & HtmlFrmOpts::Align) && + ((RndStdIds::FLY_AT_PARA == eAnchorId) || (RndStdIds::FLY_AT_CHAR == eAnchorId)) ) + { + // MIB 12.3.98: Wouldn't it be more clever to left-align frames that + // are anchored to a paragraph if necessary, instead of inserting them + // as being anchored to characters? + const SwFormatHoriOrient& rHoriOri = rFrameFormat.GetHoriOrient(); + if( !(nFrameOpts & HtmlFrmOpts::SAlign) || + text::RelOrientation::FRAME == rHoriOri.GetRelationOrient() || + text::RelOrientation::PRINT_AREA == rHoriOri.GetRelationOrient() ) + { + pStr = text::HoriOrientation::RIGHT == rHoriOri.GetHoriOrient() + ? OOO_STRING_SVTOOLS_HTML_AL_right + : OOO_STRING_SVTOOLS_HTML_AL_left; + } + } + const SwFormatVertOrient* pVertOrient; + if( (nFrameOpts & HtmlFrmOpts::Align) && !pStr && + ( !(nFrameOpts & HtmlFrmOpts::SAlign) || + (RndStdIds::FLY_AS_CHAR == eAnchorId) ) && + (pVertOrient = rItemSet.GetItemIfSet( RES_VERT_ORIENT )) ) + { + switch( pVertOrient->GetVertOrient() ) + { + case text::VertOrientation::LINE_TOP: pStr = OOO_STRING_SVTOOLS_HTML_VA_top; break; + case text::VertOrientation::CHAR_TOP: + case text::VertOrientation::BOTTOM: pStr = OOO_STRING_SVTOOLS_HTML_VA_texttop; break; // not possible + case text::VertOrientation::LINE_CENTER: + case text::VertOrientation::CHAR_CENTER: pStr = OOO_STRING_SVTOOLS_HTML_VA_absmiddle; break; // not possible + case text::VertOrientation::CENTER: pStr = OOO_STRING_SVTOOLS_HTML_VA_middle; break; + case text::VertOrientation::LINE_BOTTOM: + case text::VertOrientation::CHAR_BOTTOM: pStr = OOO_STRING_SVTOOLS_HTML_VA_absbottom; break; // not possible + case text::VertOrientation::TOP: pStr = OOO_STRING_SVTOOLS_HTML_VA_bottom; break; + case text::VertOrientation::NONE: break; + } + } + if( pStr ) + { + sOut.append(OString::Concat(" " OOO_STRING_SVTOOLS_HTML_O_align "=\"") + + pStr + "\""); + } + + // HSPACE and VSPACE + Size aTwipSpc( 0, 0 ); + const SvxLRSpaceItem* pLRSpaceItem; + if( (nFrameOpts & (HtmlFrmOpts::Space|HtmlFrmOpts::MarginSize)) && + (pLRSpaceItem = rItemSet.GetItemIfSet( RES_LR_SPACE )) ) + { + aTwipSpc.setWidth( + ( pLRSpaceItem->GetLeft() + pLRSpaceItem->GetRight() ) / 2 ); + m_nDfltLeftMargin = m_nDfltRightMargin = aTwipSpc.Width(); + } + const SvxULSpaceItem* pULSpaceItem; + if( (nFrameOpts & (HtmlFrmOpts::Space|HtmlFrmOpts::MarginSize)) && + (pULSpaceItem = rItemSet.GetItemIfSet( RES_UL_SPACE )) ) + { + aTwipSpc.setHeight( + ( pULSpaceItem->GetUpper() + pULSpaceItem->GetLower() ) / 2 ); + m_nDfltTopMargin = m_nDfltBottomMargin = o3tl::narrowing<sal_uInt16>(aTwipSpc.Height()); + } + + if( (nFrameOpts & HtmlFrmOpts::Space) && + (aTwipSpc.Width() || aTwipSpc.Height()) && + !mbReqIF ) + { + Size aPixelSpc = SwHTMLWriter::ToPixel(aTwipSpc); + + if( aPixelSpc.Width() ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_hspace + "=\"" + OString::number(aPixelSpc.Width()) + "\""); + } + + if( aPixelSpc.Height() ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_vspace + "=\"" + OString::number(aPixelSpc.Height()) + "\""); + } + } + + // The spacing must be considered for the size, if the corresponding flag + // is set. + if( nFrameOpts & HtmlFrmOpts::MarginSize ) + { + aTwipSpc.setWidth( aTwipSpc.Width() * -2 ); + aTwipSpc.setHeight( aTwipSpc.Height() * -2 ); + } + else + { + aTwipSpc.setWidth( 0 ); + aTwipSpc.setHeight( 0 ); + } + + const SvxBoxItem* pBoxItem; + if( !(nFrameOpts & HtmlFrmOpts::AbsSize) && + (pBoxItem = rItemSet.GetItemIfSet( RES_BOX )) ) + { + aTwipSpc.AdjustWidth(pBoxItem->CalcLineSpace( SvxBoxItemLine::LEFT ) ); + aTwipSpc.AdjustWidth(pBoxItem->CalcLineSpace( SvxBoxItemLine::RIGHT ) ); + aTwipSpc.AdjustHeight(pBoxItem->CalcLineSpace( SvxBoxItemLine::TOP ) ); + aTwipSpc.AdjustHeight(pBoxItem->CalcLineSpace( SvxBoxItemLine::BOTTOM ) ); + } + + // WIDTH and/or HEIGHT + // Output SwFrameSize::Variable/SwFrameSize::Minimum only, if ANYSIZE is set + const SwFormatFrameSize *pFSItem; + if( (nFrameOpts & HtmlFrmOpts::Size) && + (pFSItem = rItemSet.GetItemIfSet( RES_FRM_SIZE )) && + ( (nFrameOpts & HtmlFrmOpts::AnySize) || + SwFrameSize::Fixed == pFSItem->GetHeightSizeType()) ) + { + sal_uInt8 nPercentWidth = pFSItem->GetWidthPercent(); + sal_uInt8 nPercentHeight = pFSItem->GetHeightPercent(); + + // Size of the object in Twips without margins + Size aTwipSz( (nPercentWidth ? 0 + : pFSItem->GetWidth()-aTwipSpc.Width()), + (nPercentHeight ? 0 + : pFSItem->GetHeight()-aTwipSpc.Height()) ); + + OSL_ENSURE( aTwipSz.Width() >= 0 && aTwipSz.Height() >= 0, "Frame size minus spacing < 0!!!???" ); + if( aTwipSz.Width() < 0 ) + aTwipSz.setWidth( 0 ); + if( aTwipSz.Height() < 0 ) + aTwipSz.setHeight( 0 ); + + Size aPixelSz(SwHTMLWriter::ToPixel(aTwipSz)); + + if( (nFrameOpts & HtmlFrmOpts::Width) && + ((nPercentWidth && nPercentWidth!=255) || aPixelSz.Width()) ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_width "=\""); + if( nPercentWidth ) + sOut.append(OString::number(static_cast<sal_Int32>(nPercentWidth)) + "%"); + else + sOut.append(static_cast<sal_Int32>(aPixelSz.Width())); + sOut.append("\""); + } + + if( (nFrameOpts & HtmlFrmOpts::Height) && + ((nPercentHeight && nPercentHeight!=255) || aPixelSz.Height()) ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_height "=\""); + if( nPercentHeight ) + sOut.append(OString::number(static_cast<sal_Int32>(nPercentHeight)) + "%"); + else + sOut.append(static_cast<sal_Int32>(aPixelSz.Height())); + sOut.append("\""); + } + } + + if (!sOut.isEmpty()) + { + Strm().WriteOString( sOut ); + sOut.setLength(0); + } + + if (!mbReqIF) + { + // Insert wrap for graphics that are anchored to a paragraph as + // <BR CLEAR=...> in the string + const SwFormatSurround* pSurround; + if( (nFrameOpts & HtmlFrmOpts::BrClear) && + ((RndStdIds::FLY_AT_PARA == rFrameFormat.GetAnchor().GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == rFrameFormat.GetAnchor().GetAnchorId())) && + (pSurround = rItemSet.GetItemIfSet( RES_SURROUND )) ) + { + sal_Int16 eHoriOri = rFrameFormat.GetHoriOrient().GetHoriOrient(); + pStr = nullptr; + css::text::WrapTextMode eSurround = pSurround->GetSurround(); + bool bAnchorOnly = pSurround->IsAnchorOnly(); + switch( eHoriOri ) + { + case text::HoriOrientation::RIGHT: + { + switch( eSurround ) + { + case css::text::WrapTextMode_NONE: + case css::text::WrapTextMode_RIGHT: + pStr = OOO_STRING_SVTOOLS_HTML_AL_right; + break; + case css::text::WrapTextMode_LEFT: + case css::text::WrapTextMode_PARALLEL: + if( bAnchorOnly ) + m_bClearRight = true; + break; + default: + ; + } + } + break; + + default: + // If a frame is centered, it gets left aligned. This + // should be taken into account here, too. + { + switch( eSurround ) + { + case css::text::WrapTextMode_NONE: + case css::text::WrapTextMode_LEFT: + pStr = OOO_STRING_SVTOOLS_HTML_AL_left; + break; + case css::text::WrapTextMode_RIGHT: + case css::text::WrapTextMode_PARALLEL: + if( bAnchorOnly ) + m_bClearLeft = true; + break; + default: + ; + } + } + break; + + } + + if( pStr ) + { + sOut.append("<" OOO_STRING_SVTOOLS_HTML_linebreak + " " OOO_STRING_SVTOOLS_HTML_O_clear + "=\"" + OString::Concat(pStr) + "\">"); + sRetEndTags = sOut.makeStringAndClear(); + } + } + } + return sRetEndTags; +} + +void SwHTMLWriter::writeFrameFormatOptions(HtmlWriter& aHtml, const SwFrameFormat& rFrameFormat, const OUString& rAlternateText, HtmlFrmOpts nFrameOptions) +{ + bool bReplacement = (nFrameOptions & HtmlFrmOpts::Replacement) || mbReqIF; + const SfxItemSet& rItemSet = rFrameFormat.GetAttrSet(); + + // Name + if( (nFrameOptions & (HtmlFrmOpts::Id|HtmlFrmOpts::Name)) && + !rFrameFormat.GetName().isEmpty() && !bReplacement) + { + const char* pAttributeName = (nFrameOptions & HtmlFrmOpts::Id) ? OOO_STRING_SVTOOLS_HTML_O_id : OOO_STRING_SVTOOLS_HTML_O_name; + aHtml.attribute(pAttributeName, rFrameFormat.GetName()); + } + + // Name + if (nFrameOptions & HtmlFrmOpts::Dir) + { + SvxFrameDirection nCurrentDirection = GetHTMLDirection(rItemSet); + OString sDirection = convertDirection(nCurrentDirection); + aHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_dir, sDirection); + } + + // alt + if( (nFrameOptions & HtmlFrmOpts::Alt) && !rAlternateText.isEmpty() && !bReplacement ) + { + aHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_alt, rAlternateText); + } + + // align + std::string_view pAlignString; + RndStdIds eAnchorId = rFrameFormat.GetAnchor().GetAnchorId(); + if( (nFrameOptions & HtmlFrmOpts::Align) && + ((RndStdIds::FLY_AT_PARA == eAnchorId) || (RndStdIds::FLY_AT_CHAR == eAnchorId)) && !bReplacement) + { + const SwFormatHoriOrient& rHoriOri = rFrameFormat.GetHoriOrient(); + if( !(nFrameOptions & HtmlFrmOpts::SAlign) || + text::RelOrientation::FRAME == rHoriOri.GetRelationOrient() || + text::RelOrientation::PRINT_AREA == rHoriOri.GetRelationOrient() ) + { + pAlignString = text::HoriOrientation::RIGHT == rHoriOri.GetHoriOrient() + ? std::string_view(OOO_STRING_SVTOOLS_HTML_AL_right) + : std::string_view(OOO_STRING_SVTOOLS_HTML_AL_left); + } + } + const SwFormatVertOrient* pVertOrient; + if( (nFrameOptions & HtmlFrmOpts::Align) && pAlignString.empty() && + ( !(nFrameOptions & HtmlFrmOpts::SAlign) || + (RndStdIds::FLY_AS_CHAR == eAnchorId) ) && + (pVertOrient = rItemSet.GetItemIfSet( RES_VERT_ORIENT )) ) + { + switch( pVertOrient->GetVertOrient() ) + { + case text::VertOrientation::LINE_TOP: pAlignString = OOO_STRING_SVTOOLS_HTML_VA_top; break; + case text::VertOrientation::CHAR_TOP: + case text::VertOrientation::BOTTOM: pAlignString = OOO_STRING_SVTOOLS_HTML_VA_texttop; break; + case text::VertOrientation::LINE_CENTER: + case text::VertOrientation::CHAR_CENTER: pAlignString = OOO_STRING_SVTOOLS_HTML_VA_absmiddle; break; + case text::VertOrientation::CENTER: pAlignString = OOO_STRING_SVTOOLS_HTML_VA_middle; break; + case text::VertOrientation::LINE_BOTTOM: + case text::VertOrientation::CHAR_BOTTOM: pAlignString = OOO_STRING_SVTOOLS_HTML_VA_absbottom; break; + case text::VertOrientation::TOP: pAlignString = OOO_STRING_SVTOOLS_HTML_VA_bottom; break; + case text::VertOrientation::NONE: break; + } + } + if (!pAlignString.empty() && !bReplacement) + { + aHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_align, pAlignString); + } + + // hspace and vspace + Size aTwipSpc( 0, 0 ); + const SvxLRSpaceItem* pLRSpaceItem; + if( (nFrameOptions & (HtmlFrmOpts::Space | HtmlFrmOpts::MarginSize)) && + (pLRSpaceItem = rItemSet.GetItemIfSet( RES_LR_SPACE )) ) + { + aTwipSpc.setWidth( + ( pLRSpaceItem->GetLeft() + pLRSpaceItem->GetRight() ) / 2 ); + m_nDfltLeftMargin = m_nDfltRightMargin = aTwipSpc.Width(); + } + const SvxULSpaceItem* pULSpaceItem; + if( (nFrameOptions & (HtmlFrmOpts::Space|HtmlFrmOpts::MarginSize)) && + (pULSpaceItem = rItemSet.GetItemIfSet( RES_UL_SPACE )) ) + { + aTwipSpc.setHeight( + ( pULSpaceItem->GetUpper() + pULSpaceItem->GetLower() ) / 2 ); + m_nDfltTopMargin = m_nDfltBottomMargin = o3tl::narrowing<sal_uInt16>(aTwipSpc.Height()); + } + + if( (nFrameOptions & HtmlFrmOpts::Space) && + (aTwipSpc.Width() || aTwipSpc.Height()) && + !mbReqIF ) + { + Size aPixelSpc = SwHTMLWriter::ToPixel(aTwipSpc); + + if (aPixelSpc.Width()) + { + aHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_hspace, static_cast<sal_Int32>(aPixelSpc.Width())); + } + + if (aPixelSpc.Height()) + { + aHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_vspace, static_cast<sal_Int32>(aPixelSpc.Height())); + } + } + + // The spacing must be considered for the size, if the corresponding flag + // is set. + if( nFrameOptions & HtmlFrmOpts::MarginSize ) + { + aTwipSpc.setWidth( aTwipSpc.Width() * -2 ); + aTwipSpc.setHeight( aTwipSpc.Height() * -2 ); + } + else + { + aTwipSpc.setWidth( 0 ); + aTwipSpc.setHeight( 0 ); + } + + const SvxBoxItem* pBoxItem; + if( !(nFrameOptions & HtmlFrmOpts::AbsSize) && + (pBoxItem = rItemSet.GetItemIfSet( RES_BOX )) ) + { + aTwipSpc.AdjustWidth(pBoxItem->CalcLineSpace( SvxBoxItemLine::LEFT ) ); + aTwipSpc.AdjustWidth(pBoxItem->CalcLineSpace( SvxBoxItemLine::RIGHT ) ); + aTwipSpc.AdjustHeight(pBoxItem->CalcLineSpace( SvxBoxItemLine::TOP ) ); + aTwipSpc.AdjustHeight(pBoxItem->CalcLineSpace( SvxBoxItemLine::BOTTOM ) ); + } + + // "width" and/or "height" + // Only output SwFrameSize::Variable/SwFrameSize::Minimum if ANYSIZE is set + std::optional<SwFormatFrameSize> aFrameSize; + const SwFormatFrameSize* pFSItem = rItemSet.GetItemIfSet( RES_FRM_SIZE ); + const SdrObject* pObject; + if (!pFSItem && (pObject = rFrameFormat.FindSdrObject())) + { + // Write size for Draw shapes as well. + const tools::Rectangle& rSnapRect = pObject->GetSnapRect(); + aFrameSize.emplace(); + aFrameSize->SetWidthSizeType(SwFrameSize::Fixed); + aFrameSize->SetWidth(rSnapRect.getOpenWidth()); + aFrameSize->SetHeightSizeType(SwFrameSize::Fixed); + aFrameSize->SetHeight(rSnapRect.getOpenHeight()); + pFSItem = &*aFrameSize; + } + if( (nFrameOptions & HtmlFrmOpts::Size) && + pFSItem && + ( (nFrameOptions & HtmlFrmOpts::AnySize) || + SwFrameSize::Fixed == pFSItem->GetHeightSizeType()) ) + { + sal_uInt8 nPercentWidth = pFSItem->GetWidthPercent(); + sal_uInt8 nPercentHeight = pFSItem->GetHeightPercent(); + + // Size of the object in Twips without margins + Size aTwipSz( (nPercentWidth && nPercentWidth != 255 ? 0 + : pFSItem->GetWidth()-aTwipSpc.Width()), + (nPercentHeight && nPercentHeight != 255 ? 0 + : pFSItem->GetHeight()-aTwipSpc.Height()) ); + + OSL_ENSURE( aTwipSz.Width() >= 0 && aTwipSz.Height() >= 0, "Frame size minus spacing < 0!!!???" ); + if( aTwipSz.Width() < 0 ) + aTwipSz.setWidth( 0 ); + if( aTwipSz.Height() < 0 ) + aTwipSz.setHeight( 0 ); + + Size aPixelSz(SwHTMLWriter::ToPixel(aTwipSz)); + + if( (nFrameOptions & HtmlFrmOpts::Width) && + ((nPercentWidth && nPercentWidth!=255) || aPixelSz.Width()) ) + { + OString sWidth; + if (nPercentWidth) + { + if (nPercentWidth == 255) + { + if (nPercentHeight) + { + sWidth = "auto"_ostr; + } + else + { + sWidth = OString::number(static_cast<sal_Int32>(aPixelSz.Width())); + } + } + else + { + sWidth = OString::number(static_cast<sal_Int32>(nPercentWidth)) + "%"; + } + } + else + sWidth = OString::number(static_cast<sal_Int32>(aPixelSz.Width())); + if (!mbXHTML || sWidth != "auto") + { + aHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_width, sWidth); + } + } + + if( (nFrameOptions & HtmlFrmOpts::Height) && + ((nPercentHeight && nPercentHeight!=255) || aPixelSz.Height()) ) + { + OString sHeight; + if (nPercentHeight) + { + if (nPercentHeight == 255) + { + if (nPercentWidth) + { + sHeight = "auto"_ostr; + } + else + { + sHeight = OString::number(static_cast<sal_Int32>(aPixelSz.Height())); + } + } + else + { + sHeight = OString::number(static_cast<sal_Int32>(nPercentHeight)) + "%"; + } + } + else + sHeight = OString::number(static_cast<sal_Int32>(aPixelSz.Height())); + if (!mbXHTML || sHeight != "auto") + { + aHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_height, sHeight); + } + } + } + + if (mbReqIF) + return; + + // Insert wrap for graphics that are anchored to a paragraph as + // <BR CLEAR=...> in the string + + if( !(nFrameOptions & HtmlFrmOpts::BrClear) ) + return; + RndStdIds nAnchorId = rFrameFormat.GetAnchor().GetAnchorId(); + if (RndStdIds::FLY_AT_PARA != nAnchorId && RndStdIds::FLY_AT_CHAR != nAnchorId) + return; + const SwFormatSurround* pSurround = rItemSet.GetItemIfSet( RES_SURROUND ); + if (!pSurround) + return; + + std::string_view pSurroundString; + + sal_Int16 eHoriOri = rFrameFormat.GetHoriOrient().GetHoriOrient(); + css::text::WrapTextMode eSurround = pSurround->GetSurround(); + bool bAnchorOnly = pSurround->IsAnchorOnly(); + switch( eHoriOri ) + { + case text::HoriOrientation::RIGHT: + { + switch( eSurround ) + { + case css::text::WrapTextMode_NONE: + case css::text::WrapTextMode_RIGHT: + pSurroundString = OOO_STRING_SVTOOLS_HTML_AL_right; + break; + case css::text::WrapTextMode_LEFT: + case css::text::WrapTextMode_PARALLEL: + if( bAnchorOnly ) + m_bClearRight = true; + break; + default: + ; + } + } + break; + + default: + // If a frame is centered, it gets left aligned. This + // should be taken into account here, too. + { + switch( eSurround ) + { + case css::text::WrapTextMode_NONE: + case css::text::WrapTextMode_LEFT: + pSurroundString = OOO_STRING_SVTOOLS_HTML_AL_left; + break; + case css::text::WrapTextMode_RIGHT: + case css::text::WrapTextMode_PARALLEL: + if( bAnchorOnly ) + m_bClearLeft = true; + break; + default: + break; + } + } + break; + } + + if (!pSurroundString.empty()) + { + aHtml.start(OOO_STRING_SVTOOLS_HTML_linebreak ""_ostr); + aHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_clear, pSurroundString); + aHtml.end(); + } +} + +namespace +{ + +OUString lclWriteOutImap(SwHTMLWriter& rWrt, const SfxItemSet& rItemSet, const SwFrameFormat& rFrameFormat, + const Size& rRealSize, const ImageMap* pAltImgMap, const SwFormatURL*& pURLItem) +{ + OUString aIMapName; + + // Only consider the URL attribute if no ImageMap was supplied + if (!pAltImgMap) + pURLItem = rItemSet.GetItemIfSet( RES_URL ); + + // write ImageMap + const ImageMap* pIMap = pAltImgMap; + if( !pIMap && pURLItem ) + { + pIMap = pURLItem->GetMap(); + } + + if (pIMap) + { + // make the name unique + aIMapName = pIMap->GetName(); + OUString aNameBase; + if (!aIMapName.isEmpty()) + aNameBase = aIMapName; + else + aNameBase = OOO_STRING_SVTOOLS_HTML_map; + + if (aIMapName.isEmpty()) + aIMapName = aNameBase + OUString::number(rWrt.m_nImgMapCnt); + + bool bFound; + do + { + bFound = false; + for (const OUString & rImgMapName : rWrt.m_aImgMapNames) + { + // TODO: Unicode: Comparison is case insensitive for ASCII + // characters only now! + if (aIMapName.equalsIgnoreAsciiCase(rImgMapName)) + { + bFound = true; + break; + } + } + if (bFound) + { + rWrt.m_nImgMapCnt++; + aIMapName = aNameBase + OUString::number( rWrt.m_nImgMapCnt ); + } + } while (bFound); + + bool bScale = false; + Fraction aScaleX(1, 1); + Fraction aScaleY(1, 1); + + const SwFormatFrameSize& rFrameSize = rFrameFormat.GetFrameSize(); + const SvxBoxItem& rBox = rFrameFormat.GetBox(); + + if (!rFrameSize.GetWidthPercent() && rRealSize.Width()) + { + SwTwips nWidth = rFrameSize.GetWidth(); + nWidth -= rBox.CalcLineSpace(SvxBoxItemLine::LEFT) + rBox.CalcLineSpace(SvxBoxItemLine::RIGHT); + + OSL_ENSURE( nWidth > 0, "Are there any graphics that are 0 twip wide!?" ); + if (nWidth <= 0) // should not happen + nWidth = 1; + + if (rRealSize.Width() != nWidth) + { + aScaleX = Fraction(nWidth, rRealSize.Width()); + bScale = true; + } + } + + if (!rFrameSize.GetHeightPercent() && rRealSize.Height()) + { + SwTwips nHeight = rFrameSize.GetHeight(); + + nHeight -= rBox.CalcLineSpace(SvxBoxItemLine::TOP) + rBox.CalcLineSpace(SvxBoxItemLine::BOTTOM); + + OSL_ENSURE( nHeight > 0, "Are there any graphics that are 0 twip high!?" ); + if (nHeight <= 0) + nHeight = 1; + + if (rRealSize.Height() != nHeight) + { + aScaleY = Fraction(nHeight, rRealSize.Height()); + bScale = true; + } + } + + rWrt.m_aImgMapNames.push_back(aIMapName); + + OString aIndMap, aIndArea; + const char *pIndArea = nullptr, *pIndMap = nullptr; + + if (rWrt.IsLFPossible()) + { + rWrt.OutNewLine( true ); + aIndMap = rWrt.GetIndentString(); + aIndArea = rWrt.GetIndentString(1); + pIndArea = aIndArea.getStr(); + pIndMap = aIndMap.getStr(); + } + + if (bScale) + { + ImageMap aScaledIMap(*pIMap); + aScaledIMap.Scale(aScaleX, aScaleY); + HTMLOutFuncs::Out_ImageMap( rWrt.Strm(), rWrt.GetBaseURL(), aScaledIMap, aIMapName, + aIMapEventTable, + rWrt.m_bCfgStarBasic, + SAL_NEWLINE_STRING, pIndArea, pIndMap ); + } + else + { + HTMLOutFuncs::Out_ImageMap( rWrt.Strm(), rWrt.GetBaseURL(), *pIMap, aIMapName, + aIMapEventTable, + rWrt.m_bCfgStarBasic, + SAL_NEWLINE_STRING, pIndArea, pIndMap ); + } + } + return aIMapName; +} + +OUString getFrameFormatText(const SwFrameFormat& rFrameFormat) +{ + const SwFormatContent& rFlyContent = rFrameFormat.GetContent(); + const SwNodeIndex* pSttIx = rFlyContent.GetContentIdx(); + if (!pSttIx) + return {}; + + const SwNodeOffset nStt = pSttIx->GetIndex(); + const auto& nodes = rFrameFormat.GetDoc()->GetNodes(); + const SwNodeOffset nEnd = nodes[nStt]->EndOfSectionIndex(); + + OUStringBuffer result; + for (SwNodeOffset i = nStt + 1; i < nEnd; ++i) + { + if (const auto* pTextNd = nodes[i]->GetTextNode()) + { + if (!result.isEmpty()) + result.append("\n"); + result.append(comphelper::string::encodeForXml(pTextNd->GetExpandText( + nullptr, 0, -1, true, true, false, + ExpandMode::ExpandFields | ExpandMode::HideInvisible | ExpandMode::HideDeletions + | ExpandMode::HideFieldmarkCommands))); + } + } + + return result.makeStringAndClear(); +} + +} + +SwHTMLWriter& OutHTML_ImageStart( HtmlWriter& rHtml, SwHTMLWriter& rWrt, const SwFrameFormat &rFrameFormat, + const OUString& rGraphicURL, + Graphic const & rGraphic, const OUString& rAlternateText, + const Size &rRealSize, HtmlFrmOpts nFrameOpts, + const char *pMarkType, + const ImageMap *pAltImgMap, + const OUString& rMimeType ) +{ + // <object data="..."> instead of <img src="..."> + bool bReplacement = (nFrameOpts & HtmlFrmOpts::Replacement) || rWrt.mbReqIF; + + if (rWrt.mbSkipImages) + return rWrt; + + // if necessary, temporarily close an open attribute + if( !rWrt.m_aINetFormats.empty() ) + { + SwFormatINetFormat* pINetFormat = rWrt.m_aINetFormats.back(); + OutHTML_INetFormat( rWrt, *pINetFormat, false ); + } + + OUString aGraphicURL( rGraphicURL ); + if( !rWrt.mbEmbedImages && !HTMLOutFuncs::PrivateURLToInternalImg(aGraphicURL) && !rWrt.mpTempBaseURL ) + aGraphicURL = URIHelper::simpleNormalizedMakeRelative( rWrt.GetBaseURL(), aGraphicURL); + + const SfxItemSet& rItemSet = rFrameFormat.GetAttrSet(); + + const SwFormatURL* pURLItem = nullptr; + OUString aIMapName = lclWriteOutImap(rWrt, rItemSet, rFrameFormat, rRealSize, pAltImgMap, pURLItem); + + // put img into new line + if (rWrt.IsLFPossible()) + rWrt.OutNewLine( true ); + + // <a name=...></a>...<img ...> + if( pMarkType && !rFrameFormat.GetName().isEmpty() ) + { + rWrt.OutImplicitMark( rFrameFormat.GetName(), pMarkType ); + } + + // URL -> <a>...<img ... >...</a> + const SvxMacroItem *pMacItem = rItemSet.GetItemIfSet(RES_FRMMACRO); + + if (pURLItem || pMacItem) + { + OUString aMapURL; + OUString aName; + OUString aTarget; + + if(pURLItem) + { + aMapURL = pURLItem->GetURL(); + aName = pURLItem->GetName(); + aTarget = pURLItem->GetTargetFrameName(); + } + + bool bEvents = pMacItem && !pMacItem->GetMacroTable().empty(); + + if( !aMapURL.isEmpty() || !aName.isEmpty() || !aTarget.isEmpty() || bEvents ) + { + rHtml.start(OOO_STRING_SVTOOLS_HTML_anchor ""_ostr); + + // Output "href" element if a link or macro exists + if( !aMapURL.isEmpty() || bEvents ) + { + rHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_href, rWrt.convertHyperlinkHRefValue(aMapURL)); + } + + if( !aName.isEmpty() ) + { + rHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_name, aName); + } + + if( !aTarget.isEmpty() ) + { + rHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_target, aTarget); + } + + if( pMacItem ) + { + const SvxMacroTableDtor& rMacTable = pMacItem->GetMacroTable(); + if (!rMacTable.empty()) + { + HtmlWriterHelper::applyEvents(rHtml, rMacTable, aAnchorEventTable, rWrt.m_bCfgStarBasic); + } + } + } + } + + // <font color = ...>...<img ... >...</font> + sal_uInt16 nBorderWidth = 0; + const SvxBoxItem* pBoxItem; + if( (nFrameOpts & HtmlFrmOpts::Border) && + (pBoxItem = rItemSet.GetItemIfSet( RES_BOX )) ) + { + Size aTwipBorder( 0, 0 ); + const ::editeng::SvxBorderLine *pColBorderLine = nullptr; + const ::editeng::SvxBorderLine *pBorderLine = pBoxItem->GetLeft(); + if( pBorderLine ) + { + pColBorderLine = pBorderLine; + aTwipBorder.AdjustWidth(pBorderLine->GetOutWidth() ); + } + + pBorderLine = pBoxItem->GetRight(); + if( pBorderLine ) + { + pColBorderLine = pBorderLine; + aTwipBorder.AdjustWidth(pBorderLine->GetOutWidth() ); + } + + pBorderLine = pBoxItem->GetTop(); + if( pBorderLine ) + { + pColBorderLine = pBorderLine; + aTwipBorder.AdjustHeight(pBorderLine->GetOutWidth() ); + } + + pBorderLine = pBoxItem->GetBottom(); + if( pBorderLine ) + { + pColBorderLine = pBorderLine; + aTwipBorder.AdjustHeight(pBorderLine->GetOutWidth() ); + } + + aTwipBorder.setWidth( aTwipBorder.Width() / 2 ); + aTwipBorder.setHeight( aTwipBorder.Height() / 2 ); + + if( (aTwipBorder.Width() || aTwipBorder.Height()) && + Application::GetDefaultDevice() ) + { + Size aPixelBorder = SwHTMLWriter::ToPixel(aTwipBorder); + + if( aPixelBorder.Width() ) + aPixelBorder.setHeight( 0 ); + + nBorderWidth = + o3tl::narrowing<sal_uInt16>(aPixelBorder.Width() + aPixelBorder.Height()); + } + + if( pColBorderLine ) + { + rHtml.start(OOO_STRING_SVTOOLS_HTML_font ""_ostr); + HtmlWriterHelper::applyColor(rHtml, OOO_STRING_SVTOOLS_HTML_O_color, pColBorderLine->GetColor()); + } + } + + OString aTag(OOO_STRING_SVTOOLS_HTML_image ""_ostr); + if (bReplacement) + // Write replacement graphic of OLE object as <object>. + aTag = OOO_STRING_SVTOOLS_HTML_object ""_ostr; + rHtml.start(aTag); + + if(rWrt.mbEmbedImages) + { + OUString aGraphicInBase64; + if (XOutBitmap::GraphicToBase64(rGraphic, aGraphicInBase64)) + { + OString sBuffer(OString::Concat(OOO_STRING_SVTOOLS_HTML_O_data) + + ":" + + OUStringToOString(aGraphicInBase64, RTL_TEXTENCODING_UTF8)); + rHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_src, sBuffer); + } + else + rWrt.m_nWarn = WARN_SWG_POOR_LOAD; + } + else + { + OString sBuffer(OUStringToOString(aGraphicURL, RTL_TEXTENCODING_UTF8)); + OString aAttribute(OOO_STRING_SVTOOLS_HTML_O_src ""_ostr); + if (bReplacement) + aAttribute = OOO_STRING_SVTOOLS_HTML_O_data ""_ostr; + rHtml.attribute(aAttribute, sBuffer); + } + + if (bReplacement) + { + // Handle XHTML type attribute for OLE replacement images. + if (!rMimeType.isEmpty()) + rHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_type, rMimeType); + } + + // Events + if (const SvxMacroItem* pMacroItem = rItemSet.GetItemIfSet(RES_FRMMACRO)) + { + const SvxMacroTableDtor& rMacTable = pMacroItem->GetMacroTable(); + if (!rMacTable.empty()) + { + HtmlWriterHelper::applyEvents(rHtml, rMacTable, aImageEventTable, rWrt.m_bCfgStarBasic); + } + } + + // alt, align, width, height, hspace, vspace + rWrt.writeFrameFormatOptions(rHtml, rFrameFormat, rAlternateText, nFrameOpts); + if( rWrt.IsHTMLMode( HTMLMODE_ABS_POS_FLY ) ) + rWrt.OutCSS1_FrameFormatOptions( rFrameFormat, nFrameOpts ); + + if ((nFrameOpts & HtmlFrmOpts::Border) && !bReplacement) + { + rHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_border, nBorderWidth); + } + + if( pURLItem && pURLItem->IsServerMap() ) + { + rHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_ismap); + } + + if( !aIMapName.isEmpty() ) + { + rHtml.attribute(OOO_STRING_SVTOOLS_HTML_O_usemap, "#" + aIMapName); + } + + if (bReplacement) + { + OUString aAltText = rAlternateText; + // In ReqIF mode, output text from the frame instead + if (rWrt.mbReqIF) + if (OUString aFrameText = getFrameFormatText(rFrameFormat); !aFrameText.isEmpty()) + aAltText = aFrameText; + + // XHTML object replacement image's alternate text doesn't use the + // "alt" attribute. + if (aAltText.isEmpty()) + // Empty alternate text is not valid. + rHtml.characters(" "); + else + rHtml.characters(aAltText.toUtf8()); + } + + return rWrt; +} + +SwHTMLWriter& OutHTML_ImageEnd( HtmlWriter& rHtml, SwHTMLWriter& rWrt ) +{ + rHtml.flushStack(); + + if( !rWrt.m_aINetFormats.empty() ) + { + // There is still an attribute on the stack that has to be reopened + SwFormatINetFormat *pINetFormat = rWrt.m_aINetFormats.back(); + OutHTML_INetFormat( rWrt, *pINetFormat, true ); + } + + return rWrt; +} + +SwHTMLWriter& OutHTML_BulletImage( SwHTMLWriter& rWrt, + const char *pTag, + const SvxBrushItem* pBrush, + const OUString &rGraphicURL) +{ + OUString aGraphicInBase64; + OUString aLink; + if( pBrush ) + { + aLink = pBrush->GetGraphicLink(); + if(rWrt.mbEmbedImages || aLink.isEmpty()) + { + const Graphic* pGrf = pBrush->GetGraphic(); + if( pGrf ) + { + if( !XOutBitmap::GraphicToBase64(*pGrf, aGraphicInBase64) ) + { + rWrt.m_nWarn = WARN_SWG_POOR_LOAD; + } + } + } + else if(!aLink.isEmpty()) + { + if( rWrt.m_bCfgCpyLinkedGrfs ) + { + rWrt.CopyLocalFileToINet( aLink ); + } + + } + } + else if(!rWrt.mbEmbedImages) + { + aLink = rGraphicURL; + } + if(!aLink.isEmpty()) + { + if( !HTMLOutFuncs::PrivateURLToInternalImg(aLink) ) + aLink = URIHelper::simpleNormalizedMakeRelative( rWrt.GetBaseURL(), aLink); + } + + OStringBuffer sOut; + if( pTag ) + sOut.append(OString::Concat("<") + pTag); + + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_style "=\""); + if(!aLink.isEmpty()) + { + sOut.append(OOO_STRING_SVTOOLS_HTML_O_src "=\""); + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( rWrt.Strm(), aLink ); + } + else + { + sOut.append("list-style-image: url(" + OOO_STRING_SVTOOLS_HTML_O_data ":"); + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( rWrt.Strm(), aGraphicInBase64 ); + sOut.append(");"); + } + sOut.append('\"'); + + if (pTag) + sOut.append('>'); + rWrt.Strm().WriteOString( sOut ); + + return rWrt; +} + +static SwHTMLWriter& OutHTML_FrameFormatTableNode( SwHTMLWriter& rWrt, const SwFrameFormat& rFrameFormat ) +{ + const SwFormatContent& rFlyContent = rFrameFormat.GetContent(); + SwNodeOffset nStt = rFlyContent.GetContentIdx()->GetIndex()+1; + SwNodeOffset nEnd = rWrt.m_pDoc->GetNodes()[nStt-1]->EndOfSectionIndex(); + + OUString aCaption; + bool bTopCaption = false; + + // Not const, because GetTable won't be const sometime later + SwNode *pNd = rWrt.m_pDoc->GetNodes()[ nStt ]; + SwTableNode *pTableNd = pNd->GetTableNode(); + const SwTextNode *pTextNd = pNd->GetTextNode(); + if( !pTableNd && pTextNd ) + { + // Table with heading + bTopCaption = true; + pTableNd = rWrt.m_pDoc->GetNodes()[nStt+1]->GetTableNode(); + } + OSL_ENSURE( pTableNd, "Frame does not contain a table" ); + if( pTableNd ) + { + SwNodeOffset nTableEnd = pTableNd->EndOfSectionIndex(); + OSL_ENSURE( nTableEnd == nEnd - 1 || + (nTableEnd == nEnd - 2 && !bTopCaption), + "Invalid frame content for a table" ); + + if( nTableEnd == nEnd - 2 ) + pTextNd = rWrt.m_pDoc->GetNodes()[nTableEnd+1]->GetTextNode(); + } + if( pTextNd ) + aCaption = pTextNd->GetText(); + + if( pTableNd ) + { + HTMLSaveData aSaveData( rWrt, pTableNd->GetIndex()+1, + pTableNd->EndOfSectionIndex(), + true, &rFrameFormat ); + rWrt.m_bOutFlyFrame = true; + OutHTML_SwTableNode( rWrt, *pTableNd, &rFrameFormat, &aCaption, + bTopCaption ); + } + + return rWrt; +} + +static SwHTMLWriter & OutHTML_FrameFormatAsMulticol( SwHTMLWriter& rWrt, + const SwFrameFormat& rFrameFormat, + bool bInCntnr ) +{ + rWrt.ChangeParaToken( HtmlTokenId::NONE ); + + // Close the current <DL>! + rWrt.OutAndSetDefList( 0 ); + + // output as Multicol + if (rWrt.IsLFPossible()) + rWrt.OutNewLine(); + + OStringBuffer sOut("<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_multicol); + + const SwFormatCol& rFormatCol = rFrameFormat.GetCol(); + + // output the number of columns as COLS + sal_uInt16 nCols = rFormatCol.GetNumCols(); + if( nCols ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_cols + "=\"" + OString::number(nCols) + "\""); + } + + // the Gutter width (minimum value) as GUTTER + sal_uInt16 nGutter = rFormatCol.GetGutterWidth( true ); + if( nGutter!=USHRT_MAX ) + { + nGutter = SwHTMLWriter::ToPixel(nGutter); + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_gutter + "=\"" + OString::number(nGutter) + "\""); + } + + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + + // WIDTH + HtmlFrmOpts nFrameFlags = HTML_FRMOPTS_MULTICOL; + if( rWrt.IsHTMLMode( HTMLMODE_ABS_POS_FLY ) && !bInCntnr ) + nFrameFlags |= HTML_FRMOPTS_MULTICOL_CSS1; + rWrt.OutFrameFormatOptions(rFrameFormat, OUString(), nFrameFlags); + if( rWrt.IsHTMLMode( HTMLMODE_ABS_POS_FLY ) && !bInCntnr ) + rWrt.OutCSS1_FrameFormatOptions( rFrameFormat, nFrameFlags ); + + rWrt.Strm().WriteChar( '>' ); + + rWrt.SetLFPossible(true); + rWrt.IncIndentLevel(); // indent the content of Multicol + + const SwFormatContent& rFlyContent = rFrameFormat.GetContent(); + SwNodeOffset nStt = rFlyContent.GetContentIdx()->GetIndex(); + const SwStartNode* pSttNd = rWrt.m_pDoc->GetNodes()[nStt]->GetStartNode(); + OSL_ENSURE( pSttNd, "Where is the start node" ); + + { + // in a block, so that the old state can be restored in time + // before the end + HTMLSaveData aSaveData( rWrt, nStt+1, + pSttNd->EndOfSectionIndex(), + true, &rFrameFormat ); + rWrt.m_bOutFlyFrame = true; + rWrt.Out_SwDoc( rWrt.m_pCurrentPam.get() ); + } + + rWrt.DecIndentLevel(); // indent the content of Multicol; + if (rWrt.IsLFPossible()) + rWrt.OutNewLine(); + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_multicol), false ); + rWrt.SetLFPossible(true); + + return rWrt; +} + +static SwHTMLWriter& OutHTML_FrameFormatAsSpacer( SwHTMLWriter& rWrt, const SwFrameFormat& rFrameFormat ) +{ + // if possible, output a line break before the graphic + if (rWrt.IsLFPossible()) + rWrt.OutNewLine( true ); + + OString sOut = + "<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_spacer " " + OOO_STRING_SVTOOLS_HTML_O_type "=\"" + OOO_STRING_SVTOOLS_HTML_SPTYPE_block "\""; + rWrt.Strm().WriteOString( sOut ); + + // ALIGN, WIDTH, HEIGHT + OString aEndTags = rWrt.OutFrameFormatOptions(rFrameFormat, OUString(), HTML_FRMOPTS_SPACER); + + rWrt.Strm().WriteChar( '>' ); + if( !aEndTags.isEmpty() ) + rWrt.Strm().WriteOString( aEndTags ); + + return rWrt; +} + +static SwHTMLWriter& OutHTML_FrameFormatAsDivOrSpan( SwHTMLWriter& rWrt, + const SwFrameFormat& rFrameFormat, bool bSpan) +{ + OString aTag; + if( !bSpan ) + { + rWrt.ChangeParaToken( HtmlTokenId::NONE ); + + // Close the current <DL>! + rWrt.OutAndSetDefList( 0 ); + aTag = OOO_STRING_SVTOOLS_HTML_division ""_ostr; + } + else + aTag = OOO_STRING_SVTOOLS_HTML_span ""_ostr; + + // output as DIV + if (rWrt.IsLFPossible()) + rWrt.OutNewLine(); + + OStringBuffer sOut("<" + rWrt.GetNamespace() + aTag); + + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + HtmlFrmOpts nFrameFlags = HTML_FRMOPTS_DIV; + if( rWrt.IsHTMLMode( HTMLMODE_BORDER_NONE ) ) + nFrameFlags |= HtmlFrmOpts::SNoBorder; + OString aEndTags = rWrt.OutFrameFormatOptions(rFrameFormat, OUString(), nFrameFlags); + rWrt.OutCSS1_FrameFormatOptions( rFrameFormat, nFrameFlags ); + rWrt.Strm().WriteChar( '>' ); + + rWrt.IncIndentLevel(); // indent the content + rWrt.SetLFPossible(true); + + const SwFormatContent& rFlyContent = rFrameFormat.GetContent(); + SwNodeOffset nStt = rFlyContent.GetContentIdx()->GetIndex(); + + // Output frame-anchored frames that are anchored to the start node + rWrt.OutFlyFrame( nStt, 0, HtmlPosition::Any ); + + const SwStartNode* pSttNd = rWrt.m_pDoc->GetNodes()[nStt]->GetStartNode(); + OSL_ENSURE( pSttNd, "Where is the start node" ); + + { + // in a block, so that the old state can be restored in time + // before the end + HTMLSaveData aSaveData( rWrt, nStt+1, + pSttNd->EndOfSectionIndex(), + true, &rFrameFormat ); + rWrt.m_bOutFlyFrame = true; + rWrt.Out_SwDoc( rWrt.m_pCurrentPam.get() ); + } + + rWrt.DecIndentLevel(); // indent the content of Multicol; + if (rWrt.IsLFPossible()) + rWrt.OutNewLine(); + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + aTag), false ); + + if( !aEndTags.isEmpty() ) + rWrt.Strm().WriteOString( aEndTags ); + + return rWrt; +} + +/// Starts the OLE version of an image in the ReqIF + OLE case. +static void OutHTML_ImageOLEStart(SwHTMLWriter& rWrt, const Graphic& rGraphic, + const SwFrameFormat& rFrameFormat) +{ + if (!rWrt.mbReqIF || !rWrt.m_bExportImagesAsOLE) + return; + + // Write the original image as an RTF fragment. + OUString aFileName; + if (rWrt.GetOrigFileName()) + aFileName = *rWrt.GetOrigFileName(); + INetURLObject aURL(aFileName); + OUString aName = aURL.getBase() + "_" + aURL.getExtension() + "_" + + OUString::number(rGraphic.GetChecksum(), 16); + aURL.setBase(aName); + aURL.setExtension(u"ole"); + aFileName = aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE); + + SvFileStream aOutStream(aFileName, StreamMode::WRITE); + if (!SwReqIfReader::WrapGraphicInRtf(rGraphic, rFrameFormat, aOutStream)) + SAL_WARN("sw.html", "SwReqIfReader::WrapGraphicInRtf() failed"); + + // Refer to this data. + aFileName = URIHelper::simpleNormalizedMakeRelative(rWrt.GetBaseURL(), aFileName); + rWrt.Strm().WriteOString( + Concat2View("<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_object)); + rWrt.Strm().WriteOString(Concat2View(" data=\"" + aFileName.toUtf8() + "\"")); + rWrt.Strm().WriteOString(" type=\"text/rtf\""); + rWrt.Strm().WriteOString(">"); + rWrt.OutNewLine(); +} + +/// Ends the OLE version of an image in the ReqIF + OLE case. +static void OutHTML_ImageOLEEnd(SwHTMLWriter& rWrt) +{ + if (rWrt.mbReqIF && rWrt.m_bExportImagesAsOLE) + { + rWrt.Strm().WriteOString( + Concat2View("</" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_object ">")); + } +} + +static SwHTMLWriter & OutHTML_FrameFormatAsImage( SwHTMLWriter& rWrt, const SwFrameFormat& rFrameFormat, bool bPNGFallback) +{ + bool bWritePNGFallback = rWrt.mbReqIF && !rWrt.m_bExportImagesAsOLE && bPNGFallback; + + if (rWrt.mbSkipImages) + return rWrt; + + ImageMap aIMap; + std::optional<Size> aDPI; + if (rWrt.m_nShapeDPI.has_value()) + { + aDPI.emplace(*rWrt.m_nShapeDPI, *rWrt.m_nShapeDPI); + } + Graphic aGraphic( const_cast<SwFrameFormat &>(rFrameFormat).MakeGraphic( &aIMap, /*nMaximumQuadraticPixels=*/2100000, aDPI ) ); + + if (rWrt.mbReqIF) + { + // ImageMap doesn't seem to be allowed in reqif. + if (auto pGrafObj = dynamic_cast<const SdrGrafObj*>(rFrameFormat.FindSdrObject())) + { + aGraphic = pGrafObj->GetGraphic(); + } + else + { + // We only have a bitmap, write that as PNG without any fallback. + bWritePNGFallback = false; + } + } + + Size aSz( 0, 0 ); + OUString GraphicURL; + OUString aMimeType("image/jpeg"); + if(!rWrt.mbEmbedImages) + { + if( rWrt.GetOrigFileName() ) + GraphicURL = *rWrt.GetOrigFileName(); + + OUString aFilterName("JPG"); + XOutFlags nFlags = XOutFlags::UseGifIfPossible | XOutFlags::UseNativeIfPossible; + + if (rWrt.mbReqIF && !bWritePNGFallback) + { + // Writing image without fallback PNG in ReqIF mode: force PNG output. + aFilterName = "PNG"; + nFlags = XOutFlags::NONE; + aMimeType = "image/png"; + } + else if (rWrt.mbReqIF) + { + // Original format is wanted, don't force JPG. + aFilterName.clear(); + aMimeType.clear(); + } + + if( aGraphic.GetType() == GraphicType::NONE || + XOutBitmap::WriteGraphic( aGraphic, GraphicURL, + aFilterName, + nFlags ) != ERRCODE_NONE ) + { + // empty or incorrect, because there is nothing to output + rWrt.m_nWarn = WARN_SWG_POOR_LOAD; + return rWrt; + } + + GraphicURL = URIHelper::SmartRel2Abs( + INetURLObject(rWrt.GetBaseURL()), GraphicURL, + URIHelper::GetMaybeFileHdl() ); + + } + uno::Reference<beans::XPropertySet> xGraphic(aGraphic.GetXGraphic(), uno::UNO_QUERY); + if (xGraphic.is() && aMimeType.isEmpty()) + xGraphic->getPropertyValue("MimeType") >>= aMimeType; + + OutHTML_ImageOLEStart(rWrt, aGraphic, rFrameFormat); + + HtmlWriter aHtml(rWrt.Strm(), rWrt.maNamespace); + OutHTML_ImageStart( aHtml, rWrt, rFrameFormat, GraphicURL, aGraphic, rFrameFormat.GetName(), aSz, + HtmlFrmOpts::GenImgMask, "frame", + aIMap.GetIMapObjectCount() ? &aIMap : nullptr, aMimeType ); + + GfxLink aLink = aGraphic.GetGfxLink(); + if (bWritePNGFallback && aLink.GetType() != GfxLinkType::NativePng) + { + OutHTML_FrameFormatAsImage( rWrt, rFrameFormat, /*bPNGFallback=*/false); + } + + OutHTML_ImageEnd(aHtml, rWrt); + + OutHTML_ImageOLEEnd(rWrt); + + return rWrt; +} + +static SwHTMLWriter& OutHTML_FrameFormatGrfNode( SwHTMLWriter& rWrt, const SwFrameFormat& rFrameFormat, + bool bInCntnr, bool bPNGFallback ) +{ + bool bWritePNGFallback = rWrt.mbReqIF && !rWrt.m_bExportImagesAsOLE && bPNGFallback; + + if (rWrt.mbSkipImages) + return rWrt; + + const SwFormatContent& rFlyContent = rFrameFormat.GetContent(); + SwNodeOffset nStt = rFlyContent.GetContentIdx()->GetIndex()+1; + SwGrfNode *pGrfNd = rWrt.m_pDoc->GetNodes()[ nStt ]->GetGrfNode(); + OSL_ENSURE( pGrfNd, "Grf node expected" ); + if( !pGrfNd ) + return rWrt; + + HtmlFrmOpts nFrameFlags = bInCntnr ? HTML_FRMOPTS_IMG_CNTNR : HTML_FRMOPTS_IMG; + if( rWrt.IsHTMLMode( HTMLMODE_ABS_POS_FLY ) && !bInCntnr ) + nFrameFlags |= HTML_FRMOPTS_IMG_CSS1; + + Graphic aGraphic = pGrfNd->GetGraphic(); + + if (aGraphic.GetType() == GraphicType::GdiMetafile) + { + // We only have a metafile, write that as PNG without any fallback. + bWritePNGFallback = false; + } + + OUString aGraphicURL; + OUString aMimeType; + if(!rWrt.mbEmbedImages) + { + const SwMirrorGrf& rMirror = pGrfNd->GetSwAttrSet().GetMirrorGrf(); + + if( !pGrfNd->IsLinkedFile() || MirrorGraph::Dont != rMirror.GetValue() ) + { + // create a (mirrored) jpeg file + if( rWrt.GetOrigFileName() ) + aGraphicURL = *rWrt.GetOrigFileName(); + else + aGraphicURL = rWrt.GetBaseURL(); + pGrfNd->GetGrf( true ); + + XOutFlags nFlags = XOutFlags::UseGifIfSensible | + XOutFlags::UseNativeIfPossible; + switch( rMirror.GetValue() ) + { + case MirrorGraph::Vertical: nFlags = XOutFlags::MirrorHorz; break; + case MirrorGraph::Horizontal: nFlags = XOutFlags::MirrorVert; break; + case MirrorGraph::Both: + nFlags = XOutFlags::MirrorVert | XOutFlags::MirrorHorz; + break; + default: break; + } + + const SwFormatFrameSize& rSize = rFrameFormat.GetFrameSize(); + Size aMM100Size = o3tl::convert( rSize.GetSize(), + o3tl::Length::twip, o3tl::Length::mm100 ); + + OUString aFilterName; + + if (rWrt.mbReqIF) + { + // In ReqIF mode, do not try to write GIF for other image types + nFlags &= ~XOutFlags::UseGifIfSensible; + if (!bWritePNGFallback) + { + // Writing image without fallback PNG in ReqIF mode: force PNG + // output. + // But don't force it when writing the original format and we'll write PNG inside + // that. + aFilterName = "PNG"; + nFlags &= ~XOutFlags::UseNativeIfPossible; + } + } + + const Graphic& rGraphic = pGrfNd->GetGrf(); + + // So that Graphic::IsTransparent() can report true. + if (!rGraphic.isAvailable()) + const_cast<Graphic&>(rGraphic).makeAvailable(); + + if (rWrt.mbReqIF && bWritePNGFallback) + { + // ReqIF: force native data if possible. + const std::shared_ptr<VectorGraphicData>& pVectorGraphicData = rGraphic.getVectorGraphicData(); + if (pVectorGraphicData && pVectorGraphicData->getType() == VectorGraphicDataType::Svg) + { + aFilterName = "svg"; + } + else if (rGraphic.GetGfxLink().IsEMF()) + { + aFilterName = "emf"; + } + else if (pVectorGraphicData && pVectorGraphicData->getType() == VectorGraphicDataType::Wmf) + { + aFilterName = "wmf"; + } + else if (rGraphic.GetGfxLink().GetType() == GfxLinkType::NativeTif) + { + aFilterName = "tif"; + } + } + + ErrCode nErr = XOutBitmap::WriteGraphic( rGraphic, aGraphicURL, + aFilterName, nFlags, &aMM100Size, nullptr, &aMimeType ); + if( nErr ) + { + rWrt.m_nWarn = WARN_SWG_POOR_LOAD; + return rWrt; + } + aGraphicURL = URIHelper::SmartRel2Abs( + INetURLObject(rWrt.GetBaseURL()), aGraphicURL, + URIHelper::GetMaybeFileHdl() ); + } + else + { + pGrfNd->GetFileFilterNms( &aGraphicURL, nullptr ); + if( rWrt.m_bCfgCpyLinkedGrfs ) + rWrt.CopyLocalFileToINet( aGraphicURL ); + } + + } + uno::Reference<beans::XPropertySet> xGraphic(aGraphic.GetXGraphic(), uno::UNO_QUERY); + if (xGraphic.is() && aMimeType.isEmpty()) + xGraphic->getPropertyValue("MimeType") >>= aMimeType; + + OutHTML_ImageOLEStart(rWrt, aGraphic, rFrameFormat); + + HtmlWriter aHtml(rWrt.Strm(), rWrt.maNamespace); + OutHTML_ImageStart( aHtml, rWrt, rFrameFormat, aGraphicURL, aGraphic, pGrfNd->GetTitle(), + pGrfNd->GetTwipSize(), nFrameFlags, "graphic", nullptr, aMimeType ); + + GfxLink aLink = aGraphic.GetGfxLink(); + if (bWritePNGFallback && aLink.GetType() != GfxLinkType::NativePng) + { + // Not OLE mode, outer format is not PNG: write inner PNG. + OutHTML_FrameFormatGrfNode( rWrt, rFrameFormat, + bInCntnr, /*bPNGFallback=*/false ); + } + + OutHTML_ImageEnd(aHtml, rWrt); + + OutHTML_ImageOLEEnd(rWrt); + + return rWrt; +} + +static SwHTMLWriter& OutHTML_FrameFormatAsMarquee( SwHTMLWriter& rWrt, const SwFrameFormat& rFrameFormat, + const SdrObject& rSdrObj ) +{ + // get the edit engine attributes of the object as SW attributes and + // sort them as Hints + const SfxItemSet& rFormatItemSet = rFrameFormat.GetAttrSet(); + SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END> aItemSet( *rFormatItemSet.GetPool() ); + SwHTMLWriter::GetEEAttrsFromDrwObj( aItemSet, &rSdrObj ); + bool bCfgOutStylesOld = rWrt.m_bCfgOutStyles; + rWrt.m_bCfgOutStyles = false; + rWrt.m_bTextAttr = true; + rWrt.m_bTagOn = true; + Out_SfxItemSet( aHTMLAttrFnTab, rWrt, aItemSet, false ); + rWrt.m_bTextAttr = false; + + OutHTML_DrawFrameFormatAsMarquee( rWrt, + static_cast<const SwDrawFrameFormat &>(rFrameFormat), + rSdrObj ); + rWrt.m_bTextAttr = true; + rWrt.m_bTagOn = false; + Out_SfxItemSet( aHTMLAttrFnTab, rWrt, aItemSet, false ); + rWrt.m_bTextAttr = false; + rWrt.m_bCfgOutStyles = bCfgOutStylesOld; + + return rWrt; +} + +SwHTMLWriter& OutHTML_HeaderFooter( SwHTMLWriter& rWrt, const SwFrameFormat& rFrameFormat, + bool bHeader ) +{ + // output as Multicol + rWrt.OutNewLine(); + OStringBuffer sOut; + sOut.append(OOO_STRING_SVTOOLS_HTML_division " " + OOO_STRING_SVTOOLS_HTML_O_title "=\"") + .append( bHeader ? "header" : "footer" ).append("\""); + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + sOut) ); + + rWrt.IncIndentLevel(); // indent the content of Multicol; + + // Piece a spacer for the spacing together. Because the + // <DL> or </DL> always produces a space between paragraphs, it is + // subtracted if necessary. + const SvxULSpaceItem& rULSpace = rFrameFormat.GetULSpace(); + sal_uInt16 nSize = bHeader ? rULSpace.GetLower() : rULSpace.GetUpper(); + rWrt.m_nHeaderFooterSpace = nSize; + + OString aSpacer; + if( rWrt.IsHTMLMode(HTMLMODE_VERT_SPACER) && + nSize > HTML_PARSPACE ) + { + nSize -= HTML_PARSPACE; + nSize = SwHTMLWriter::ToPixel(nSize); + + aSpacer = OOO_STRING_SVTOOLS_HTML_spacer + " " OOO_STRING_SVTOOLS_HTML_O_type + "=\"" OOO_STRING_SVTOOLS_HTML_SPTYPE_vertical "\"" + " " OOO_STRING_SVTOOLS_HTML_O_size + "=\"" + OString::number(nSize) + "\""; + } + + const SwFormatContent& rFlyContent = rFrameFormat.GetContent(); + SwNodeOffset nStt = rFlyContent.GetContentIdx()->GetIndex(); + const SwStartNode* pSttNd = rWrt.m_pDoc->GetNodes()[nStt]->GetStartNode(); + OSL_ENSURE( pSttNd, "Where is the start node" ); + + if( !bHeader && !aSpacer.isEmpty() ) + { + rWrt.OutNewLine(); + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + aSpacer) ); + } + + { + // in a block, so that the old state can be restored in time + // before the end. pFlyFormat doesn't need to be set here, because + // PageDesc attributes cannot occur here + HTMLSaveData aSaveData( rWrt, nStt+1, + pSttNd->EndOfSectionIndex() ); + + if( bHeader ) + rWrt.m_bOutHeader = true; + else + rWrt.m_bOutFooter = true; + + rWrt.Out_SwDoc( rWrt.m_pCurrentPam.get() ); + } + + if( bHeader && !aSpacer.isEmpty() ) + { + rWrt.OutNewLine(); + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + aSpacer) ); + } + + rWrt.DecIndentLevel(); // indent the content of Multicol; + rWrt.OutNewLine(); + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_division), false ); + + rWrt.m_nHeaderFooterSpace = 0; + + return rWrt; +} + +void SwHTMLWriter::AddLinkTarget( std::u16string_view aURL ) +{ + if( aURL.empty() || aURL[0] != '#' ) + return; + + // There might be a '|' as delimiter (if the link has been inserted + // freshly) or a '%7c' or a '%7C' if the document has been saved and + // loaded already. + sal_Int32 nPos = aURL.size(); + bool bFound = false, bEncoded = false; + while( !bFound && nPos > 0 ) + { + sal_Unicode c = aURL[ --nPos ]; + switch( c ) + { + case cMarkSeparator: + bFound = true; + break; + case '%': + bFound = (aURL.size() - nPos) >=3 && aURL[ nPos+1 ] == '7'; + if(bFound) + { + c = aURL[ nPos+2 ]; + bFound = (c == 'C' || c == 'c'); + } + if( bFound ) + bEncoded = true; + } + } + if( !bFound || nPos < 2 ) // at least "#a|..." + return; + + aURL = aURL.substr( 1 ); + + // nPos-1+1/3 (-1 because of Erase) + OUString sCmp = OUString(aURL.substr(bEncoded ? nPos+2 : nPos)).replaceAll(" ",""); + if( sCmp.isEmpty() ) + return; + + sCmp = sCmp.toAsciiLowerCase(); + + if( sCmp == "region" || + sCmp == "frame" || + sCmp == "graphic" || + sCmp == "ole" || + sCmp == "table" ) + { + // Just remember it in a sorted array + OUString aURL2(aURL); + if( bEncoded ) + { + aURL2 = aURL2.replaceAt( nPos - 1, 3, rtl::OUStringChar(cMarkSeparator) ); + } + m_aImplicitMarks.insert( aURL2 ); + } + else if( sCmp == "outline" ) + { + // Here, we need position and name. That's why we sort a + // sal_uInt16 and a string array ourselves. + OUString aOutline( aURL.substr( 0, nPos-1 ) ); + SwPosition aPos( *m_pCurrentPam->GetPoint() ); + if( m_pDoc->GotoOutline( aPos, aOutline ) ) + { + SwNodeOffset nIdx = aPos.GetNodeIndex(); + + decltype(m_aOutlineMarkPoss)::size_type nIns=0; + while( nIns < m_aOutlineMarkPoss.size() && + m_aOutlineMarkPoss[nIns] < nIdx ) + nIns++; + + m_aOutlineMarkPoss.insert( m_aOutlineMarkPoss.begin()+nIns, nIdx ); + OUString aURL2(aURL); + if( bEncoded ) + { + aURL2 = aURL2.replaceAt( nPos - 1, 3, rtl::OUStringChar(cMarkSeparator) ); + } + m_aOutlineMarks.insert( m_aOutlineMarks.begin()+nIns, aURL2 ); + } + } +} + +void SwHTMLWriter::CollectLinkTargets() +{ + const SwTextINetFormat* pTextAttr; + + for (const SfxPoolItem* pItem : m_pDoc->GetAttrPool().GetItemSurrogates(RES_TXTATR_INETFMT)) + { + auto pINetFormat = dynamic_cast<const SwFormatINetFormat*>(pItem); + const SwTextNode* pTextNd; + + if( pINetFormat && + nullptr != ( pTextAttr = pINetFormat->GetTextINetFormat()) && + nullptr != ( pTextNd = pTextAttr->GetpTextNode() ) && + pTextNd->GetNodes().IsDocNodes() ) + { + AddLinkTarget( pINetFormat->GetValue() ); + } + } + + for (const SfxPoolItem* pItem : m_pDoc->GetAttrPool().GetItemSurrogates(RES_URL)) + { + auto pURL = dynamic_cast<const SwFormatURL*>(pItem); + if( pURL ) + { + AddLinkTarget( pURL->GetURL() ); + const ImageMap *pIMap = pURL->GetMap(); + if( pIMap ) + { + for( size_t i=0; i<pIMap->GetIMapObjectCount(); ++i ) + { + const IMapObject* pObj = pIMap->GetIMapObject( i ); + if( pObj ) + { + AddLinkTarget( pObj->GetURL() ); + } + } + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlform.cxx b/sw/source/filter/html/htmlform.cxx new file mode 100644 index 0000000000..d37abddc68 --- /dev/null +++ b/sw/source/filter/html/htmlform.cxx @@ -0,0 +1,2455 @@ +/* -*- 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 <hintids.hxx> +#include <comphelper/documentinfo.hxx> +#include <comphelper/string.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <tools/UnitConversion.hxx> + +#include <o3tl/string_view.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/unohelp.hxx> +#include <svtools/htmlkywd.hxx> +#include <svtools/htmltokn.h> +#include <svl/urihelper.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/event.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/viewfrm.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <svx/svdouno.hxx> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/form/ListSourceType.hpp> +#include <com/sun/star/form/FormButtonType.hpp> +#include <com/sun/star/form/FormSubmitEncoding.hpp> +#include <com/sun/star/form/FormSubmitMethod.hpp> +#include <com/sun/star/drawing/XDrawPageSupplier.hpp> +#include <com/sun/star/script/XEventAttacherManager.hpp> +#include <com/sun/star/text/WrapTextMode.hpp> +#include <com/sun/star/text/HoriOrientation.hpp> +#include <com/sun/star/text/VertOrientation.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <com/sun/star/container/XIndexContainer.hpp> +#include <com/sun/star/drawing/XControlShape.hpp> +#include <com/sun/star/awt/XTextLayoutConstrains.hpp> +#include <com/sun/star/awt/XLayoutConstrains.hpp> +#include <com/sun/star/awt/XImageConsumer.hpp> +#include <com/sun/star/awt/ImageStatus.hpp> +#include <com/sun/star/form/XImageProducerSupplier.hpp> +#include <com/sun/star/form/XForm.hpp> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentUndoRedo.hxx> +#include <pam.hxx> +#include <swtable.hxx> +#include <fmtanchr.hxx> +#include <htmltbl.hxx> +#include <docsh.hxx> +#include <viewsh.hxx> +#include <unodraw.hxx> +#include <unotextrange.hxx> + +#include "swcss1.hxx" +#include "swhtml.hxx" +#include "htmlform.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::form; + +const sal_uInt16 TABINDEX_MIN = 0; +const sal_uInt16 TABINDEX_MAX = 32767; + +HTMLOptionEnum<FormSubmitMethod> const aHTMLFormMethodTable[] = +{ + { OOO_STRING_SVTOOLS_HTML_METHOD_get, FormSubmitMethod_GET }, + { OOO_STRING_SVTOOLS_HTML_METHOD_post, FormSubmitMethod_POST }, + { nullptr, FormSubmitMethod(0) } +}; + +HTMLOptionEnum<FormSubmitEncoding> const aHTMLFormEncTypeTable[] = +{ + { OOO_STRING_SVTOOLS_HTML_ET_url, FormSubmitEncoding_URL }, + { OOO_STRING_SVTOOLS_HTML_ET_multipart, FormSubmitEncoding_MULTIPART }, + { OOO_STRING_SVTOOLS_HTML_ET_text, FormSubmitEncoding_TEXT }, + { nullptr, FormSubmitEncoding(0) } +}; + +namespace { + +enum HTMLWordWrapMode { HTML_WM_OFF, HTML_WM_HARD, HTML_WM_SOFT }; + +} + +HTMLOptionEnum<HTMLWordWrapMode> const aHTMLTextAreaWrapTable[] = +{ + { OOO_STRING_SVTOOLS_HTML_WW_off, HTML_WM_OFF }, + { OOO_STRING_SVTOOLS_HTML_WW_hard, HTML_WM_HARD }, + { OOO_STRING_SVTOOLS_HTML_WW_soft, HTML_WM_SOFT }, + { OOO_STRING_SVTOOLS_HTML_WW_physical, HTML_WM_HARD }, + { OOO_STRING_SVTOOLS_HTML_WW_virtual, HTML_WM_SOFT }, + { nullptr, HTMLWordWrapMode(0) } +}; + +static SvMacroItemId aEventTypeTable[] = +{ + SvMacroItemId::HtmlOnSubmitForm, + SvMacroItemId::HtmlOnResetForm, + SvMacroItemId::HtmlOnGetFocus, + SvMacroItemId::HtmlOnLoseFocus, + SvMacroItemId::HtmlOnClick, + SvMacroItemId::HtmlOnClickItem, + SvMacroItemId::HtmlOnChange, + SvMacroItemId::HtmlOnSelect, + SvMacroItemId::NONE +}; + +const char * aEventListenerTable[] = +{ + "XSubmitListener", + "XResetListener", + "XFocusListener", + "XFocusListener", + "XApproveActionListener", + "XItemListener", + "XChangeListener", + "" +}; + +const char * aEventMethodTable[] = +{ + "approveSubmit", + "approveReset", + "focusGained", + "focusLost", + "approveAction", + "itemStateChanged", + "changed", + "" +}; + +const char * aEventSDOptionTable[] = +{ + OOO_STRING_SVTOOLS_HTML_O_SDonsubmit, + OOO_STRING_SVTOOLS_HTML_O_SDonreset, + OOO_STRING_SVTOOLS_HTML_O_SDonfocus, + OOO_STRING_SVTOOLS_HTML_O_SDonblur, + OOO_STRING_SVTOOLS_HTML_O_SDonclick, + OOO_STRING_SVTOOLS_HTML_O_SDonclick, + OOO_STRING_SVTOOLS_HTML_O_SDonchange, + nullptr +}; + +const char * aEventOptionTable[] = +{ + OOO_STRING_SVTOOLS_HTML_O_onsubmit, + OOO_STRING_SVTOOLS_HTML_O_onreset, + OOO_STRING_SVTOOLS_HTML_O_onfocus, + OOO_STRING_SVTOOLS_HTML_O_onblur, + OOO_STRING_SVTOOLS_HTML_O_onclick, + OOO_STRING_SVTOOLS_HTML_O_onclick, + OOO_STRING_SVTOOLS_HTML_O_onchange, + nullptr +}; + +class SwHTMLForm_Impl +{ + SwDocShell *m_pDocShell; + + SvKeyValueIterator *m_pHeaderAttrs; + + // Cached interfaces + uno::Reference< drawing::XDrawPage > m_xDrawPage; + uno::Reference< container::XIndexContainer > m_xForms; + uno::Reference< drawing::XShapes > m_xShapes; + uno::Reference< XMultiServiceFactory > m_xServiceFactory; + + uno::Reference< script::XEventAttacherManager > m_xControlEventManager; + uno::Reference< script::XEventAttacherManager > m_xFormEventManager; + + // Context information + uno::Reference< container::XIndexContainer > m_xFormComps; + uno::Reference< beans::XPropertySet > m_xFCompPropertySet; + uno::Reference< drawing::XShape > m_xShape; + + OUString m_sText; + std::vector<OUString> m_aStringList; + std::vector<OUString> m_aValueList; + std::vector<sal_uInt16> m_aSelectedList; + +public: + explicit SwHTMLForm_Impl( SwDocShell *pDSh ) : + m_pDocShell( pDSh ), + m_pHeaderAttrs( pDSh ? pDSh->GetHeaderAttributes() : nullptr ) + { + OSL_ENSURE( m_pDocShell, "No DocShell, no Controls" ); + } + + const uno::Reference< XMultiServiceFactory >& GetServiceFactory(); + void GetDrawPage(); + const uno::Reference< drawing::XShapes >& GetShapes(); + const uno::Reference< script::XEventAttacherManager >& GetControlEventManager(); + const uno::Reference< script::XEventAttacherManager >& GetFormEventManager(); + const uno::Reference< container::XIndexContainer >& GetForms(); + + const uno::Reference< container::XIndexContainer >& GetFormComps() const + { + return m_xFormComps; + } + + void SetFormComps( const uno::Reference< container::XIndexContainer >& r ) + { + m_xFormComps = r; + } + + void ReleaseFormComps() { m_xFormComps = nullptr; m_xControlEventManager = nullptr; } + + const uno::Reference< beans::XPropertySet >& GetFCompPropSet() const + { + return m_xFCompPropertySet; + } + + void SetFCompPropSet( const uno::Reference< beans::XPropertySet >& r ) + { + m_xFCompPropertySet = r; + } + + void ReleaseFCompPropSet() { m_xFCompPropertySet = nullptr; } + + const uno::Reference< drawing::XShape >& GetShape() const { return m_xShape; } + void SetShape( const uno::Reference< drawing::XShape >& r ) { m_xShape = r; } + + OUString& GetText() { return m_sText; } + void EraseText() { m_sText.clear(); } + + std::vector<OUString>& GetStringList() { return m_aStringList; } + void EraseStringList() + { + m_aStringList.clear(); + } + + std::vector<OUString>& GetValueList() { return m_aValueList; } + void EraseValueList() + { + m_aValueList.clear(); + } + + std::vector<sal_uInt16>& GetSelectedList() { return m_aSelectedList; } + void EraseSelectedList() + { + m_aSelectedList.clear(); + } + + SvKeyValueIterator *GetHeaderAttrs() const { return m_pHeaderAttrs; } +}; + +const uno::Reference< XMultiServiceFactory >& SwHTMLForm_Impl::GetServiceFactory() +{ + if( !m_xServiceFactory.is() && m_pDocShell ) + { + m_xServiceFactory = + uno::Reference< XMultiServiceFactory >( m_pDocShell->GetBaseModel(), + UNO_QUERY ); + OSL_ENSURE( m_xServiceFactory.is(), + "XServiceFactory not received from model" ); + } + return m_xServiceFactory; +} + +void SwHTMLForm_Impl::GetDrawPage() +{ + if( !m_xDrawPage.is() && m_pDocShell ) + { + uno::Reference< drawing::XDrawPageSupplier > xTextDoc( m_pDocShell->GetBaseModel(), + UNO_QUERY ); + OSL_ENSURE( xTextDoc.is(), + "drawing::XDrawPageSupplier not received from model" ); + m_xDrawPage = xTextDoc->getDrawPage(); + OSL_ENSURE( m_xDrawPage.is(), "drawing::XDrawPage not received" ); + } +} + +const uno::Reference< container::XIndexContainer >& SwHTMLForm_Impl::GetForms() +{ + if( !m_xForms.is() ) + { + GetDrawPage(); + if( m_xDrawPage.is() ) + { + uno::Reference< XFormsSupplier > xFormsSupplier( m_xDrawPage, UNO_QUERY ); + OSL_ENSURE( xFormsSupplier.is(), + "XFormsSupplier not received from drawing::XDrawPage" ); + + uno::Reference< container::XNameContainer > xNameCont = + xFormsSupplier->getForms(); + m_xForms.set( xNameCont, UNO_QUERY ); + + OSL_ENSURE( m_xForms.is(), "XForms not received" ); + } + } + return m_xForms; +} + +const uno::Reference< drawing::XShapes > & SwHTMLForm_Impl::GetShapes() +{ + if( !m_xShapes.is() ) + { + GetDrawPage(); + if( m_xDrawPage.is() ) + { + m_xShapes = m_xDrawPage; + OSL_ENSURE( m_xShapes.is(), + "XShapes not received from drawing::XDrawPage" ); + } + } + return m_xShapes; +} + +const uno::Reference< script::XEventAttacherManager >& + SwHTMLForm_Impl::GetControlEventManager() +{ + if( !m_xControlEventManager.is() && m_xFormComps.is() ) + { + m_xControlEventManager = + uno::Reference< script::XEventAttacherManager >( m_xFormComps, UNO_QUERY ); + OSL_ENSURE( m_xControlEventManager.is(), + "uno::Reference< XEventAttacherManager > not received from xFormComps" ); + } + + return m_xControlEventManager; +} + +const uno::Reference< script::XEventAttacherManager >& + SwHTMLForm_Impl::GetFormEventManager() +{ + if( !m_xFormEventManager.is() ) + { + GetForms(); + if( m_xForms.is() ) + { + m_xFormEventManager = + uno::Reference< script::XEventAttacherManager >( m_xForms, UNO_QUERY ); + OSL_ENSURE( m_xFormEventManager.is(), + "uno::Reference< XEventAttacherManager > not received from xForms" ); + } + } + + return m_xFormEventManager; +} + +namespace { + +class SwHTMLImageWatcher : + public cppu::WeakImplHelper< awt::XImageConsumer, XEventListener > +{ + uno::Reference< drawing::XShape > m_xShape; // the control + uno::Reference< XImageProducerSupplier > m_xSrc; + uno::Reference< awt::XImageConsumer > m_xThis; // reference to self + bool m_bSetWidth; + bool m_bSetHeight; + + void clear(); + +public: + SwHTMLImageWatcher( uno::Reference< drawing::XShape > xShape, + bool bWidth, bool bHeight ); + + // startProduction can not be called in the constructor because it can + // destruct itself, hence a separate method. + void start() { m_xSrc->getImageProducer()->startProduction(); } + + // UNO binding + + // XImageConsumer + virtual void SAL_CALL init( sal_Int32 Width, sal_Int32 Height) override; + virtual void SAL_CALL setColorModel( + sal_Int16 BitCount, const uno::Sequence< sal_Int32 >& RGBAPal, + sal_Int32 RedMask, sal_Int32 GreenMask, sal_Int32 BlueMask, + sal_Int32 AlphaMask) override; + virtual void SAL_CALL setPixelsByBytes( + sal_Int32 X, sal_Int32 Y, sal_Int32 Width, sal_Int32 Height, + const uno::Sequence< sal_Int8 >& ProducerData, + sal_Int32 Offset, sal_Int32 Scansize) override; + virtual void SAL_CALL setPixelsByLongs( + sal_Int32 X, sal_Int32 Y, sal_Int32 Width, sal_Int32 Height, + const uno::Sequence< sal_Int32 >& ProducerData, + sal_Int32 Offset, sal_Int32 Scansize) override; + virtual void SAL_CALL complete( + sal_Int32 Status, + const uno::Reference< awt::XImageProducer > & Producer) override; + + // XEventListener + virtual void SAL_CALL disposing( const EventObject& Source ) override; +}; + +} + +SwHTMLImageWatcher::SwHTMLImageWatcher( + uno::Reference< drawing::XShape > xShape, + bool bWidth, bool bHeight ) : + m_xShape(std::move( xShape )), + m_bSetWidth( bWidth ), m_bSetHeight( bHeight ) +{ + // Remember the source of the image + uno::Reference< drawing::XControlShape > xControlShape( m_xShape, UNO_QUERY ); + uno::Reference< awt::XControlModel > xControlModel( + xControlShape->getControl() ); + m_xSrc.set( xControlModel, UNO_QUERY ); + OSL_ENSURE( m_xSrc.is(), "No XImageProducerSupplier" ); + + // Register as Event-Listener on the shape to be able to release it on dispose. + uno::Reference< XEventListener > xEvtLstnr = static_cast<XEventListener *>(this); + uno::Reference< XComponent > xComp( m_xShape, UNO_QUERY ); + xComp->addEventListener( xEvtLstnr ); + + // Lastly we keep a reference to ourselves so we are not destroyed + // (should not be necessary since we're still registered elsewhere) + m_xThis = static_cast<awt::XImageConsumer *>(this); + + // Register at ImageProducer to retrieve the size... + m_xSrc->getImageProducer()->addConsumer( m_xThis ); +} + +void SwHTMLImageWatcher::clear() +{ + // Unregister on Shape + uno::Reference< XEventListener > xEvtLstnr = static_cast<XEventListener *>(this); + uno::Reference< XComponent > xComp( m_xShape, UNO_QUERY ); + xComp->removeEventListener( xEvtLstnr ); + + // Unregister on ImageProducer + uno::Reference<awt::XImageProducer> xProd = m_xSrc->getImageProducer(); + if( xProd.is() ) + xProd->removeConsumer( m_xThis ); +} + +void SwHTMLImageWatcher::init( sal_Int32 Width, sal_Int32 Height ) +{ + OSL_ENSURE( m_bSetWidth || m_bSetHeight, + "Width or height has to be adjusted" ); + + // If no width or height is given, it is initialized to those of + // the empty graphic that is available before the stream of a graphic + // that is to be displayed asynchronous is available. + if( !Width && !Height ) + return; + + awt::Size aNewSz; + aNewSz.Width = o3tl::convert(Width, o3tl::Length::px, o3tl::Length::mm100); + aNewSz.Height = o3tl::convert(Height, o3tl::Length::px, o3tl::Length::mm100); + + if( !m_bSetWidth || !m_bSetHeight ) + { + awt::Size aSz( m_xShape->getSize() ); + if( m_bSetWidth && aNewSz.Height ) + { + aNewSz.Width *= aSz.Height; + aNewSz.Width /= aNewSz.Height; + aNewSz.Height = aSz.Height; + } + if( m_bSetHeight && aNewSz.Width ) + { + aNewSz.Height *= aSz.Width; + aNewSz.Height /= aNewSz.Width; + aNewSz.Width = aSz.Width; + } + } + if( aNewSz.Width < MINFLY ) + aNewSz.Width = MINFLY; + if( aNewSz.Height < MINFLY ) + aNewSz.Height = MINFLY; + + m_xShape->setSize( aNewSz ); + if( m_bSetWidth ) + { + // If the control is anchored to a table, the column have to be recalculated + + // To get to the SwXShape* we need an interface that is implemented by SwXShape + + uno::Reference< beans::XPropertySet > xPropSet( m_xShape, UNO_QUERY ); + SwXShape *pSwShape = comphelper::getFromUnoTunnel<SwXShape>(xPropSet); + + OSL_ENSURE( pSwShape, "Where is SW-Shape?" ); + if( pSwShape ) + { + SwFrameFormat *pFrameFormat = pSwShape->GetFrameFormat(); + + const SwDoc *pDoc = pFrameFormat->GetDoc(); + SwNode* pAnchorNode = pFrameFormat->GetAnchor().GetAnchorNode(); + SwTableNode *pTableNd; + if (pAnchorNode && nullptr != (pTableNd = pAnchorNode->FindTableNode())) + { + const bool bLastGrf = !pTableNd->GetTable().DecGrfsThatResize(); + SwHTMLTableLayout *pLayout = + pTableNd->GetTable().GetHTMLTableLayout(); + if( pLayout ) + { + const sal_uInt16 nBrowseWidth = + pLayout->GetBrowseWidthByTable( *pDoc ); + + if ( nBrowseWidth ) + { + pLayout->Resize( nBrowseWidth, true, true, + bLastGrf ? HTMLTABLE_RESIZE_NOW + : 500 ); + } + } + } + } + } + + // unregister and delete self + clear(); + m_xThis = nullptr; +} + +void SwHTMLImageWatcher::setColorModel( + sal_Int16, const Sequence< sal_Int32 >&, sal_Int32, sal_Int32, + sal_Int32, sal_Int32 ) +{ +} + +void SwHTMLImageWatcher::setPixelsByBytes( + sal_Int32, sal_Int32, sal_Int32, sal_Int32, + const Sequence< sal_Int8 >&, sal_Int32, sal_Int32 ) +{ +} + +void SwHTMLImageWatcher::setPixelsByLongs( + sal_Int32, sal_Int32, sal_Int32, sal_Int32, + const Sequence< sal_Int32 >&, sal_Int32, sal_Int32 ) +{ +} + +void SwHTMLImageWatcher::complete( sal_Int32 Status, + const uno::Reference< awt::XImageProducer >& ) +{ + if( awt::ImageStatus::IMAGESTATUS_ERROR == Status || awt::ImageStatus::IMAGESTATUS_ABORTED == Status ) + { + // unregister and delete self + clear(); + m_xThis = nullptr; + } +} + +void SwHTMLImageWatcher::disposing(const lang::EventObject& evt) +{ + uno::Reference< awt::XImageConsumer > xTmp; + + // We need to release the shape if it is disposed of + if( evt.Source == m_xShape ) + { + clear(); + xTmp = static_cast<awt::XImageConsumer*>(this); + m_xThis = nullptr; + } +} + +void SwHTMLParser::DeleteFormImpl() +{ + delete m_pFormImpl; + m_pFormImpl = nullptr; +} + +static void lcl_html_setFixedFontProperty( + const uno::Reference< beans::XPropertySet >& rPropSet ) +{ + vcl::Font aFixedFont( OutputDevice::GetDefaultFont( + DefaultFontType::FIXED, LANGUAGE_ENGLISH_US, + GetDefaultFontFlags::OnlyOne ) ); + Any aTmp; + aTmp <<= aFixedFont.GetFamilyName(); + rPropSet->setPropertyValue("FontName", aTmp ); + + aTmp <<= aFixedFont.GetStyleName(); + rPropSet->setPropertyValue("FontStyleName", + aTmp ); + + aTmp <<= static_cast<sal_Int16>(aFixedFont.GetFamilyType()); + rPropSet->setPropertyValue("FontFamily", aTmp ); + + aTmp <<= static_cast<sal_Int16>(aFixedFont.GetCharSet()); + rPropSet->setPropertyValue("FontCharset", + aTmp ); + + aTmp <<= static_cast<sal_Int16>(aFixedFont.GetPitch()); + rPropSet->setPropertyValue("FontPitch", aTmp ); + + aTmp <<= float(10.0); + rPropSet->setPropertyValue("FontHeight", aTmp ); +} + +void SwHTMLParser::SetControlSize( const uno::Reference< drawing::XShape >& rShape, + const Size& rTextSz, + bool bMinWidth, + bool bMinHeight ) +{ + if( !rTextSz.Width() && !rTextSz.Height() && !bMinWidth && !bMinHeight ) + return; + + // To get to SwXShape* we need an interface that is implemented by SwXShape + + uno::Reference< beans::XPropertySet > xPropSet( rShape, UNO_QUERY ); + + SwViewShell *pVSh = m_xDoc->getIDocumentLayoutAccess().GetCurrentViewShell(); + if( !pVSh && !m_nEventId ) + { + // If there is no view shell by now and the doc shell is an internal + // one, no view shell will be created. That for, we have to do that of + // our own. This happens if a linked section is inserted or refreshed. + SwDocShell *pDocSh = m_xDoc->GetDocShell(); + if( pDocSh ) + { + if ( pDocSh->GetMedium() ) + { + // if there is no hidden property in the MediaDescriptor it should be removed after loading + const SfxBoolItem* pHiddenItem = pDocSh->GetMedium()->GetItemSet().GetItem(SID_HIDDEN, false); + m_bRemoveHidden = ( pHiddenItem == nullptr || !pHiddenItem->GetValue() ); + } + + m_pTempViewFrame = SfxViewFrame::LoadHiddenDocument( *pDocSh, SFX_INTERFACE_NONE ); + CallStartAction(); + pVSh = m_xDoc->getIDocumentLayoutAccess().GetCurrentViewShell(); + // this ridiculous hack also enables Undo, so turn it off again + m_xDoc->GetIDocumentUndoRedo().DoUndo(false); + } + } + + SwXShape *pSwShape = comphelper::getFromUnoTunnel<SwXShape>(xPropSet); + + OSL_ENSURE( pSwShape, "Where is SW-Shape?" ); + + // has to be a Draw-Format + SwFrameFormat *pFrameFormat = pSwShape ? pSwShape->GetFrameFormat() : nullptr ; + OSL_ENSURE( pFrameFormat && RES_DRAWFRMFMT == pFrameFormat->Which(), "No DrawFrameFormat" ); + + // look if a SdrObject exists for it + const SdrObject *pObj = pFrameFormat ? pFrameFormat->FindSdrObject() : nullptr; + OSL_ENSURE( pObj, "SdrObject not found" ); + OSL_ENSURE( pObj && SdrInventor::FmForm == pObj->GetObjInventor(), "wrong Inventor" ); + + const SdrView* pDrawView = pVSh ? pVSh->GetDrawView() : nullptr; + + const SdrUnoObj *pFormObj = dynamic_cast<const SdrUnoObj*>( pObj ); + uno::Reference< awt::XControl > xControl; + if ( pDrawView && pVSh->GetWin() && pFormObj ) + xControl = pFormObj->GetUnoControl( *pDrawView, *pVSh->GetWin()->GetOutDev() ); + + awt::Size aSz( rShape->getSize() ); + awt::Size aNewSz( 0, 0 ); + + // #i71248# ensure we got a XControl before applying corrections + if(xControl.is()) + { + if( bMinWidth || bMinHeight ) + { + uno::Reference< awt::XLayoutConstrains > xLC( xControl, UNO_QUERY ); + awt::Size aTmpSz( xLC->getPreferredSize() ); + if( bMinWidth ) + aNewSz.Width = aTmpSz.Width; + if( bMinHeight ) + aNewSz.Height = aTmpSz.Height; + } + if( rTextSz.Width() || rTextSz.Height()) + { + uno::Reference< awt::XTextLayoutConstrains > xLC( xControl, UNO_QUERY ); + OSL_ENSURE( xLC.is(), "no XTextLayoutConstrains" ); + if( xLC.is() ) + { + awt::Size aTmpSz( rTextSz.Width(), rTextSz.Height() ); + if( -1 == rTextSz.Width() ) + { + aTmpSz.Width = 0; + aTmpSz.Height = m_nSelectEntryCnt; + } + aTmpSz = xLC->getMinimumSize( static_cast< sal_Int16 >(aTmpSz.Width), static_cast< sal_Int16 >(aTmpSz.Height) ); + if( rTextSz.Width() ) + aNewSz.Width = aTmpSz.Width; + if( rTextSz.Height() ) + aNewSz.Height = aTmpSz.Height; + } + } + } + + aNewSz.Width = o3tl::convert(aNewSz.Width, o3tl::Length::px, o3tl::Length::mm100); + aNewSz.Height = o3tl::convert(aNewSz.Height, o3tl::Length::px, o3tl::Length::mm100); + if( aNewSz.Width ) + { + if( aNewSz.Width < MINLAY ) + aNewSz.Width = MINLAY; + aSz.Width = aNewSz.Width; + } + if( aNewSz.Height ) + { + if( aNewSz.Height < MINLAY ) + aNewSz.Height = MINLAY; + aSz.Height = aNewSz.Height; + } + + rShape->setSize( aSz ); +} + +static bool lcl_html_setEvents( + const uno::Reference< script::XEventAttacherManager > & rEvtMn, + sal_uInt32 nPos, const SvxMacroTableDtor& rMacroTable, + const std::vector<OUString>& rUnoMacroTable, + const std::vector<OUString>& rUnoMacroParamTable, + const OUString& rType ) +{ + // First the number of events has to be determined + sal_Int32 nEvents = 0; + + for( int i = 0; SvMacroItemId::NONE != aEventTypeTable[i]; ++i ) + { + const SvxMacro *pMacro = rMacroTable.Get( aEventTypeTable[i] ); + // As long as not all events are implemented the table also holds empty strings + if( pMacro && aEventListenerTable[i] ) + nEvents++; + } + for( const auto &rStr : rUnoMacroTable ) + { + sal_Int32 nIndex = 0; + if( o3tl::getToken(rStr, 0, '-', nIndex ).empty() || -1 == nIndex ) + continue; + if( o3tl::getToken(rStr, 0, '-', nIndex ).empty() || -1 == nIndex ) + continue; + if( nIndex < rStr.getLength() ) + nEvents++; + } + + if( 0==nEvents ) + return false; + + Sequence<script::ScriptEventDescriptor> aDescs( nEvents ); + script::ScriptEventDescriptor* pDescs = aDescs.getArray(); + sal_Int32 nEvent = 0; + + for( int i=0; SvMacroItemId::NONE != aEventTypeTable[i]; ++i ) + { + const SvxMacro *pMacro = rMacroTable.Get( aEventTypeTable[i] ); + if( pMacro && aEventListenerTable[i] ) + { + script::ScriptEventDescriptor& rDesc = pDescs[nEvent++]; + rDesc.ListenerType = + OUString::createFromAscii(aEventListenerTable[i]); + rDesc.EventMethod = OUString::createFromAscii(aEventMethodTable[i]); + rDesc.ScriptType = pMacro->GetLanguage(); + rDesc.ScriptCode = pMacro->GetMacName(); + } + } + + for( const auto &rStr : rUnoMacroTable ) + { + sal_Int32 nIndex = 0; + OUString sListener( rStr.getToken( 0, '-', nIndex ) ); + if( sListener.isEmpty() || -1 == nIndex ) + continue; + + OUString sMethod( rStr.getToken( 0, '-', nIndex ) ); + if( sMethod.isEmpty() || -1 == nIndex ) + continue; + + OUString sCode( rStr.copy( nIndex ) ); + if( sCode.isEmpty() ) + continue; + + script::ScriptEventDescriptor& rDesc = pDescs[nEvent++]; + rDesc.ListenerType = sListener; + rDesc.EventMethod = sMethod; + rDesc.ScriptType = rType; + rDesc.ScriptCode = sCode; + rDesc.AddListenerParam.clear(); + + if(!rUnoMacroParamTable.empty()) + { + OUString sSearch = sListener + "-" +sMethod + "-"; + sal_Int32 nLen = sSearch.getLength(); + for(const auto & rParam : rUnoMacroParamTable) + { + if( rParam.startsWith( sSearch ) && rParam.getLength() > nLen ) + { + rDesc.AddListenerParam = rParam.copy(nLen); + break; + } + } + } + } + rEvtMn->registerScriptEvents( nPos, aDescs ); + return true; +} + +static void lcl_html_getEvents( const OUString& rOption, std::u16string_view rValue, + std::vector<OUString>& rUnoMacroTable, + std::vector<OUString>& rUnoMacroParamTable ) +{ + if( rOption.startsWithIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_O_sdevent ) ) + { + OUString aEvent = OUString::Concat(rOption.subView( strlen( OOO_STRING_SVTOOLS_HTML_O_sdevent ) )) + + "-" + rValue; + rUnoMacroTable.push_back(aEvent); + } + else if( rOption.startsWithIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_O_sdaddparam ) ) + { + OUString aParam = OUString::Concat(rOption.subView( strlen( OOO_STRING_SVTOOLS_HTML_O_sdaddparam ) )) + + "-" + rValue; + rUnoMacroParamTable.push_back(aParam); + } +} + +uno::Reference< drawing::XShape > SwHTMLParser::InsertControl( + const uno::Reference< XFormComponent > & rFComp, + const uno::Reference< beans::XPropertySet > & rFCompPropSet, + const Size& rSize, sal_Int16 eVertOri, sal_Int16 eHoriOri, + SfxItemSet& rCSS1ItemSet, SvxCSS1PropertyInfo& rCSS1PropInfo, + const SvxMacroTableDtor& rMacroTable, const std::vector<OUString>& rUnoMacroTable, + const std::vector<OUString>& rUnoMacroParamTable, bool bSetFCompPropSet, + bool bHidden ) +{ + uno::Reference< drawing::XShape > xShape; + + const uno::Reference< container::XIndexContainer > & rFormComps = + m_pFormImpl->GetFormComps(); + Any aAny( &rFComp, cppu::UnoType<XFormComponent>::get()); + rFormComps->insertByIndex( rFormComps->getCount(), aAny ); + + if( !bHidden ) + { + Any aTmp; + sal_Int32 nLeftSpace = 0; + sal_Int32 nRightSpace = 0; + sal_Int32 nUpperSpace = 0; + sal_Int32 nLowerSpace = 0; + + const uno::Reference< XMultiServiceFactory > & rServiceFactory = + m_pFormImpl->GetServiceFactory(); + if( !rServiceFactory.is() ) + return xShape; + + uno::Reference< XInterface > xCreate = rServiceFactory->createInstance( "com.sun.star.drawing.ControlShape" ); + if( !xCreate.is() ) + return xShape; + + xShape.set( xCreate, UNO_QUERY ); + + OSL_ENSURE( xShape.is(), "XShape not received" ); + awt::Size aTmpSz; + aTmpSz.Width = rSize.Width(); + aTmpSz.Height = rSize.Height(); + xShape->setSize( aTmpSz ); + + uno::Reference< beans::XPropertySet > xShapePropSet( xCreate, UNO_QUERY ); + + // set left/right border + // note: parser never creates SvxLeftMarginItem! must be converted + if (const SvxTextLeftMarginItem *const pLeft = rCSS1ItemSet.GetItemIfSet(RES_MARGIN_TEXTLEFT)) + { + if( rCSS1PropInfo.m_bLeftMargin ) + { + // should be SvxLeftMarginItem... "cast" it + nLeftSpace = convertTwipToMm100(pLeft->GetTextLeft()); + rCSS1PropInfo.m_bLeftMargin = false; + } + rCSS1ItemSet.ClearItem(RES_MARGIN_TEXTLEFT); + } + if (const SvxRightMarginItem *const pRight = rCSS1ItemSet.GetItemIfSet(RES_MARGIN_RIGHT)) + { + if( rCSS1PropInfo.m_bRightMargin ) + { + nRightSpace = convertTwipToMm100(pRight->GetRight()); + rCSS1PropInfo.m_bRightMargin = false; + } + rCSS1ItemSet.ClearItem(RES_MARGIN_RIGHT); + } + if( nLeftSpace || nRightSpace ) + { + Any aAny2; + aAny2 <<= nLeftSpace; + xShapePropSet->setPropertyValue("LeftMargin", aAny2 ); + + aAny2 <<= nRightSpace; + xShapePropSet->setPropertyValue("RightMargin", aAny2 ); + } + + // set upper/lower border + if( const SvxULSpaceItem *pULItem = rCSS1ItemSet.GetItemIfSet( RES_UL_SPACE ) ) + { + // Flatten first line indent + if( rCSS1PropInfo.m_bTopMargin ) + { + nUpperSpace = convertTwipToMm100( pULItem->GetUpper() ); + rCSS1PropInfo.m_bTopMargin = false; + } + if( rCSS1PropInfo.m_bBottomMargin ) + { + nLowerSpace = convertTwipToMm100( pULItem->GetLower() ); + rCSS1PropInfo.m_bBottomMargin = false; + } + + rCSS1ItemSet.ClearItem( RES_UL_SPACE ); + } + if( nUpperSpace || nLowerSpace ) + { + uno::Any aAny2; + aAny2 <<= nUpperSpace; + xShapePropSet->setPropertyValue("TopMargin", aAny2 ); + + aAny2 <<= nLowerSpace; + xShapePropSet->setPropertyValue("BottomMargin", aAny2 ); + } + + uno::Reference< beans::XPropertySetInfo > xPropSetInfo = + rFCompPropSet->getPropertySetInfo(); + OUString sPropName = "BackgroundColor"; + const SvxBrushItem* pBrushItem = rCSS1ItemSet.GetItemIfSet( RES_BACKGROUND ); + if( pBrushItem && xPropSetInfo->hasPropertyByName( sPropName ) ) + { + const Color &rColor = pBrushItem->GetColor(); + /// copy color, if color is not "no fill"/"auto fill" + if( rColor != COL_TRANSPARENT ) + { + /// copy complete color with transparency + aTmp <<= rColor; + rFCompPropSet->setPropertyValue( sPropName, aTmp ); + } + + } + + sPropName = "TextColor"; + const SvxColorItem* pColorItem = rCSS1ItemSet.GetItemIfSet( RES_CHRATR_COLOR ); + if( pColorItem && xPropSetInfo->hasPropertyByName( sPropName ) ) + { + aTmp <<= static_cast<sal_Int32>(pColorItem->GetValue().GetRGBColor()); + rFCompPropSet->setPropertyValue( sPropName, aTmp ); + } + + sPropName = "FontHeight"; + const SvxFontHeightItem* pFontHeightItem = rCSS1ItemSet.GetItemIfSet( RES_CHRATR_FONTSIZE ); + if( pFontHeightItem && xPropSetInfo->hasPropertyByName( sPropName ) ) + { + float fVal = static_cast< float >( pFontHeightItem->GetHeight() / 20.0 ); + aTmp <<= fVal; + rFCompPropSet->setPropertyValue( sPropName, aTmp ); + } + + if( const SvxFontItem* pFontItem = rCSS1ItemSet.GetItemIfSet( RES_CHRATR_FONT ) ) + { + sPropName = "FontName"; + if( xPropSetInfo->hasPropertyByName( sPropName ) ) + { + aTmp <<= pFontItem->GetFamilyName(); + rFCompPropSet->setPropertyValue( sPropName, aTmp ); + } + sPropName = "FontStyleName"; + if( xPropSetInfo->hasPropertyByName( sPropName ) ) + { + aTmp <<= pFontItem->GetStyleName(); + rFCompPropSet->setPropertyValue( sPropName, aTmp ); + } + sPropName = "FontFamily"; + if( xPropSetInfo->hasPropertyByName( sPropName ) ) + { + aTmp <<= static_cast<sal_Int16>(pFontItem->GetFamily()) ; + rFCompPropSet->setPropertyValue( sPropName, aTmp ); + } + sPropName = "FontCharset"; + if( xPropSetInfo->hasPropertyByName( sPropName ) ) + { + aTmp <<= static_cast<sal_Int16>(pFontItem->GetCharSet()) ; + rFCompPropSet->setPropertyValue( sPropName, aTmp ); + } + sPropName = "FontPitch"; + if( xPropSetInfo->hasPropertyByName( sPropName ) ) + { + aTmp <<= static_cast<sal_Int16>(pFontItem->GetPitch()) ; + rFCompPropSet->setPropertyValue( sPropName, aTmp ); + } + } + + sPropName = "FontWeight"; + const SvxWeightItem* pWeightItem = rCSS1ItemSet.GetItemIfSet( RES_CHRATR_WEIGHT ); + if( pWeightItem && xPropSetInfo->hasPropertyByName( sPropName ) ) + { + float fVal = vcl::unohelper::ConvertFontWeight( + pWeightItem->GetWeight() ); + aTmp <<= fVal; + rFCompPropSet->setPropertyValue( sPropName, aTmp ); + } + + sPropName = "FontSlant"; + const SvxPostureItem* pPostureItem = rCSS1ItemSet.GetItemIfSet( RES_CHRATR_POSTURE ); + if( pPostureItem && xPropSetInfo->hasPropertyByName( sPropName ) ) + { + aTmp <<= static_cast<sal_Int16>(pPostureItem->GetPosture()); + rFCompPropSet->setPropertyValue( sPropName, aTmp ); + } + + sPropName = "FontUnderline"; + const SvxUnderlineItem* pUnderlineItem = rCSS1ItemSet.GetItemIfSet( RES_CHRATR_UNDERLINE ); + if( pUnderlineItem && xPropSetInfo->hasPropertyByName( sPropName ) ) + { + aTmp <<= static_cast<sal_Int16>(pUnderlineItem->GetLineStyle()); + rFCompPropSet->setPropertyValue( sPropName, aTmp ); + } + + sPropName = "FontStrikeout"; + const SvxCrossedOutItem* pCrossedOutItem = rCSS1ItemSet.GetItemIfSet( RES_CHRATR_CROSSEDOUT ); + if( pCrossedOutItem && xPropSetInfo->hasPropertyByName( sPropName ) ) + { + aTmp <<= static_cast<sal_Int16>(pCrossedOutItem->GetStrikeout()); + rFCompPropSet->setPropertyValue( sPropName, aTmp ); + } + + uno::Reference< text::XTextRange > xTextRg; + text::TextContentAnchorType nAnchorType = text::TextContentAnchorType_AS_CHARACTER; + bool bSetPos = false, bSetSurround = false; + sal_Int32 nXPos = 0, nYPos = 0; + text::WrapTextMode nSurround = text::WrapTextMode_NONE; + if( SVX_CSS1_POS_ABSOLUTE == rCSS1PropInfo.m_ePosition && + SVX_CSS1_LTYPE_TWIP == rCSS1PropInfo.m_eLeftType && + SVX_CSS1_LTYPE_TWIP == rCSS1PropInfo.m_eTopType ) + { + const SwStartNode *pFlySttNd = + m_pPam->GetPoint()->GetNode().FindFlyStartNode(); + + if( pFlySttNd ) + { + nAnchorType = text::TextContentAnchorType_AT_FRAME; + SwPaM aPaM( *pFlySttNd ); + + uno::Reference< text::XText > xDummyTextRef; // dirty, but works according to OS... + xTextRg = new SwXTextRange( aPaM, xDummyTextRef ); + } + else + { + nAnchorType = text::TextContentAnchorType_AT_PAGE; + } + nXPos = convertTwipToMm100( rCSS1PropInfo.m_nLeft ) + nLeftSpace; + nYPos = convertTwipToMm100( rCSS1PropInfo.m_nTop ) + nUpperSpace; + bSetPos = true; + + nSurround = text::WrapTextMode_THROUGH; + bSetSurround = true; + } + else if( SvxAdjust::Left == rCSS1PropInfo.m_eFloat || + text::HoriOrientation::LEFT == eHoriOri ) + { + nAnchorType = text::TextContentAnchorType_AT_PARAGRAPH; + nXPos = nLeftSpace; + nYPos = nUpperSpace; + bSetPos = true; + nSurround = text::WrapTextMode_RIGHT; + bSetSurround = true; + } + else if( text::VertOrientation::NONE != eVertOri ) + { + sal_Int16 nVertOri = text::VertOrientation::NONE; + switch( eVertOri ) + { + case text::VertOrientation::TOP: + nVertOri = text::VertOrientation::TOP; + break; + case text::VertOrientation::CENTER: + nVertOri = text::VertOrientation::CENTER; + break; + case text::VertOrientation::BOTTOM: + nVertOri = text::VertOrientation::BOTTOM; + break; + case text::VertOrientation::CHAR_TOP: + nVertOri = text::VertOrientation::CHAR_TOP; + break; + case text::VertOrientation::CHAR_CENTER: + nVertOri = text::VertOrientation::CHAR_CENTER; + break; + case text::VertOrientation::CHAR_BOTTOM: + nVertOri = text::VertOrientation::CHAR_BOTTOM; + break; + case text::VertOrientation::LINE_TOP: + nVertOri = text::VertOrientation::LINE_TOP; + break; + case text::VertOrientation::LINE_CENTER: + nVertOri = text::VertOrientation::LINE_CENTER; + break; + case text::VertOrientation::LINE_BOTTOM: + nVertOri = text::VertOrientation::LINE_BOTTOM; + break; + // coverity[dead_error_begin] - following conditions exist to avoid compiler warning + case text::VertOrientation::NONE: + nVertOri = text::VertOrientation::NONE; + break; + } + aTmp <<= nVertOri ; + xShapePropSet->setPropertyValue("VertOrient", aTmp ); + } + + aTmp <<= nAnchorType ; + xShapePropSet->setPropertyValue("AnchorType", aTmp ); + + if( text::TextContentAnchorType_AT_PAGE == nAnchorType ) + { + aTmp <<= sal_Int16(1) ; + xShapePropSet->setPropertyValue("AnchorPageNo", aTmp ); + } + else + { + if( !xTextRg.is() ) + { + uno::Reference< text::XText > xDummyTextRef; // dirty but works according to OS... + xTextRg = new SwXTextRange( *m_pPam, xDummyTextRef ); + } + + aTmp <<= xTextRg; + xShapePropSet->setPropertyValue("TextRange", aTmp ); + } + + if( bSetPos ) + { + aTmp <<= sal_Int16(text::HoriOrientation::NONE); + xShapePropSet->setPropertyValue("HoriOrient", aTmp ); + aTmp <<= nXPos ; + xShapePropSet->setPropertyValue("HoriOrientPosition", aTmp ); + + aTmp <<= sal_Int16(text::VertOrientation::NONE); + xShapePropSet->setPropertyValue("VertOrient", aTmp ); + aTmp <<= nYPos ; + xShapePropSet->setPropertyValue("VertOrientPosition", aTmp ); + } + if( bSetSurround ) + { + aTmp <<= nSurround ; + xShapePropSet->setPropertyValue("Surround", aTmp ); + } + + m_pFormImpl->GetShapes()->add(xShape); + + // Set ControlModel to ControlShape + uno::Reference< drawing::XControlShape > xControlShape( xShape, UNO_QUERY ); + uno::Reference< awt::XControlModel > xControlModel( rFComp, UNO_QUERY ); + xControlShape->setControl( xControlModel ); + } + + // Since the focus is set at insertion of the controls, focus events will be sent + // To prevent previous JavaScript-Events from being called, these events will only be set retroactively + if( !rMacroTable.empty() || !rUnoMacroTable.empty() ) + { + bool bHasEvents = lcl_html_setEvents( m_pFormImpl->GetControlEventManager(), + rFormComps->getCount() - 1, + rMacroTable, rUnoMacroTable, rUnoMacroParamTable, + GetScriptTypeString(m_pFormImpl->GetHeaderAttrs()) ); + if (bHasEvents) + NotifyMacroEventRead(); + } + + if( bSetFCompPropSet ) + { + m_pFormImpl->SetFCompPropSet( rFCompPropSet ); + } + + return xShape; +} + +void SwHTMLParser::NewForm( bool bAppend ) +{ + // Does a form already exist? + if( m_pFormImpl && m_pFormImpl->GetFormComps().is() ) + return; + + if( bAppend ) + { + if( m_pPam->GetPoint()->GetContentIndex() ) + AppendTextNode( AM_SPACE ); + else + AddParSpace(); + } + + if( !m_pFormImpl ) + m_pFormImpl = new SwHTMLForm_Impl( m_xDoc->GetDocShell() ); + + OUString aAction( m_sBaseURL ); + OUString sName, sTarget; + FormSubmitEncoding nEncType = FormSubmitEncoding_URL; + FormSubmitMethod nMethod = FormSubmitMethod_GET; + SvxMacroTableDtor aMacroTable; + std::vector<OUString> aUnoMacroTable; + std::vector<OUString> aUnoMacroParamTable; + SvKeyValueIterator *pHeaderAttrs = m_pFormImpl->GetHeaderAttrs(); + ScriptType eDfltScriptType = GetScriptType( pHeaderAttrs ); + const OUString& rDfltScriptType = GetScriptTypeString( pHeaderAttrs ); + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + ScriptType eScriptType2 = eDfltScriptType; + SvMacroItemId nEvent = SvMacroItemId::NONE; + bool bSetEvent = false; + + switch( rOption.GetToken() ) + { + case HtmlOptionId::ACTION: + aAction = rOption.GetString(); + break; + case HtmlOptionId::METHOD: + nMethod = rOption.GetEnum( aHTMLFormMethodTable, nMethod ); + break; + case HtmlOptionId::ENCTYPE: + nEncType = rOption.GetEnum( aHTMLFormEncTypeTable, nEncType ); + break; + case HtmlOptionId::TARGET: + sTarget = rOption.GetString(); + break; + case HtmlOptionId::NAME: + sName = rOption.GetString(); + break; + + case HtmlOptionId::SDONSUBMIT: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONSUBMIT: + nEvent = SvMacroItemId::HtmlOnSubmitForm; + bSetEvent = true; + break; + + case HtmlOptionId::SDONRESET: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONRESET: + nEvent = SvMacroItemId::HtmlOnResetForm; + bSetEvent = true; + break; + + default: + lcl_html_getEvents( rOption.GetTokenString(), + rOption.GetString(), + aUnoMacroTable, aUnoMacroParamTable ); + break; + } + + if( bSetEvent ) + { + OUString sEvent( rOption.GetString() ); + if( !sEvent.isEmpty() ) + { + sEvent = convertLineEnd(sEvent, GetSystemLineEnd()); + OUString aScriptType2; + if( EXTENDED_STYPE==eScriptType2 ) + aScriptType2 = rDfltScriptType; + aMacroTable.Insert( nEvent, SvxMacro( sEvent, aScriptType2, eScriptType2 ) ); + } + } + } + + const uno::Reference< XMultiServiceFactory > & rSrvcMgr = + m_pFormImpl->GetServiceFactory(); + if( !rSrvcMgr.is() ) + return; + + uno::Reference< XInterface > xInt; + uno::Reference<XForm> xForm; + try + { + xInt = rSrvcMgr->createInstance("com.sun.star.form.component.Form"); + if (!xInt.is()) + return; + xForm.set(xInt, UNO_QUERY); + SAL_WARN_IF(!xForm.is(), "sw", "no XForm for com.sun.star.form.component.Form?"); + if (!xForm.is()) + return; + } + catch (...) + { + TOOLS_WARN_EXCEPTION("sw", ""); + return; + } + + uno::Reference< container::XIndexContainer > xFormComps( xForm, UNO_QUERY ); + m_pFormImpl->SetFormComps( xFormComps ); + + uno::Reference< beans::XPropertySet > xFormPropSet( xForm, UNO_QUERY ); + + Any aTmp; + aTmp <<= sName; + xFormPropSet->setPropertyValue("Name", aTmp ); + + if( !aAction.isEmpty() ) + { + aAction = URIHelper::SmartRel2Abs(INetURLObject(m_sBaseURL), aAction, Link<OUString *, bool>(), false); + } + else + { + // use directory at empty URL + INetURLObject aURLObj( m_aPathToFile ); + aAction = aURLObj.GetPartBeforeLastName(); + } + aTmp <<= aAction; + xFormPropSet->setPropertyValue("TargetURL", + aTmp ); + + aTmp <<= nMethod; + xFormPropSet->setPropertyValue("SubmitMethod", + aTmp ); + + aTmp <<= nEncType; + xFormPropSet->setPropertyValue("SubmitEncoding", aTmp ); + + if( !sTarget.isEmpty() ) + { + aTmp <<= sTarget; + xFormPropSet->setPropertyValue( "TargetFrame", aTmp ); + } + + const uno::Reference< container::XIndexContainer > & rForms = + m_pFormImpl->GetForms(); + Any aAny( &xForm, cppu::UnoType<XForm>::get()); + rForms->insertByIndex( rForms->getCount(), aAny ); + if( !aMacroTable.empty() ) + { + bool bHasEvents = lcl_html_setEvents( m_pFormImpl->GetFormEventManager(), + rForms->getCount() - 1, + aMacroTable, aUnoMacroTable, aUnoMacroParamTable, + rDfltScriptType ); + if (bHasEvents) + NotifyMacroEventRead(); + } +} + +void SwHTMLParser::EndForm( bool bAppend ) +{ + if( m_pFormImpl && m_pFormImpl->GetFormComps().is() ) + { + if( bAppend ) + { + if( m_pPam->GetPoint()->GetContentIndex() ) + AppendTextNode( AM_SPACE ); + else + AddParSpace(); + } + + m_pFormImpl->ReleaseFormComps(); + } +} + +void SwHTMLParser::InsertInput() +{ + assert(m_vPendingStack.empty()); + + if( !m_pFormImpl || !m_pFormImpl->GetFormComps().is() ) + return; + + OUString sImgSrc, aId, aClass, aStyle, sName; + OUString sText; + SvxMacroTableDtor aMacroTable; + std::vector<OUString> aUnoMacroTable; + std::vector<OUString> aUnoMacroParamTable; + sal_uInt16 nSize = 0; + sal_Int16 nMaxLen = 0; + sal_Int16 nChecked = TRISTATE_FALSE; + sal_Int32 nTabIndex = TABINDEX_MAX + 1; + HTMLInputType eType = HTMLInputType::Text; + bool bDisabled = false, bValue = false; + bool bSetGrfWidth = false, bSetGrfHeight = false; + bool bHidden = false; + tools::Long nWidth=0, nHeight=0; + sal_Int16 eVertOri = text::VertOrientation::TOP; + sal_Int16 eHoriOri = text::HoriOrientation::NONE; + SvKeyValueIterator *pHeaderAttrs = m_pFormImpl->GetHeaderAttrs(); + ScriptType eDfltScriptType = GetScriptType( pHeaderAttrs ); + const OUString& rDfltScriptType = GetScriptTypeString( pHeaderAttrs ); + + HtmlOptionId nKeepCRLFToken = HtmlOptionId::VALUE; + const HTMLOptions& rHTMLOptions = GetOptions( &nKeepCRLFToken ); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + ScriptType eScriptType2 = eDfltScriptType; + SvMacroItemId nEvent = SvMacroItemId::NONE; + bool bSetEvent = false; + + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::TYPE: + eType = rOption.GetInputType(); + break; + case HtmlOptionId::NAME: + sName = rOption.GetString(); + break; + case HtmlOptionId::VALUE: + sText = rOption.GetString(); + bValue = true; + break; + case HtmlOptionId::CHECKED: + nChecked = TRISTATE_TRUE; + break; + case HtmlOptionId::DISABLED: + bDisabled = true; + break; + case HtmlOptionId::MAXLENGTH: + nMaxLen = static_cast<sal_Int16>(rOption.GetNumber()); + break; + case HtmlOptionId::SIZE: + nSize = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + break; + case HtmlOptionId::SRC: + sImgSrc = rOption.GetString(); + break; + case HtmlOptionId::WIDTH: + // only save pixel values at first! + nWidth = rOption.GetNumber(); + break; + case HtmlOptionId::HEIGHT: + // only save pixel values at first! + nHeight = rOption.GetNumber(); + break; + case HtmlOptionId::ALIGN: + eVertOri = + rOption.GetEnum( aHTMLImgVAlignTable, eVertOri ); + eHoriOri = + rOption.GetEnum( aHTMLImgHAlignTable, eHoriOri ); + break; + case HtmlOptionId::TABINDEX: + // only save pixel values at first! + nTabIndex = rOption.GetNumber(); + break; + + case HtmlOptionId::SDONFOCUS: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONFOCUS: + nEvent = SvMacroItemId::HtmlOnGetFocus; + bSetEvent = true; + break; + + case HtmlOptionId::SDONBLUR: // actually only EDIT + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONBLUR: + nEvent = SvMacroItemId::HtmlOnLoseFocus; + bSetEvent = true; + break; + + case HtmlOptionId::SDONCLICK: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONCLICK: + nEvent = SvMacroItemId::HtmlOnClick; + bSetEvent = true; + break; + + case HtmlOptionId::SDONCHANGE: // actually only EDIT + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONCHANGE: + nEvent = SvMacroItemId::HtmlOnChange; + bSetEvent = true; + break; + + case HtmlOptionId::SDONSELECT: // actually only EDIT + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONSELECT: + nEvent = SvMacroItemId::HtmlOnSelect; + bSetEvent = true; + break; + + default: + lcl_html_getEvents( rOption.GetTokenString(), + rOption.GetString(), + aUnoMacroTable, aUnoMacroParamTable ); + break; + } + + if( bSetEvent ) + { + OUString sEvent( rOption.GetString() ); + if( !sEvent.isEmpty() ) + { + sEvent = convertLineEnd(sEvent, GetSystemLineEnd()); + OUString aScriptType2; + if( EXTENDED_STYPE==eScriptType2 ) + aScriptType2 = rDfltScriptType; + aMacroTable.Insert( nEvent, SvxMacro( sEvent, aScriptType2, eScriptType2 ) ); + } + } + } + + if( HTMLInputType::Image==eType ) + { + // Image controls without image URL are ignored (same as MS) + if( sImgSrc.isEmpty() ) + return; + } + else + { + // evaluation of ALIGN for all controls is not a good idea as long as + // paragraph bound controls do not influence the height of the cells of a table + eVertOri = text::VertOrientation::TOP; + eHoriOri = text::HoriOrientation::NONE; + } + + // Default is HTMLInputType::Text + const char *pType = "TextField"; + bool bKeepCRLFInValue = false; + switch( eType ) + { + case HTMLInputType::Checkbox: + pType = "CheckBox"; + bKeepCRLFInValue = true; + break; + + case HTMLInputType::Radio: + pType = "RadioButton"; + bKeepCRLFInValue = true; + break; + + case HTMLInputType::Password: + bKeepCRLFInValue = true; + break; + + case HTMLInputType::Button: + bKeepCRLFInValue = true; + [[fallthrough]]; + case HTMLInputType::Submit: + case HTMLInputType::Reset: + pType = "CommandButton"; + break; + + case HTMLInputType::Image: + pType = "ImageButton"; + break; + + case HTMLInputType::File: + pType = "FileControl"; + break; + + case HTMLInputType::Hidden: + pType = "HiddenControl"; + bKeepCRLFInValue = true; + break; + default: + ; + } + + // For some controls CR/LF has to be deleted from VALUE + if( !bKeepCRLFInValue ) + { + sText = sText.replaceAll("\r", "").replaceAll("\n", ""); + } + + const uno::Reference< XMultiServiceFactory > & rServiceFactory = + m_pFormImpl->GetServiceFactory(); + if( !rServiceFactory.is() ) + return; + + OUString sServiceName = "com.sun.star.form.component." + + OUString::createFromAscii(pType); + uno::Reference< XInterface > xInt = + rServiceFactory->createInstance( sServiceName ); + if( !xInt.is() ) + return; + + uno::Reference< XFormComponent > xFComp( xInt, UNO_QUERY ); + if( !xFComp.is() ) + return; + + uno::Reference< beans::XPropertySet > xPropSet( xFComp, UNO_QUERY ); + + Any aTmp; + aTmp <<= sName; + xPropSet->setPropertyValue("Name", aTmp ); + + if( HTMLInputType::Hidden != eType ) + { + if( nTabIndex >= TABINDEX_MIN && nTabIndex <= TABINDEX_MAX ) + { + aTmp <<= static_cast<sal_Int16>(nTabIndex) ; + xPropSet->setPropertyValue("TabIndex", aTmp ); + } + + if( bDisabled ) + { + xPropSet->setPropertyValue("Enabled", Any(false) ); + } + } + + aTmp <<= sText; + + Size aSz( 0, 0 ); // defaults + Size aTextSz( 0, 0 ); // Text size + bool bMinWidth = false, bMinHeight = false; + bool bUseSize = false; + switch( eType ) + { + case HTMLInputType::Checkbox: + case HTMLInputType::Radio: + { + if( !bValue ) + aTmp <<= OUString( OOO_STRING_SVTOOLS_HTML_on ); + xPropSet->setPropertyValue("RefValue", + aTmp ); + aTmp <<= OUString(); + xPropSet->setPropertyValue("Label", + aTmp ); + // RadioButton: The DefaultChecked property should only be set + // if the control has been created and activateTabOrder has been called + // because otherwise it would still belong to the previous group. + if( HTMLInputType::Checkbox == eType ) + { + aTmp <<= nChecked ; + xPropSet->setPropertyValue("DefaultState", aTmp ); + } + + const SvxMacro* pMacro = aMacroTable.Get( SvMacroItemId::HtmlOnClick ); + if( pMacro ) + { + aMacroTable.Insert( SvMacroItemId::HtmlOnClickItem, *pMacro ); + aMacroTable.Erase( SvMacroItemId::HtmlOnClick ); + } + // evaluating SIZE shouldn't be necessary here? + bMinWidth = bMinHeight = true; + } + break; + + case HTMLInputType::Image: + { + // SIZE = WIDTH + aSz.setWidth(o3tl::convert(nWidth, o3tl::Length::px, o3tl::Length::mm100)); + aSz.setHeight(o3tl::convert(nHeight, o3tl::Length::px, o3tl::Length::mm100)); + aTmp <<= FormButtonType_SUBMIT; + xPropSet->setPropertyValue("ButtonType", aTmp ); + + aTmp <<= sal_Int16(0) ; + xPropSet->setPropertyValue("Border", + aTmp ); + } + break; + + case HTMLInputType::Button: + case HTMLInputType::Submit: + case HTMLInputType::Reset: + { + FormButtonType eButtonType; + switch( eType ) + { + case HTMLInputType::Button: + eButtonType = FormButtonType_PUSH; + break; + case HTMLInputType::Submit: + eButtonType = FormButtonType_SUBMIT; + if (sText.isEmpty()) + sText = OOO_STRING_SVTOOLS_HTML_IT_submit; + break; + case HTMLInputType::Reset: + eButtonType = FormButtonType_RESET; + if (sText.isEmpty()) + sText = OOO_STRING_SVTOOLS_HTML_IT_reset; + break; + default: + ; + } + aTmp <<= sText; + xPropSet->setPropertyValue("Label", + aTmp ); + + aTmp <<= eButtonType; + xPropSet->setPropertyValue("ButtonType", aTmp ); + + bMinWidth = bMinHeight = true; + bUseSize = true; + } + break; + + case HTMLInputType::Password: + case HTMLInputType::Text: + case HTMLInputType::File: + if( HTMLInputType::File != eType ) + { + // The VALUE of file control will be ignored for security reasons + xPropSet->setPropertyValue("DefaultText", aTmp ); + if( nMaxLen != 0 ) + { + aTmp <<= nMaxLen ; + xPropSet->setPropertyValue("MaxTextLen", aTmp ); + } + } + + if( HTMLInputType::Password == eType ) + { + aTmp <<= sal_Int16('*') ; + xPropSet->setPropertyValue("EchoChar", aTmp ); + } + + lcl_html_setFixedFontProperty( xPropSet ); + + if( !nSize ) + nSize = 20; + aTextSz.setWidth( nSize ); + bMinHeight = true; + break; + + case HTMLInputType::Hidden: + xPropSet->setPropertyValue("HiddenValue", aTmp ); + bHidden = true; + break; + default: + ; + } + + if( bUseSize && nSize>0 ) + { + aSz.setWidth(o3tl::convert(nSize, o3tl::Length::px, o3tl::Length::mm100)); + OSL_ENSURE( !aTextSz.Width(), "text width is present" ); + bMinWidth = false; + } + + SfxItemSet aCSS1ItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aCSS1PropInfo; + if( HasStyleOptions( aStyle, aId, aClass ) ) + { + ParseStyleOptions( aStyle, aId, aClass, aCSS1ItemSet, aCSS1PropInfo ); + if( !aId.isEmpty() ) + InsertBookmark( aId ); + } + + if( SVX_CSS1_LTYPE_TWIP== aCSS1PropInfo.m_eWidthType ) + { + aSz.setWidth( convertTwipToMm100( aCSS1PropInfo.m_nWidth ) ); + aTextSz.setWidth( 0 ); + bMinWidth = false; + } + if( SVX_CSS1_LTYPE_TWIP== aCSS1PropInfo.m_eHeightType ) + { + aSz.setHeight( convertTwipToMm100( aCSS1PropInfo.m_nHeight ) ); + aTextSz.setHeight( 0 ); + bMinHeight = false; + } + + // Set sensible default values if the image button has no valid size + if( HTMLInputType::Image== eType ) + { + if( !aSz.Width() ) + { + aSz.setWidth( HTML_DFLT_IMG_WIDTH ); + bSetGrfWidth = true; + if (m_xTable) + IncGrfsThatResizeTable(); + } + if( !aSz.Height() ) + { + aSz.setHeight( HTML_DFLT_IMG_HEIGHT ); + bSetGrfHeight = true; + } + } + if( aSz.Width() < MINFLY ) + aSz.setWidth( MINFLY ); + if( aSz.Height() < MINFLY ) + aSz.setHeight( MINFLY ); + + uno::Reference< drawing::XShape > xShape = InsertControl( + xFComp, xPropSet, aSz, + eVertOri, eHoriOri, + aCSS1ItemSet, aCSS1PropInfo, + aMacroTable, aUnoMacroTable, + aUnoMacroParamTable, false, + bHidden ); + if( aTextSz.Width() || aTextSz.Height() || bMinWidth || bMinHeight ) + { + OSL_ENSURE( !(bSetGrfWidth || bSetGrfHeight), "Adjust graphic size???" ); + SetControlSize( xShape, aTextSz, bMinWidth, bMinHeight ); + } + + if( HTMLInputType::Radio == eType ) + { + aTmp <<= nChecked ; + xPropSet->setPropertyValue("DefaultState", aTmp ); + } + + if( HTMLInputType::Image == eType ) + { + // Set the URL after inserting the graphic because the Download can + // only register with XModel after the control has been inserted. + aTmp <<= URIHelper::SmartRel2Abs(INetURLObject(m_sBaseURL), sImgSrc, Link<OUString *, bool>(), false); + xPropSet->setPropertyValue("ImageURL", + aTmp ); + } + + if( bSetGrfWidth || bSetGrfHeight ) + { + rtl::Reference<SwHTMLImageWatcher> pWatcher = + new SwHTMLImageWatcher( xShape, bSetGrfWidth, bSetGrfHeight ); + pWatcher->start(); + } +} + +void SwHTMLParser::NewTextArea() +{ + assert(m_vPendingStack.empty()); + + OSL_ENSURE( !m_bTextArea, "TextArea in TextArea?" ); + OSL_ENSURE( !m_pFormImpl || !m_pFormImpl->GetFCompPropSet().is(), + "TextArea in Control?" ); + + if( !m_pFormImpl || !m_pFormImpl->GetFormComps().is() ) + { + // Close special treatment for TextArea in the parser + FinishTextArea(); + return; + } + + OUString aId, aClass, aStyle; + OUString sName; + sal_Int32 nTabIndex = TABINDEX_MAX + 1; + SvxMacroTableDtor aMacroTable; + std::vector<OUString> aUnoMacroTable; + std::vector<OUString> aUnoMacroParamTable; + sal_uInt16 nRows = 0, nCols = 0; + HTMLWordWrapMode nWrap = HTML_WM_OFF; + bool bDisabled = false; + SvKeyValueIterator *pHeaderAttrs = m_pFormImpl->GetHeaderAttrs(); + ScriptType eDfltScriptType = GetScriptType( pHeaderAttrs ); + const OUString& rDfltScriptType = GetScriptTypeString( pHeaderAttrs ); + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + ScriptType eScriptType2 = eDfltScriptType; + SvMacroItemId nEvent = SvMacroItemId::NONE; + bool bSetEvent = false; + + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::NAME: + sName = rOption.GetString(); + break; + case HtmlOptionId::DISABLED: + bDisabled = true; + break; + case HtmlOptionId::ROWS: + nRows = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + break; + case HtmlOptionId::COLS: + nCols = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + break; + case HtmlOptionId::WRAP: + nWrap = rOption.GetEnum( aHTMLTextAreaWrapTable, nWrap ); + break; + + case HtmlOptionId::TABINDEX: + nTabIndex = rOption.GetSNumber(); + break; + + case HtmlOptionId::SDONFOCUS: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONFOCUS: + nEvent = SvMacroItemId::HtmlOnGetFocus; + bSetEvent = true; + break; + + case HtmlOptionId::SDONBLUR: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONBLUR: + nEvent = SvMacroItemId::HtmlOnLoseFocus; + bSetEvent = true; + break; + + case HtmlOptionId::SDONCLICK: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONCLICK: + nEvent = SvMacroItemId::HtmlOnClick; + bSetEvent = true; + break; + + case HtmlOptionId::SDONCHANGE: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONCHANGE: + nEvent = SvMacroItemId::HtmlOnChange; + bSetEvent = true; + break; + + case HtmlOptionId::SDONSELECT: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONSELECT: + nEvent = SvMacroItemId::HtmlOnSelect; + bSetEvent = true; + break; + + default: + lcl_html_getEvents( rOption.GetTokenString(), + rOption.GetString(), + aUnoMacroTable, aUnoMacroParamTable ); + break; + } + + if( bSetEvent ) + { + OUString sEvent( rOption.GetString() ); + if( !sEvent.isEmpty() ) + { + sEvent = convertLineEnd(sEvent, GetSystemLineEnd()); + if( EXTENDED_STYPE==eScriptType2 ) + m_aScriptType = rDfltScriptType; + aMacroTable.Insert( nEvent, SvxMacro( sEvent, m_aScriptType, eScriptType2 ) ); + } + } + } + + const uno::Reference< lang::XMultiServiceFactory > & rSrvcMgr = + m_pFormImpl->GetServiceFactory(); + if( !rSrvcMgr.is() ) + { + FinishTextArea(); + return; + } + uno::Reference< uno::XInterface > xInt = rSrvcMgr->createInstance( + "com.sun.star.form.component.TextField" ); + if( !xInt.is() ) + { + FinishTextArea(); + return; + } + + uno::Reference< XFormComponent > xFComp( xInt, UNO_QUERY ); + OSL_ENSURE( xFComp.is(), "no FormComponent?" ); + + uno::Reference< beans::XPropertySet > xPropSet( xFComp, UNO_QUERY ); + + Any aTmp; + aTmp <<= sName; + xPropSet->setPropertyValue("Name", aTmp ); + + aTmp <<= true; + xPropSet->setPropertyValue("MultiLine", aTmp ); + xPropSet->setPropertyValue("VScroll", aTmp ); + if( HTML_WM_OFF == nWrap ) + xPropSet->setPropertyValue("HScroll", aTmp ); + if( HTML_WM_HARD == nWrap ) + xPropSet->setPropertyValue("HardLineBreaks", aTmp ); + + if( nTabIndex >= TABINDEX_MIN && nTabIndex <= TABINDEX_MAX ) + { + aTmp <<= static_cast<sal_Int16>(nTabIndex) ; + xPropSet->setPropertyValue("TabIndex", aTmp ); + } + + lcl_html_setFixedFontProperty( xPropSet ); + + if( bDisabled ) + { + xPropSet->setPropertyValue("Enabled", Any(false) ); + } + + OSL_ENSURE( m_pFormImpl->GetText().isEmpty(), "Text is not empty!" ); + + if( !nCols ) + nCols = 20; + if( !nRows ) + nRows = 1; + + Size aTextSz( nCols, nRows ); + + SfxItemSet aCSS1ItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aCSS1PropInfo; + if( HasStyleOptions( aStyle, aId, aClass ) ) + { + ParseStyleOptions( aStyle, aId, aClass, aCSS1ItemSet, aCSS1PropInfo ); + if( !aId.isEmpty() ) + InsertBookmark( aId ); + } + + Size aSz( MINFLY, MINFLY ); + if( SVX_CSS1_LTYPE_TWIP== aCSS1PropInfo.m_eWidthType ) + { + aSz.setWidth( convertTwipToMm100( aCSS1PropInfo.m_nWidth ) ); + aTextSz.setWidth( 0 ); + } + if( SVX_CSS1_LTYPE_TWIP== aCSS1PropInfo.m_eHeightType ) + { + aSz.setHeight( convertTwipToMm100( aCSS1PropInfo.m_nHeight ) ); + aTextSz.setHeight( 0 ); + } + if( aSz.Width() < MINFLY ) + aSz.setWidth( MINFLY ); + if( aSz.Height() < MINFLY ) + aSz.setHeight( MINFLY ); + + uno::Reference< drawing::XShape > xShape = InsertControl( xFComp, xPropSet, aSz, + text::VertOrientation::TOP, text::HoriOrientation::NONE, + aCSS1ItemSet, aCSS1PropInfo, + aMacroTable, aUnoMacroTable, + aUnoMacroParamTable ); + if( aTextSz.Width() || aTextSz.Height() ) + SetControlSize( xShape, aTextSz, false, false ); + + // create new context + std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(HtmlTokenId::TEXTAREA_ON)); + + // temporarily disable PRE/Listing/XMP + SplitPREListingXMP(xCntxt.get()); + PushContext(xCntxt); + + m_bTextArea = true; + m_bTAIgnoreNewPara = true; +} + +void SwHTMLParser::EndTextArea() +{ + OSL_ENSURE( m_bTextArea, "no TextArea or wrong type" ); + OSL_ENSURE( m_pFormImpl && m_pFormImpl->GetFCompPropSet().is(), + "TextArea missing" ); + + const uno::Reference< beans::XPropertySet > & rPropSet = + m_pFormImpl->GetFCompPropSet(); + + Any aTmp; + aTmp <<= m_pFormImpl->GetText(); + rPropSet->setPropertyValue("DefaultText", aTmp ); + m_pFormImpl->EraseText(); + + m_pFormImpl->ReleaseFCompPropSet(); + + // get context + std::unique_ptr<HTMLAttrContext> xCntxt(PopContext(HtmlTokenId::TEXTAREA_ON)); + if (xCntxt) + { + // end attributes + EndContext(xCntxt.get()); + } + + m_bTextArea = false; +} + +void SwHTMLParser::InsertTextAreaText( HtmlTokenId nToken ) +{ + OSL_ENSURE( m_bTextArea, "no TextArea or wrong type" ); + OSL_ENSURE( m_pFormImpl && m_pFormImpl->GetFCompPropSet().is(), + "TextArea missing" ); + + OUString& rText = m_pFormImpl->GetText(); + switch( nToken) + { + case HtmlTokenId::TEXTTOKEN: + rText += aToken; + break; + case HtmlTokenId::NEWPARA: + if( !m_bTAIgnoreNewPara ) + rText += "\n"; + break; + default: + rText += "<"; + rText += sSaveToken; + if( !aToken.isEmpty() ) + { + rText += " "; + rText += aToken; + } + rText += ">"; + } + + m_bTAIgnoreNewPara = false; +} + +void SwHTMLParser::NewSelect() +{ + assert(m_vPendingStack.empty()); + + OSL_ENSURE( !m_bSelect, "Select in Select?" ); + OSL_ENSURE( !m_pFormImpl || !m_pFormImpl->GetFCompPropSet().is(), + "Select in Control?" ); + + if( !m_pFormImpl || !m_pFormImpl->GetFormComps().is() ) + return; + + OUString aId, aClass, aStyle; + OUString sName; + sal_Int32 nTabIndex = TABINDEX_MAX + 1; + SvxMacroTableDtor aMacroTable; + std::vector<OUString> aUnoMacroTable; + std::vector<OUString> aUnoMacroParamTable; + bool bMultiple = false; + bool bDisabled = false; + m_nSelectEntryCnt = 1; + SvKeyValueIterator *pHeaderAttrs = m_pFormImpl->GetHeaderAttrs(); + ScriptType eDfltScriptType = GetScriptType( pHeaderAttrs ); + const OUString& rDfltScriptType = GetScriptTypeString( pHeaderAttrs ); + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + ScriptType eScriptType2 = eDfltScriptType; + SvMacroItemId nEvent = SvMacroItemId::NONE; + bool bSetEvent = false; + + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::NAME: + sName = rOption.GetString(); + break; + case HtmlOptionId::MULTIPLE: + bMultiple = true; + break; + case HtmlOptionId::DISABLED: + bDisabled = true; + break; + case HtmlOptionId::SIZE: + m_nSelectEntryCnt = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + break; + + case HtmlOptionId::TABINDEX: + nTabIndex = rOption.GetSNumber(); + break; + + case HtmlOptionId::SDONFOCUS: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONFOCUS: + nEvent = SvMacroItemId::HtmlOnGetFocus; + bSetEvent = true; + break; + + case HtmlOptionId::SDONBLUR: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONBLUR: + nEvent = SvMacroItemId::HtmlOnLoseFocus; + bSetEvent = true; + break; + + case HtmlOptionId::SDONCLICK: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONCLICK: + nEvent = SvMacroItemId::HtmlOnClick; + bSetEvent = true; + break; + + case HtmlOptionId::SDONCHANGE: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONCHANGE: + nEvent = SvMacroItemId::HtmlOnChange; + bSetEvent = true; + break; + + default: + lcl_html_getEvents( rOption.GetTokenString(), + rOption.GetString(), + aUnoMacroTable, aUnoMacroParamTable ); + break; + } + + if( bSetEvent ) + { + OUString sEvent( rOption.GetString() ); + if( !sEvent.isEmpty() ) + { + sEvent = convertLineEnd(sEvent, GetSystemLineEnd()); + if( EXTENDED_STYPE==eScriptType2 ) + m_aScriptType = rDfltScriptType; + aMacroTable.Insert( nEvent, SvxMacro( sEvent, m_aScriptType, eScriptType2 ) ); + } + } + } + + const uno::Reference< lang::XMultiServiceFactory > & rSrvcMgr = + m_pFormImpl->GetServiceFactory(); + if( !rSrvcMgr.is() ) + { + FinishTextArea(); + return; + } + uno::Reference< uno::XInterface > xInt = rSrvcMgr->createInstance( + "com.sun.star.form.component.ListBox" ); + if( !xInt.is() ) + { + FinishTextArea(); + return; + } + + uno::Reference< XFormComponent > xFComp( xInt, UNO_QUERY ); + OSL_ENSURE(xFComp.is(), "no FormComponent?"); + + uno::Reference< beans::XPropertySet > xPropSet( xFComp, UNO_QUERY ); + + Any aTmp; + aTmp <<= sName; + xPropSet->setPropertyValue("Name", aTmp ); + + if( nTabIndex >= TABINDEX_MIN && nTabIndex <= TABINDEX_MAX ) + { + aTmp <<= static_cast<sal_Int16>(nTabIndex) ; + xPropSet->setPropertyValue("TabIndex", aTmp ); + } + + if( bDisabled ) + { + xPropSet->setPropertyValue("Enabled", Any(false) ); + } + + Size aTextSz( 0, 0 ); + bool bMinWidth = true, bMinHeight = true; + if( !bMultiple && 1==m_nSelectEntryCnt ) + { + xPropSet->setPropertyValue("Dropdown", Any(true) ); + } + else + { + if( m_nSelectEntryCnt <= 1 ) // 4 lines is default + m_nSelectEntryCnt = 4; + + if( bMultiple ) + { + xPropSet->setPropertyValue("MultiSelection", Any(true) ); + } + aTextSz.setHeight( m_nSelectEntryCnt ); + bMinHeight = false; + } + + SfxItemSet aCSS1ItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aCSS1PropInfo; + if( HasStyleOptions( aStyle, aId, aClass ) ) + { + ParseStyleOptions( aStyle, aId, aClass, aCSS1ItemSet, aCSS1PropInfo ); + if( !aId.isEmpty() ) + InsertBookmark( aId ); + } + + Size aSz( MINFLY, MINFLY ); + m_bFixSelectWidth = true; + if( SVX_CSS1_LTYPE_TWIP== aCSS1PropInfo.m_eWidthType ) + { + aSz.setWidth( convertTwipToMm100( aCSS1PropInfo.m_nWidth ) ); + m_bFixSelectWidth = false; + bMinWidth = false; + } + if( SVX_CSS1_LTYPE_TWIP== aCSS1PropInfo.m_eHeightType ) + { + aSz.setHeight( convertTwipToMm100( aCSS1PropInfo.m_nHeight ) ); + aTextSz.setHeight( 0 ); + bMinHeight = false; + } + if( aSz.Width() < MINFLY ) + aSz.setWidth( MINFLY ); + if( aSz.Height() < MINFLY ) + aSz.setHeight( MINFLY ); + + uno::Reference< drawing::XShape > xShape = InsertControl( xFComp, xPropSet, aSz, + text::VertOrientation::TOP, text::HoriOrientation::NONE, + aCSS1ItemSet, aCSS1PropInfo, + aMacroTable, aUnoMacroTable, + aUnoMacroParamTable ); + if( m_bFixSelectWidth ) + m_pFormImpl->SetShape( xShape ); + if( aTextSz.Height() || bMinWidth || bMinHeight ) + SetControlSize( xShape, aTextSz, bMinWidth, bMinHeight ); + + // create new context + std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(HtmlTokenId::SELECT_ON)); + + // temporarily disable PRE/Listing/XMP + SplitPREListingXMP(xCntxt.get()); + PushContext(xCntxt); + + m_bSelect = true; +} + +void SwHTMLParser::EndSelect() +{ + assert(m_vPendingStack.empty()); + + OSL_ENSURE( m_bSelect, "no Select" ); + OSL_ENSURE( m_pFormImpl && m_pFormImpl->GetFCompPropSet().is(), + "no select control" ); + + const uno::Reference< beans::XPropertySet > & rPropSet = + m_pFormImpl->GetFCompPropSet(); + + size_t nEntryCnt = m_pFormImpl->GetStringList().size(); + if(!m_pFormImpl->GetStringList().empty()) + { + Sequence<OUString> aList( static_cast<sal_Int32>(nEntryCnt) ); + Sequence<OUString> aValueList( static_cast<sal_Int32>(nEntryCnt) ); + OUString *pStrings = aList.getArray(); + OUString *pValues = aValueList.getArray(); + + for(size_t i = 0; i < nEntryCnt; ++i) + { + OUString sText(m_pFormImpl->GetStringList()[i]); + sText = comphelper::string::stripEnd(sText, ' '); + pStrings[i] = sText; + + sText = m_pFormImpl->GetValueList()[i]; + pValues[i] = sText; + } + + rPropSet->setPropertyValue("StringItemList", Any(aList) ); + + rPropSet->setPropertyValue("ListSourceType", Any(ListSourceType_VALUELIST) ); + + rPropSet->setPropertyValue("ListSource", Any(aValueList) ); + + size_t nSelCnt = m_pFormImpl->GetSelectedList().size(); + if( !nSelCnt && 1 == m_nSelectEntryCnt && nEntryCnt ) + { + // In a dropdown list an entry should always be selected. + m_pFormImpl->GetSelectedList().insert( m_pFormImpl->GetSelectedList().begin(), 0 ); + nSelCnt = 1; + } + Sequence<sal_Int16> aSelList( static_cast<sal_Int32>(nSelCnt) ); + sal_Int16 *pSels = aSelList.getArray(); + for(size_t i = 0; i < nSelCnt; ++i) + { + pSels[i] = static_cast<sal_Int16>(m_pFormImpl->GetSelectedList()[i]); + } + rPropSet->setPropertyValue("DefaultSelection", Any(aSelList) ); + + m_pFormImpl->EraseStringList(); + m_pFormImpl->EraseValueList(); + } + + m_pFormImpl->EraseSelectedList(); + + if( m_bFixSelectWidth ) + { + OSL_ENSURE( m_pFormImpl->GetShape().is(), "Shape not saved" ); + Size aTextSz( -1, 0 ); + SetControlSize( m_pFormImpl->GetShape(), aTextSz, false, false ); + } + + m_pFormImpl->ReleaseFCompPropSet(); + + // get context + std::unique_ptr<HTMLAttrContext> xCntxt(PopContext(HtmlTokenId::SELECT_ON)); + if (xCntxt) + { + // close attributes + EndContext(xCntxt.get()); + } + + m_bSelect = false; +} + +void SwHTMLParser::InsertSelectOption() +{ + OSL_ENSURE( m_bSelect, "no Select" ); + OSL_ENSURE( m_pFormImpl && m_pFormImpl->GetFCompPropSet().is(), + "no Select-Control" ); + + m_bLBEntrySelected = false; + OUString aValue; + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + // leave out for now + break; + case HtmlOptionId::SELECTED: + m_bLBEntrySelected = true; + break; + case HtmlOptionId::VALUE: + aValue = rOption.GetString(); + if( aValue.isEmpty() ) + aValue = "$$$empty$$$"; + break; + default: break; + } + } + + sal_uInt16 nEntryCnt = m_pFormImpl->GetStringList().size(); + m_pFormImpl->GetStringList().push_back(OUString()); + m_pFormImpl->GetValueList().push_back(aValue); + if( m_bLBEntrySelected ) + { + m_pFormImpl->GetSelectedList().push_back( nEntryCnt ); + } +} + +void SwHTMLParser::InsertSelectText() +{ + OSL_ENSURE( m_bSelect, "no select" ); + OSL_ENSURE( m_pFormImpl && m_pFormImpl->GetFCompPropSet().is(), + "no select control" ); + + if(m_pFormImpl->GetStringList().empty()) + return; + + OUString& rText = m_pFormImpl->GetStringList().back(); + + if( !aToken.isEmpty() && ' '==aToken[ 0 ] ) + { + sal_Int32 nLen = rText.getLength(); + if( !nLen || ' '==rText[nLen-1]) + aToken.remove( 0, 1 ); + } + if( !aToken.isEmpty() ) + rText += aToken; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlform.hxx b/sw/source/filter/html/htmlform.hxx new file mode 100644 index 0000000000..d1acce1faf --- /dev/null +++ b/sw/source/filter/html/htmlform.hxx @@ -0,0 +1,30 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_FILTER_HTML_HTMLFORM_HXX +#define INCLUDED_SW_SOURCE_FILTER_HTML_HTMLFORM_HXX + +extern const char* aEventListenerTable[]; +extern const char* aEventMethodTable[]; +extern const char* aEventSDOptionTable[]; +extern const char* aEventOptionTable[]; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlforw.cxx b/sw/source/filter/html/htmlforw.cxx new file mode 100644 index 0000000000..98423fc96a --- /dev/null +++ b/sw/source/filter/html/htmlforw.cxx @@ -0,0 +1,1337 @@ +/* -*- 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/form/FormSubmitEncoding.hpp> +#include <com/sun/star/form/FormSubmitMethod.hpp> +#include <com/sun/star/form/FormButtonType.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/script/XEventAttacherManager.hpp> +#include <com/sun/star/drawing/XDrawPageSupplier.hpp> +#include <com/sun/star/form/XFormsSupplier.hpp> +#include <com/sun/star/form/XForm.hpp> +#include <com/sun/star/form/FormComponentType.hpp> +#include <com/sun/star/awt/XTextLayoutConstrains.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <hintids.hxx> +#include <o3tl/any.hxx> +#include <rtl/math.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <svl/macitem.hxx> +#include <svtools/htmlout.hxx> +#include <svtools/htmlkywd.hxx> +#include <svl/urihelper.hxx> +#include <vcl/unohelp.hxx> +#include <svx/svdouno.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <osl/diagnose.h> +#include <docsh.hxx> +#include <fmtanchr.hxx> +#include <viewsh.hxx> +#include <pam.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include "wrthtml.hxx" +#include "htmlfly.hxx" +#include "htmlform.hxx" +#include <frmfmt.hxx> +#include <frameformats.hxx> +#include <memory> + +using namespace ::com::sun::star; + +const HtmlFrmOpts HTML_FRMOPTS_CONTROL = + HtmlFrmOpts::NONE; +const HtmlFrmOpts HTML_FRMOPTS_CONTROL_CSS1 = + HtmlFrmOpts::SAlign | + HtmlFrmOpts::SSize | + HtmlFrmOpts::SSpace | + HtmlFrmOpts::BrClear; +const HtmlFrmOpts HTML_FRMOPTS_IMG_CONTROL = + HtmlFrmOpts::Align | + HtmlFrmOpts::BrClear; +const HtmlFrmOpts HTML_FRMOPTS_IMG_CONTROL_CSS1 = + HtmlFrmOpts::SAlign | + HtmlFrmOpts::SSpace; + +static void lcl_html_outEvents( SvStream& rStrm, + const uno::Reference< form::XFormComponent >& rFormComp, + bool bCfgStarBasic ) +{ + uno::Reference< uno::XInterface > xParentIfc = rFormComp->getParent(); + OSL_ENSURE( xParentIfc.is(), "lcl_html_outEvents: no parent interface" ); + if( !xParentIfc.is() ) + return; + uno::Reference< container::XIndexAccess > xIndexAcc( xParentIfc, uno::UNO_QUERY ); + uno::Reference< script::XEventAttacherManager > xEventManager( xParentIfc, + uno::UNO_QUERY ); + if( !xIndexAcc.is() || !xEventManager.is() ) + return; + + // and search for the position of the ControlModel within + sal_Int32 nCount = xIndexAcc->getCount(), nPos; + for( nPos = 0 ; nPos < nCount; nPos++ ) + { + uno::Any aTmp = xIndexAcc->getByIndex(nPos); + if( auto x1 = o3tl::tryAccess<uno::Reference<form::XFormComponent>>(aTmp) ) + + { + if( rFormComp == *x1 ) + break; + } + else if( auto x2 = o3tl::tryAccess<uno::Reference<form::XForm>>(aTmp) ) + { + if( rFormComp == *x2 ) + break; + } + else + { + OSL_ENSURE( false, "lcl_html_outEvents: wrong reflection" ); + } + } + + if( nPos == nCount ) + return; + + const uno::Sequence< script::ScriptEventDescriptor > aDescs = + xEventManager->getScriptEvents( nPos ); + if( !aDescs.hasElements() ) + return; + + for( const script::ScriptEventDescriptor& rDesc : aDescs ) + { + ScriptType eScriptType = EXTENDED_STYPE; + OUString aScriptType( rDesc.ScriptType ); + if( aScriptType.equalsIgnoreAsciiCase(SVX_MACRO_LANGUAGE_JAVASCRIPT) ) + eScriptType = JAVASCRIPT; + else if( aScriptType.equalsIgnoreAsciiCase(SVX_MACRO_LANGUAGE_STARBASIC ) ) + eScriptType = STARBASIC; + if( JAVASCRIPT != eScriptType && !bCfgStarBasic ) + continue; + + OUString sListener( rDesc.ListenerType ); + if (!sListener.isEmpty()) + { + const sal_Int32 nIdx { sListener.lastIndexOf('.')+1 }; + if (nIdx>0) + { + if (nIdx<sListener.getLength()) + { + sListener = sListener.copy(nIdx); + } + else + { + sListener.clear(); + } + } + } + OUString sMethod( rDesc.EventMethod ); + + const char *pOpt = nullptr; + for( int j=0; aEventListenerTable[j]; j++ ) + { + if( sListener.equalsAscii( aEventListenerTable[j] ) && + sMethod.equalsAscii( aEventMethodTable[j] ) ) + { + pOpt = (STARBASIC==eScriptType ? aEventSDOptionTable + : aEventOptionTable)[j]; + break; + } + } + + OString sOut = " "_ostr; + if( pOpt && (EXTENDED_STYPE != eScriptType || + rDesc.AddListenerParam.isEmpty()) ) + sOut += pOpt; + else + { + sOut += OOO_STRING_SVTOOLS_HTML_O_sdevent + + OUStringToOString(sListener, RTL_TEXTENCODING_ASCII_US) + "-" + + OUStringToOString(sMethod, RTL_TEXTENCODING_ASCII_US); + } + sOut += "=\""; + rStrm.WriteOString( sOut ); + HTMLOutFuncs::Out_String( rStrm, rDesc.ScriptCode ); + rStrm.WriteChar( '\"' ); + if( EXTENDED_STYPE == eScriptType && + !rDesc.AddListenerParam.isEmpty() ) + { + sOut = " " OOO_STRING_SVTOOLS_HTML_O_sdaddparam + + OUStringToOString(sListener, RTL_TEXTENCODING_ASCII_US) + "-" + + OUStringToOString(sMethod, RTL_TEXTENCODING_ASCII_US) + "=\""; + rStrm.WriteOString( sOut ); + HTMLOutFuncs::Out_String( rStrm, rDesc.AddListenerParam ); + rStrm.WriteChar( '\"' ); + } + } +} + +static bool lcl_html_isHTMLControl( sal_Int16 nClassId ) +{ + bool bRet = false; + + switch( nClassId ) + { + case form::FormComponentType::TEXTFIELD: + case form::FormComponentType::COMMANDBUTTON: + case form::FormComponentType::RADIOBUTTON: + case form::FormComponentType::CHECKBOX: + case form::FormComponentType::LISTBOX: + case form::FormComponentType::IMAGEBUTTON: + case form::FormComponentType::FILECONTROL: + bRet = true; + break; + } + + return bRet; +} + +bool SwHTMLWriter::HasControls() const +{ + SwNodeOffset nStartIdx = m_pCurrentPam->GetPoint()->GetNodeIndex(); + size_t i = 0; + + // Skip all controls in front of the current paragraph + while ( i < m_aHTMLControls.size() && m_aHTMLControls[i]->nNdIdx < nStartIdx ) + ++i; + + return i < m_aHTMLControls.size() && m_aHTMLControls[i]->nNdIdx == nStartIdx; +} + +void SwHTMLWriter::OutForm( bool bTag_On, const SwStartNode *pStartNd ) +{ + if( m_bPreserveForm ) // we are in a table or an area with form spanned over it + return; + + if( !bTag_On ) + { + // end the form when all controls are output + if( mxFormComps.is() && + mxFormComps->getCount() == m_nFormCntrlCnt ) + { + OutForm( false, mxFormComps ); + mxFormComps.clear(); + } + return; + } + + uno::Reference< container::XIndexContainer > xNewFormComps; + SwNodeOffset nStartIdx = pStartNd ? pStartNd->GetIndex() + : m_pCurrentPam->GetPoint()->GetNodeIndex(); + + // skip controls before the interesting area + size_t i = 0; + while ( i < m_aHTMLControls.size() && m_aHTMLControls[i]->nNdIdx < nStartIdx ) + ++i; + + if( !pStartNd ) + { + // Check for a single node: there it's only interesting, if there is + // a control for the node and to which form it belongs. + if( i < m_aHTMLControls.size() && + m_aHTMLControls[i]->nNdIdx == nStartIdx ) + xNewFormComps = m_aHTMLControls[i]->xFormComps; + } + else + { + // we iterate over a table/an area: we're interested in: + // - if there are controls with different start nodes + // - if there is a form, with controls which aren't all in the table/area + + uno::Reference< container::XIndexContainer > xCurrentFormComps;// current form in table + const SwStartNode *pCurrentStNd = nullptr; // and the start node of a Control + sal_Int32 nCurrentCtrls = 0; // and the found controls in it + SwNodeOffset nEndIdx = pStartNd->EndOfSectionIndex(); + for( ; i < m_aHTMLControls.size() && + m_aHTMLControls[i]->nNdIdx <= nEndIdx; i++ ) + { + const SwStartNode *pCntrlStNd = + m_pDoc->GetNodes()[m_aHTMLControls[i]->nNdIdx]->StartOfSectionNode(); + + if( xCurrentFormComps.is() ) + { + // already inside a form ... + if( xCurrentFormComps==m_aHTMLControls[i]->xFormComps ) + { + // ... and the control is also inside ... + if( pCurrentStNd!=pCntrlStNd ) + { + // ... but it's inside another cell: + // Then open a form above the table + xNewFormComps = xCurrentFormComps; + break; + } + nCurrentCtrls = nCurrentCtrls + m_aHTMLControls[i]->nCount; + } + else + { + // ... but the Control is in another cell: + // There we act as if we open a new from and continue searching. + xCurrentFormComps = m_aHTMLControls[i]->xFormComps; + pCurrentStNd = pCntrlStNd; + nCurrentCtrls = m_aHTMLControls[i]->nCount; + } + } + else + { + // We aren't in a form: + // There we act as if we open a form. + xCurrentFormComps = m_aHTMLControls[i]->xFormComps; + pCurrentStNd = pCntrlStNd; + nCurrentCtrls = m_aHTMLControls[i]->nCount; + } + } + if( !xNewFormComps.is() && xCurrentFormComps.is() && + nCurrentCtrls != xCurrentFormComps->getCount() ) + { + // A form should be opened in the table/area which isn't completely + // inside the table. Then we must also now open the form. + xNewFormComps = xCurrentFormComps; + } + } + + if( !(xNewFormComps.is() && + (!mxFormComps.is() || xNewFormComps != mxFormComps)) ) + return; + + // A form should be opened ... + if( mxFormComps.is() ) + { + // ... but a form is still open: That is in every case an error, + // but we'll close the old form nevertheless. + OutForm( false, mxFormComps ); + + //!!!nWarn = 1; // Control will be assigned to wrong form + } + + mxFormComps = xNewFormComps; + + OutForm( true, mxFormComps ); + uno::Reference< beans::XPropertySet > xTmp; + OutHiddenControls( mxFormComps, xTmp ); +} + +void SwHTMLWriter::OutHiddenForms() +{ + // Without DrawModel there can't be controls. Then you also can't access the + // document via UNO, because otherwise a DrawModel would be created. + if( !m_pDoc->getIDocumentDrawModelAccess().GetDrawModel() ) + return; + + SwDocShell *pDocSh = m_pDoc->GetDocShell(); + if( !pDocSh ) + return; + + uno::Reference< drawing::XDrawPageSupplier > xDPSupp( pDocSh->GetBaseModel(), + uno::UNO_QUERY ); + OSL_ENSURE( xDPSupp.is(), "XTextDocument not received from XModel" ); + uno::Reference< drawing::XDrawPage > xDrawPage = xDPSupp->getDrawPage(); + + OSL_ENSURE( xDrawPage.is(), "XDrawPage not received" ); + if( !xDrawPage.is() ) + return; + + uno::Reference< form::XFormsSupplier > xFormsSupplier( xDrawPage, uno::UNO_QUERY ); + OSL_ENSURE( xFormsSupplier.is(), + "XFormsSupplier not received from XDrawPage" ); + + uno::Reference< container::XNameContainer > xTmp = xFormsSupplier->getForms(); + OSL_ENSURE( xTmp.is(), "XForms not received" ); + uno::Reference< container::XIndexContainer > xForms( xTmp, uno::UNO_QUERY ); + OSL_ENSURE( xForms.is(), "XForms without container::XIndexContainer?" ); + + sal_Int32 nCount = xForms->getCount(); + for( sal_Int32 i=0; i<nCount; i++) + { + uno::Any aTmp = xForms->getByIndex( i ); + if( auto x = o3tl::tryAccess<uno::Reference<form::XForm>>(aTmp) ) + OutHiddenForm( *x ); + else + { + OSL_ENSURE( false, "OutHiddenForms: wrong reflection" ); + } + } +} + +void SwHTMLWriter::OutHiddenForm( const uno::Reference< form::XForm > & rForm ) +{ + uno::Reference< container::XIndexContainer > xFormComps( rForm, uno::UNO_QUERY ); + if( !xFormComps.is() ) + return; + + sal_Int32 nCount = xFormComps->getCount(); + bool bHiddenOnly = nCount > 0, bHidden = false; + for( sal_Int32 i=0; i<nCount; i++ ) + { + uno::Any aTmp = xFormComps->getByIndex( i ); + auto xFormComp = o3tl::tryAccess<uno::Reference<form::XFormComponent>>( + aTmp); + OSL_ENSURE( xFormComp, "OutHiddenForm: wrong reflection" ); + if( !xFormComp ) + continue; + + uno::Reference< form::XForm > xForm( *xFormComp, uno::UNO_QUERY ); + if( xForm.is() ) + OutHiddenForm( xForm ); + + if( bHiddenOnly ) + { + uno::Reference< beans::XPropertySet > xPropSet( *xFormComp, uno::UNO_QUERY ); + OUString sPropName("ClassId"); + if( xPropSet->getPropertySetInfo()->hasPropertyByName( sPropName ) ) + { + uno::Any aAny2 = xPropSet->getPropertyValue( sPropName ); + if( auto n = o3tl::tryAccess<sal_Int16>(aAny2) ) + { + if( form::FormComponentType::HIDDENCONTROL == *n ) + bHidden = true; + else if( lcl_html_isHTMLControl( *n ) ) + bHiddenOnly = false; + } + } + } + } + + if( bHidden && bHiddenOnly ) + { + OutForm( true, xFormComps ); + uno::Reference< beans::XPropertySet > xTmp; + OutHiddenControls( xFormComps, xTmp ); + OutForm( false, xFormComps ); + } +} + +void SwHTMLWriter::OutForm( bool bOn, + const uno::Reference< container::XIndexContainer > & rFormComps ) +{ + m_nFormCntrlCnt = 0; + + if( !bOn ) + { + DecIndentLevel(); // indent content of form + if (IsLFPossible()) + OutNewLine(); + HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + OOO_STRING_SVTOOLS_HTML_form), false ); + SetLFPossible(true); + + return; + } + + // the new form is opened + if (IsLFPossible()) + OutNewLine(); + OString sOut = "<" + GetNamespace() + OOO_STRING_SVTOOLS_HTML_form; + + uno::Reference< beans::XPropertySet > xFormPropSet( rFormComps, uno::UNO_QUERY ); + + uno::Any aTmp = xFormPropSet->getPropertyValue( "Name" ); + if( auto s = o3tl::tryAccess<OUString>(aTmp) ) + { + if( !s->isEmpty() ) + { + sOut += " " OOO_STRING_SVTOOLS_HTML_O_name "=\""; + Strm().WriteOString( sOut ); + HTMLOutFuncs::Out_String( Strm(), *s ); + sOut = "\""_ostr; + } + } + + aTmp = xFormPropSet->getPropertyValue( "TargetURL" ); + if( auto s = o3tl::tryAccess<OUString>(aTmp) ) + { + if ( !s->isEmpty() ) + { + sOut += " " OOO_STRING_SVTOOLS_HTML_O_action "=\""; + Strm().WriteOString( sOut ); + OUString aURL + = URIHelper::simpleNormalizedMakeRelative( GetBaseURL(), *s); + HTMLOutFuncs::Out_String( Strm(), aURL ); + sOut = "\""_ostr; + } + } + + aTmp = xFormPropSet->getPropertyValue( "SubmitMethod" ); + if( auto eMethod = o3tl::tryAccess<form::FormSubmitMethod>(aTmp) ) + { + if( form::FormSubmitMethod_POST==*eMethod ) + { + sOut += " " OOO_STRING_SVTOOLS_HTML_O_method "=\"" + OOO_STRING_SVTOOLS_HTML_METHOD_post "\""; + } + } + aTmp = xFormPropSet->getPropertyValue( "SubmitEncoding" ); + if( auto eEncType = o3tl::tryAccess<form::FormSubmitEncoding>(aTmp) ) + { + const char *pStr = nullptr; + switch( *eEncType ) + { + case form::FormSubmitEncoding_MULTIPART: + pStr = OOO_STRING_SVTOOLS_HTML_ET_multipart; + break; + case form::FormSubmitEncoding_TEXT: + pStr = OOO_STRING_SVTOOLS_HTML_ET_text; + break; + default: + ; + } + + if( pStr ) + { + sOut += OString::Concat(" " OOO_STRING_SVTOOLS_HTML_O_enctype "=\"") + + pStr + "\""; + } + } + + aTmp = xFormPropSet->getPropertyValue( "TargetFrame" ); + if( auto s = o3tl::tryAccess<OUString>(aTmp) ) + { + if (!s->isEmpty() ) + { + sOut += " " OOO_STRING_SVTOOLS_HTML_O_target "=\""; + Strm().WriteOString( sOut ); + HTMLOutFuncs::Out_String( Strm(), *s ); + sOut = "\""_ostr; + } + } + + Strm().WriteOString( sOut ); + uno::Reference< form::XFormComponent > xFormComp( rFormComps, uno::UNO_QUERY ); + lcl_html_outEvents( Strm(), xFormComp, m_bCfgStarBasic ); + Strm().WriteChar( '>' ); + + IncIndentLevel(); // indent content of form + SetLFPossible(true); +} + +void SwHTMLWriter::OutHiddenControls( + const uno::Reference< container::XIndexContainer > & rFormComps, + const uno::Reference< beans::XPropertySet > & rPropSet ) +{ + sal_Int32 nCount = rFormComps->getCount(); + sal_Int32 nPos = 0; + if( rPropSet.is() ) + { + bool bDone = false; + + uno::Reference< form::XFormComponent > xFC( rPropSet, uno::UNO_QUERY ); + for( nPos=0; !bDone && nPos < nCount; nPos++ ) + { + uno::Any aTmp = rFormComps->getByIndex( nPos ); + auto x = o3tl::tryAccess<uno::Reference<form::XFormComponent>>(aTmp); + OSL_ENSURE( x, + "OutHiddenControls: wrong reflection" ); + bDone = x && *x == xFC; + } + } + + for( ; nPos < nCount; nPos++ ) + { + uno::Any aTmp = rFormComps->getByIndex( nPos ); + auto xFC = o3tl::tryAccess<uno::Reference<form::XFormComponent>>(aTmp); + OSL_ENSURE( xFC, + "OutHiddenControls: wrong reflection" ); + if( !xFC ) + continue; + uno::Reference< beans::XPropertySet > xPropSet( *xFC, uno::UNO_QUERY ); + + OUString sPropName = "ClassId"; + if( !xPropSet->getPropertySetInfo()->hasPropertyByName( sPropName ) ) + continue; + + aTmp = xPropSet->getPropertyValue( sPropName ); + auto n = o3tl::tryAccess<sal_Int16>(aTmp); + if( !n ) + continue; + + if( form::FormComponentType::HIDDENCONTROL == *n ) + { + if (IsLFPossible()) + OutNewLine( true ); + OString sOut = "<" + GetNamespace() + OOO_STRING_SVTOOLS_HTML_input " " + OOO_STRING_SVTOOLS_HTML_O_type "=\"" + OOO_STRING_SVTOOLS_HTML_IT_hidden "\""; + + aTmp = xPropSet->getPropertyValue( "Name" ); + if( auto s = o3tl::tryAccess<OUString>(aTmp) ) + { + if( !s->isEmpty() ) + { + sOut += " " OOO_STRING_SVTOOLS_HTML_O_name "=\""; + Strm().WriteOString( sOut ); + HTMLOutFuncs::Out_String( Strm(), *s ); + sOut = "\""_ostr; + } + } + aTmp = xPropSet->getPropertyValue( "HiddenValue" ); + if( auto s = o3tl::tryAccess<OUString>(aTmp) ) + { + if( !s->isEmpty() ) + { + sOut += " " OOO_STRING_SVTOOLS_HTML_O_value "=\""; + Strm().WriteOString( sOut ); + HTMLOutFuncs::Out_String( Strm(), *s ); + sOut = "\""_ostr; + } + } + sOut += ">"; + Strm().WriteOString( sOut ); + + m_nFormCntrlCnt++; + } + else if( lcl_html_isHTMLControl( *n ) ) + { + break; + } + } +} + +// here are the output routines, thus the form::Forms are bundled: + +const SdrObject *SwHTMLWriter::GetHTMLControl( const SwDrawFrameFormat& rFormat ) +{ + // it must be a Draw-Format + OSL_ENSURE( RES_DRAWFRMFMT == rFormat.Which(), + "GetHTMLControl only allow for Draw-Formats" ); + + // Look if a SdrObject exists for it + const SdrObject *pObj = rFormat.FindSdrObject(); + if( !pObj || SdrInventor::FmForm != pObj->GetObjInventor() ) + return nullptr; + + const SdrUnoObj& rFormObj = dynamic_cast<const SdrUnoObj&>(*pObj); + const uno::Reference< awt::XControlModel >& xControlModel = + rFormObj.GetUnoControlModel(); + + OSL_ENSURE( xControlModel.is(), "UNO-Control without model" ); + if( !xControlModel.is() ) + return nullptr; + + uno::Reference< beans::XPropertySet > xPropSet( xControlModel, uno::UNO_QUERY ); + + OUString sPropName("ClassId"); + if( !xPropSet->getPropertySetInfo()->hasPropertyByName( sPropName ) ) + return nullptr; + + uno::Any aTmp = xPropSet->getPropertyValue( sPropName ); + if( auto n = o3tl::tryAccess<sal_Int16>(aTmp) ) + { + if( lcl_html_isHTMLControl( *n ) ) + { + return pObj; + } + } + + return nullptr; +} + +static void GetControlSize(const SdrUnoObj& rFormObj, Size& rSz, SwDoc *pDoc) +{ + SwViewShell *pVSh = pDoc->getIDocumentLayoutAccess().GetCurrentViewShell(); + if( !pVSh ) + return; + + uno::Reference< awt::XControl > xControl; + SdrView* pDrawView = pVSh->GetDrawView(); + OSL_ENSURE( pDrawView && pVSh->GetWin(), "no DrawView or window!" ); + if ( pDrawView && pVSh->GetWin() ) + xControl = rFormObj.GetUnoControl( *pDrawView, *pVSh->GetWin()->GetOutDev() ); + uno::Reference< awt::XTextLayoutConstrains > xLC( xControl, uno::UNO_QUERY ); + OSL_ENSURE( xLC.is(), "no XTextLayoutConstrains" ); + if( !xLC.is() ) + return; + + sal_Int16 nCols=0, nLines=0; + xLC->getColumnsAndLines( nCols, nLines ); + rSz.setWidth( nCols ); + rSz.setHeight( nLines ); +} + +SwHTMLWriter& OutHTML_DrawFrameFormatAsControl( SwHTMLWriter& rWrt, + const SwDrawFrameFormat& rFormat, + const SdrUnoObj& rFormObj, + bool bInCntnr ) +{ + const uno::Reference< awt::XControlModel >& xControlModel = + rFormObj.GetUnoControlModel(); + + OSL_ENSURE( xControlModel.is(), "UNO-Control without model" ); + if( !xControlModel.is() ) + return rWrt; + + uno::Reference< beans::XPropertySet > xPropSet( xControlModel, uno::UNO_QUERY ); + uno::Reference< beans::XPropertySetInfo > xPropSetInfo = + xPropSet->getPropertySetInfo(); + + rWrt.m_nFormCntrlCnt++; + + enum Tag { TAG_INPUT, TAG_SELECT, TAG_TEXTAREA, TAG_NONE }; + static char const * const TagNames[] = { + OOO_STRING_SVTOOLS_HTML_input, OOO_STRING_SVTOOLS_HTML_select, + OOO_STRING_SVTOOLS_HTML_textarea }; + Tag eTag = TAG_INPUT; + enum Type { + TYPE_TEXT, TYPE_PASSWORD, TYPE_CHECKBOX, TYPE_RADIO, TYPE_FILE, + TYPE_SUBMIT, TYPE_IMAGE, TYPE_RESET, TYPE_BUTTON, TYPE_NONE }; + static char const * const TypeNames[] = { + OOO_STRING_SVTOOLS_HTML_IT_text, OOO_STRING_SVTOOLS_HTML_IT_password, + OOO_STRING_SVTOOLS_HTML_IT_checkbox, OOO_STRING_SVTOOLS_HTML_IT_radio, + OOO_STRING_SVTOOLS_HTML_IT_file, OOO_STRING_SVTOOLS_HTML_IT_submit, + OOO_STRING_SVTOOLS_HTML_IT_image, OOO_STRING_SVTOOLS_HTML_IT_reset, + OOO_STRING_SVTOOLS_HTML_IT_button }; + Type eType = TYPE_NONE; + OUString sValue; + OString sOptions; + bool bEmptyValue = false; + uno::Any aTmp = xPropSet->getPropertyValue( "ClassId" ); + sal_Int16 nClassId = *o3tl::doAccess<sal_Int16>(aTmp); + HtmlFrmOpts nFrameOpts = HTML_FRMOPTS_CONTROL; + switch( nClassId ) + { + case form::FormComponentType::CHECKBOX: + case form::FormComponentType::RADIOBUTTON: + eType = (form::FormComponentType::CHECKBOX == nClassId + ? TYPE_CHECKBOX : TYPE_RADIO); + aTmp = xPropSet->getPropertyValue( "DefaultState" ); + if( auto n = o3tl::tryAccess<sal_Int16>(aTmp) ) + { + if ( TRISTATE_FALSE != *n ) + { + sOptions += " " OOO_STRING_SVTOOLS_HTML_O_checked "=\"" + OOO_STRING_SVTOOLS_HTML_O_checked + "\""; + } + } + + aTmp = xPropSet->getPropertyValue( "RefValue" ); + if( auto rVal = o3tl::tryAccess<OUString>(aTmp) ) + + { + if( rVal->isEmpty() ) + bEmptyValue = true; + else if( *rVal != OOO_STRING_SVTOOLS_HTML_on ) + sValue = *rVal; + } + break; + + case form::FormComponentType::COMMANDBUTTON: + { + form::FormButtonType eButtonType = form::FormButtonType_PUSH; + aTmp = xPropSet->getPropertyValue( "ButtonType" ); + if( auto t = o3tl::tryAccess<form::FormButtonType>(aTmp) ) + eButtonType = *t; + + switch( eButtonType ) + { + case form::FormButtonType_RESET: + eType = TYPE_RESET; + break; + case form::FormButtonType_SUBMIT: + eType = TYPE_SUBMIT; + break; + case form::FormButtonType_PUSH: + default: + eType = TYPE_BUTTON; + } + + aTmp = xPropSet->getPropertyValue( "Label" ); + if( auto s = o3tl::tryAccess<OUString>(aTmp) ) + { + if( !s->isEmpty() ) + { + sValue = *s; + } + } + } + break; + + case form::FormComponentType::LISTBOX: + if (rWrt.IsLFPossible()) + rWrt.OutNewLine( true ); + eTag = TAG_SELECT; + aTmp = xPropSet->getPropertyValue( "Dropdown" ); + if( auto b1 = o3tl::tryAccess<bool>(aTmp) ) + { + if( !*b1 ) + { + Size aSz( 0, 0 ); + GetControlSize( rFormObj, aSz, rWrt.m_pDoc ); + + // How many are visible ?? + if( aSz.Height() ) + { + sOptions += " " OOO_STRING_SVTOOLS_HTML_O_size "=\"" + + OString::number(static_cast<sal_Int32>(aSz.Height())) + "\""; + } + + auto aTmp2 = xPropSet->getPropertyValue( "MultiSelection" ); + if( auto b2 = o3tl::tryAccess<bool>(aTmp2) ) + { + if ( *b2 ) + { + sOptions += " " OOO_STRING_SVTOOLS_HTML_O_multiple; + } + } + } + } + break; + + case form::FormComponentType::TEXTFIELD: + { + Size aSz( 0, 0 ); + GetControlSize( rFormObj, aSz, rWrt.m_pDoc ); + + bool bMultiLine = false; + OUString sMultiLine("MultiLine"); + if( xPropSetInfo->hasPropertyByName( sMultiLine ) ) + { + aTmp = xPropSet->getPropertyValue( sMultiLine ); + std::optional<const bool> b = o3tl::tryAccess<bool>(aTmp); + bMultiLine = b.has_value() && *b; + } + + if( bMultiLine ) + { + if (rWrt.IsLFPossible()) + rWrt.OutNewLine( true ); + eTag = TAG_TEXTAREA; + + if( aSz.Height() ) + { + sOptions += " " OOO_STRING_SVTOOLS_HTML_O_rows "=\"" + + OString::number(static_cast<sal_Int32>(aSz.Height())) + "\""; + } + if( aSz.Width() ) + { + sOptions += " " OOO_STRING_SVTOOLS_HTML_O_cols "=\"" + + OString::number(static_cast<sal_Int32>(aSz.Width())) + "\""; + } + + aTmp = xPropSet->getPropertyValue( "HScroll" ); + if( aTmp.getValueType() == cppu::UnoType<void>::get() || + (aTmp.getValueType() == cppu::UnoType<bool>::get() && + !*o3tl::forceAccess<bool>(aTmp)) ) + { + const char *pWrapStr = nullptr; + auto aTmp2 = xPropSet->getPropertyValue( "HardLineBreaks" ); + std::optional<const bool> b = o3tl::tryAccess<bool>(aTmp2); + pWrapStr = (b.has_value() && *b) ? OOO_STRING_SVTOOLS_HTML_WW_hard + : OOO_STRING_SVTOOLS_HTML_WW_soft; + sOptions += OString::Concat(" " OOO_STRING_SVTOOLS_HTML_O_wrap "=\"") + + pWrapStr + "\""; + } + } + else + { + eType = TYPE_TEXT; + OUString sEchoChar("EchoChar"); + if( xPropSetInfo->hasPropertyByName( sEchoChar ) ) + { + aTmp = xPropSet->getPropertyValue( sEchoChar ); + if( auto n = o3tl::tryAccess<sal_Int16>(aTmp) ) + { + if( *n != 0 ) + eType = TYPE_PASSWORD; + } + } + + if( aSz.Width() ) + { + sOptions += " " OOO_STRING_SVTOOLS_HTML_O_size "=\"" + + OString::number(static_cast<sal_Int32>(aSz.Width())) + "\""; + } + + aTmp = xPropSet->getPropertyValue( "MaxTextLen" ); + if( auto n = o3tl::tryAccess<sal_Int16>(aTmp) ) + { + if( *n != 0 ) + { + sOptions += " " OOO_STRING_SVTOOLS_HTML_O_maxlength "=\"" + + OString::number(static_cast<sal_Int32>(*n)) + "\""; + } + } + + if( xPropSetInfo->hasPropertyByName( "DefaultText" ) ) + { + aTmp = xPropSet->getPropertyValue( "DefaultText" ); + if( auto s = o3tl::tryAccess<OUString>(aTmp) ) + { + if( !s->isEmpty() ) + { + sValue = *s; + } + } + } + } + } + break; + + case form::FormComponentType::FILECONTROL: + { + Size aSz( 0, 0 ); + GetControlSize( rFormObj, aSz, rWrt.m_pDoc ); + eType = TYPE_FILE; + + if( aSz.Width() ) + { + sOptions += " " OOO_STRING_SVTOOLS_HTML_O_size "=\"" + + OString::number(static_cast<sal_Int32>(aSz.Width())) + "\""; + } + + // VALUE vim form: don't export because of security reasons + } + break; + + case form::FormComponentType::IMAGEBUTTON: + eType = TYPE_IMAGE; + nFrameOpts = HTML_FRMOPTS_IMG_CONTROL; + break; + + default: // doesn't know HTML + eTag = TAG_NONE; // therefore skip it + break; + } + + if( eTag == TAG_NONE ) + return rWrt; + + OString sOut = OString::Concat("<") + TagNames[eTag]; + if( eType != TYPE_NONE ) + { + sOut += OString::Concat(" " OOO_STRING_SVTOOLS_HTML_O_type "=\"") + + TypeNames[eType] + "\""; + } + + aTmp = xPropSet->getPropertyValue("Name"); + if( auto s = o3tl::tryAccess<OUString>(aTmp) ) + { + if( !s->isEmpty() ) + { + sOut += " " OOO_STRING_SVTOOLS_HTML_O_name "=\""; + rWrt.Strm().WriteOString( sOut ); + HTMLOutFuncs::Out_String( rWrt.Strm(), *s ); + sOut = "\""_ostr; + } + } + + aTmp = xPropSet->getPropertyValue("Enabled"); + if( auto b = o3tl::tryAccess<bool>(aTmp) ) + { + if( !*b ) + { + sOut += " " OOO_STRING_SVTOOLS_HTML_O_disabled; + } + } + + if( !sValue.isEmpty() || bEmptyValue ) + { + sOut += " " OOO_STRING_SVTOOLS_HTML_O_value "=\""; + rWrt.Strm().WriteOString( sOut ); + HTMLOutFuncs::Out_String( rWrt.Strm(), sValue ); + sOut = "\""_ostr; + } + + sOut += " " + sOptions; + + if( TYPE_IMAGE == eType ) + { + aTmp = xPropSet->getPropertyValue( "ImageURL" ); + if( auto s = o3tl::tryAccess<OUString>(aTmp) ) + { + if( !s->isEmpty() ) + { + sOut += " " OOO_STRING_SVTOOLS_HTML_O_src "=\""; + rWrt.Strm().WriteOString( sOut ); + + HTMLOutFuncs::Out_String( rWrt.Strm(), + URIHelper::simpleNormalizedMakeRelative( rWrt.GetBaseURL(), *s) ); + sOut = "\""_ostr; + } + } + + Size aPixelSz(SwHTMLWriter::ToPixel(rFormObj.GetLogicRect().GetSize())); + + if( aPixelSz.Width() ) + { + sOut += " " OOO_STRING_SVTOOLS_HTML_O_width "=\"" + + OString::number(static_cast<sal_Int32>(aPixelSz.Width())) + "\""; + } + + if( aPixelSz.Height() ) + { + sOut += " " OOO_STRING_SVTOOLS_HTML_O_height "=\"" + + OString::number(static_cast<sal_Int32>(aPixelSz.Height())) + "\""; + } + } + + aTmp = xPropSet->getPropertyValue( "TabIndex" ); + if( auto n = o3tl::tryAccess<sal_Int16>(aTmp) ) + { + sal_Int16 nTabIndex = *n; + if( nTabIndex > 0 ) + { + if( nTabIndex >= 32767 ) + nTabIndex = 32767; + + sOut += " " OOO_STRING_SVTOOLS_HTML_O_tabindex "=\"" + + OString::number(static_cast<sal_Int32>(nTabIndex)) + "\""; + } + } + + if( !sOut.isEmpty() ) + rWrt.Strm().WriteOString( sOut ); + + OSL_ENSURE( !bInCntnr, "Container is not supported for Controls" ); + if( rWrt.IsHTMLMode( HTMLMODE_ABS_POS_DRAW ) && !bInCntnr ) + { + // If Character-Objects can't be positioned absolutely, + // then delete the corresponding flag. + nFrameOpts |= (TYPE_IMAGE == eType + ? HTML_FRMOPTS_IMG_CONTROL_CSS1 + : HTML_FRMOPTS_CONTROL_CSS1); + } + OString aEndTags; + if( nFrameOpts != HtmlFrmOpts::NONE ) + aEndTags = rWrt.OutFrameFormatOptions(rFormat, OUString(), nFrameOpts); + + if( rWrt.m_bCfgOutStyles ) + { + bool bEdit = TAG_TEXTAREA == eTag || TYPE_FILE == eType || + TYPE_TEXT == eType; + + SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END> aItemSet( rWrt.m_pDoc->GetAttrPool() ); + if( xPropSetInfo->hasPropertyByName( "BackgroundColor" ) ) + { + aTmp = xPropSet->getPropertyValue( "BackgroundColor" ); + if( auto n = o3tl::tryAccess<sal_Int32>(aTmp) ) + { + Color aCol(ColorTransparency, *n); + aItemSet.Put( SvxBrushItem( aCol, RES_CHRATR_BACKGROUND ) ); + } + } + if( xPropSetInfo->hasPropertyByName( "TextColor" ) ) + { + aTmp = xPropSet->getPropertyValue( "TextColor" ); + if( auto n = o3tl::tryAccess<sal_Int32>(aTmp) ) + { + Color aColor( ColorTransparency, *n ); + aItemSet.Put( SvxColorItem( aColor, RES_CHRATR_COLOR ) ); + } + } + if( xPropSetInfo->hasPropertyByName( "FontHeight" ) ) + { + aTmp = xPropSet->getPropertyValue( "FontHeight" ); + if( auto nHeight = o3tl::tryAccess<float>(aTmp) ) + + { + if( *nHeight > 0 && (!bEdit || !rtl::math::approxEqual(*nHeight, 10.0)) ) + aItemSet.Put( SvxFontHeightItem( sal_Int16(*nHeight * 20.), 100, RES_CHRATR_FONTSIZE ) ); + } + } + if( xPropSetInfo->hasPropertyByName( "FontName" ) ) + { + aTmp = xPropSet->getPropertyValue( "FontName" ); + if( auto aFName = o3tl::tryAccess<OUString>(aTmp) ) + { + if( !aFName->isEmpty() ) + { + vcl::Font aFixedFont( OutputDevice::GetDefaultFont( + DefaultFontType::FIXED, LANGUAGE_ENGLISH_US, + GetDefaultFontFlags::OnlyOne ) ); + if( !bEdit || *aFName != aFixedFont.GetFamilyName() ) + { + FontFamily eFamily = FAMILY_DONTKNOW; + if( xPropSetInfo->hasPropertyByName( "FontFamily" ) ) + { + auto aTmp2 = xPropSet->getPropertyValue( "FontFamily" ); + if( auto n = o3tl::tryAccess<sal_Int16>(aTmp2) ) + eFamily = static_cast<FontFamily>(*n); + } + SvxFontItem aItem(eFamily, *aFName, OUString(), PITCH_DONTKNOW, RTL_TEXTENCODING_DONTKNOW, RES_CHRATR_FONT); + aItemSet.Put( aItem ); + } + } + } + } + if( xPropSetInfo->hasPropertyByName( "FontWeight" ) ) + { + aTmp = xPropSet->getPropertyValue( "FontWeight" ); + if( auto x = o3tl::tryAccess<float>(aTmp) ) + { + FontWeight eWeight = + vcl::unohelper::ConvertFontWeight( *x ); + if( eWeight != WEIGHT_DONTKNOW && eWeight != WEIGHT_NORMAL ) + aItemSet.Put( SvxWeightItem( eWeight, RES_CHRATR_WEIGHT ) ); + } + } + if( xPropSetInfo->hasPropertyByName( "FontSlant" ) ) + { + aTmp = xPropSet->getPropertyValue( "FontSlant" ); + if( auto n = o3tl::tryAccess<sal_Int16>(aTmp) ) + { + FontItalic eItalic = static_cast<FontItalic>(*n); + if( eItalic != ITALIC_DONTKNOW && eItalic != ITALIC_NONE ) + aItemSet.Put( SvxPostureItem( eItalic, RES_CHRATR_POSTURE ) ); + } + } + if( xPropSetInfo->hasPropertyByName( "FontLineStyle" ) ) + { + aTmp = xPropSet->getPropertyValue( "FontLineStyle" ); + if( auto n = o3tl::tryAccess<sal_Int16>(aTmp) ) + { + FontLineStyle eUnderline = static_cast<FontLineStyle>(*n); + if( eUnderline != LINESTYLE_DONTKNOW && + eUnderline != LINESTYLE_NONE ) + aItemSet.Put( SvxUnderlineItem( eUnderline, RES_CHRATR_UNDERLINE ) ); + } + } + if( xPropSetInfo->hasPropertyByName( "FontStrikeout" ) ) + { + aTmp = xPropSet->getPropertyValue( "FontStrikeout" ); + if( auto n = o3tl::tryAccess<sal_Int16>(aTmp) ) + { + FontStrikeout eStrikeout = static_cast<FontStrikeout>(*n); + if( eStrikeout != STRIKEOUT_DONTKNOW && + eStrikeout != STRIKEOUT_NONE ) + aItemSet.Put( SvxCrossedOutItem( eStrikeout, RES_CHRATR_CROSSEDOUT ) ); + } + } + + rWrt.OutCSS1_FrameFormatOptions( rFormat, nFrameOpts, &rFormObj, + &aItemSet ); + } + + uno::Reference< form::XFormComponent > xFormComp( xControlModel, uno::UNO_QUERY ); + lcl_html_outEvents( rWrt.Strm(), xFormComp, rWrt.m_bCfgStarBasic ); + + rWrt.Strm().WriteChar( '>' ); + + if( TAG_SELECT == eTag ) + { + aTmp = xPropSet->getPropertyValue( "StringItemList" ); + if( auto aList = o3tl::tryAccess<uno::Sequence<OUString>>(aTmp) ) + { + rWrt.IncIndentLevel(); // the content of Select can be indented + sal_Int32 nCnt = aList->getLength(); + const OUString *pStrings = aList->getConstArray(); + + const OUString *pValues = nullptr; + sal_Int32 nValCnt = 0; + auto aTmp2 = xPropSet->getPropertyValue( "ListSource" ); + uno::Sequence<OUString> aValList; + if( auto s = o3tl::tryAccess<uno::Sequence<OUString>>(aTmp2) ) + { + aValList = *s; + nValCnt = aValList.getLength(); + pValues = aValList.getConstArray(); + } + + uno::Any aSelTmp = xPropSet->getPropertyValue( "DefaultSelection" ); + const sal_Int16 *pSels = nullptr; + sal_Int32 nSel = 0; + sal_Int32 nSelCnt = 0; + uno::Sequence<sal_Int16> aSelList; + if( auto s = o3tl::tryAccess<uno::Sequence<sal_Int16>>(aSelTmp) ) + { + aSelList = *s; + nSelCnt = aSelList.getLength(); + pSels = aSelList.getConstArray(); + } + + for( sal_Int32 i = 0; i < nCnt; i++ ) + { + OUString sVal; + bool bSelected = false, bEmptyVal = false; + if( i < nValCnt ) + { + const OUString& rVal = pValues[i]; + if( rVal == "$$$empty$$$" ) + bEmptyVal = true; + else + sVal = rVal; + } + + bSelected = (nSel < nSelCnt) && pSels[nSel] == i; + if( bSelected ) + nSel++; + + rWrt.OutNewLine(); // every Option gets its own line + sOut = "<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_option; + if( !sVal.isEmpty() || bEmptyVal ) + { + sOut += " " OOO_STRING_SVTOOLS_HTML_O_value "=\""; + rWrt.Strm().WriteOString( sOut ); + HTMLOutFuncs::Out_String( rWrt.Strm(), sVal ); + sOut = "\""_ostr; + } + if( bSelected ) + sOut += " " OOO_STRING_SVTOOLS_HTML_O_selected; + + sOut += ">"; + rWrt.Strm().WriteOString( sOut ); + + HTMLOutFuncs::Out_String( rWrt.Strm(), pStrings[i] ); + } + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_option), false ); + + rWrt.DecIndentLevel(); + rWrt.OutNewLine();// the </SELECT> gets its own line + } + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_select), false ); + } + else if( TAG_TEXTAREA == eTag ) + { + // In TextAreas no additional spaces or LF may be exported! + OUString sVal; + aTmp = xPropSet->getPropertyValue( "DefaultText" ); + if( auto s = o3tl::tryAccess<OUString>(aTmp) ) + { + if( !s->isEmpty() ) + { + sVal = *s; + } + } + if( !sVal.isEmpty() ) + { + sVal = convertLineEnd(sVal, LINEEND_LF); + sal_Int32 nPos = 0; + while ( nPos != -1 ) + { + if( nPos ) + rWrt.Strm().WriteOString( SAL_NEWLINE_STRING ); + OUString aLine = sVal.getToken( 0, 0x0A, nPos ); + HTMLOutFuncs::Out_String( rWrt.Strm(), aLine ); + } + } + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_textarea), false ); + } + else if( TYPE_CHECKBOX == eType || TYPE_RADIO == eType ) + { + aTmp = xPropSet->getPropertyValue("Label"); + if( auto s = o3tl::tryAccess<OUString>(aTmp) ) + { + if( !s->isEmpty() ) + { + HTMLOutFuncs::Out_String( rWrt.Strm(), *s ).WriteChar( ' ' ); + } + } + } + + if( !aEndTags.isEmpty() ) + rWrt.Strm().WriteOString( aEndTags ); + + // Controls aren't bound to a paragraph, therefore don't output LF anymore! + rWrt.SetLFPossible(false); + + if( rWrt.mxFormComps.is() ) + rWrt.OutHiddenControls( rWrt.mxFormComps, xPropSet ); + return rWrt; +} + +/** + * Find out if a format belongs to a control and if yes return its form. + */ +static void AddControl( HTMLControls& rControls, + const SdrUnoObj& rFormObj, + SwNodeOffset nNodeIdx ) +{ + const uno::Reference< awt::XControlModel >& xControlModel = + rFormObj.GetUnoControlModel(); + if( !xControlModel.is() ) + return; + + uno::Reference< form::XFormComponent > xFormComp( xControlModel, uno::UNO_QUERY ); + uno::Reference< uno::XInterface > xIfc = xFormComp->getParent(); + uno::Reference< form::XForm > xForm(xIfc, uno::UNO_QUERY); + + OSL_ENSURE( xForm.is(), "Where is the form?" ); + if( xForm.is() ) + { + uno::Reference< container::XIndexContainer > xFormComps( xForm, uno::UNO_QUERY ); + std::unique_ptr<HTMLControl> pHCntrl(new HTMLControl( xFormComps, nNodeIdx )); + auto itPair = rControls.insert( std::move(pHCntrl) ); + if (!itPair.second ) + { + if( (*itPair.first)->xFormComps==xFormComps ) + (*itPair.first)->nCount++; + } + } +} + +void SwHTMLWriter::GetControls() +{ + // Idea: first off collect the paragraph- and character-bound controls. + // In the process for every control the paragraph position and VCForm are + // saved in an array. + // With that array it's possible to find out where form::Forms must be + // opened and closed. + + // collect the paragraph-bound controls + for( size_t i=0; i<m_aHTMLPosFlyFrames.size(); i++ ) + { + const SwHTMLPosFlyFrame* pPosFlyFrame = m_aHTMLPosFlyFrames[ i ].get(); + if( HtmlOut::Control != pPosFlyFrame->GetOutFn() ) + continue; + + const SdrObject *pSdrObj = pPosFlyFrame->GetSdrObject(); + OSL_ENSURE( pSdrObj, "Where is the SdrObject?" ); + if( !pSdrObj ) + continue; + + AddControl( m_aHTMLControls, dynamic_cast<const SdrUnoObj&>(*pSdrObj), + pPosFlyFrame->GetNdIndex().GetIndex() ); + } + + // and now the ones in a character-bound frame + for(sw::SpzFrameFormat* pSpz: *m_pDoc->GetSpzFrameFormats()) + { + if( RES_DRAWFRMFMT != pSpz->Which() ) + continue; + + const SwFormatAnchor& rAnchor = pSpz->GetAnchor(); + const SwNode *pAnchorNode = rAnchor.GetAnchorNode(); + if ((RndStdIds::FLY_AS_CHAR != rAnchor.GetAnchorId()) || !pAnchorNode) + continue; + + const SdrObject *pSdrObj = + SwHTMLWriter::GetHTMLControl(*static_cast<SwDrawFrameFormat*>(pSpz) ); + if( !pSdrObj ) + continue; + + AddControl( m_aHTMLControls, dynamic_cast<const SdrUnoObj&>(*pSdrObj), pAnchorNode->GetIndex() ); + } +} + +HTMLControl::HTMLControl( + uno::Reference< container::XIndexContainer > _xFormComps, + SwNodeOffset nIdx ) : + xFormComps(std::move( _xFormComps )), nNdIdx( nIdx ), nCount( 1 ) +{} + +HTMLControl::~HTMLControl() +{} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlftn.cxx b/sw/source/filter/html/htmlftn.cxx new file mode 100644 index 0000000000..db24a32bb7 --- /dev/null +++ b/sw/source/filter/html/htmlftn.cxx @@ -0,0 +1,598 @@ +/* -*- 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 <osl/diagnose.h> +#include <svtools/htmlout.hxx> +#include <svtools/htmlkywd.hxx> +#include <svtools/HtmlWriter.hxx> +#include <rtl/strbuf.hxx> +#include <ndindex.hxx> +#include <fmtftn.hxx> +#include <txtftn.hxx> +#include <ftninfo.hxx> +#include <doc.hxx> +#include <ndtxt.hxx> +#include <charfmt.hxx> + +#include "swhtml.hxx" +#include "wrthtml.hxx" + +static sal_Int32 lcl_html_getNextPart( OUString& rPart, std::u16string_view aContent, + sal_Int32 nPos ) +{ + rPart.clear(); + sal_Int32 nLen = aContent.size(); + if( nPos >= nLen ) + { + nPos = -1; + } + else + { + bool bQuoted = false, bDone = false; + for( ; nPos < nLen && !bDone; nPos++ ) + { + sal_Unicode c = aContent[nPos]; + switch( c ) + { + case '\\': + if( bQuoted ) + rPart += OUStringChar( c ); + bQuoted = !bQuoted; + break; + + case ';': + if( bQuoted ) + rPart += OUStringChar( c ); + else + bDone = true; + bQuoted = false; + break; + + default: + rPart += OUStringChar( c ); + bQuoted = false; + break; + } + } + } + + return nPos; +} + +static sal_Int32 lcl_html_getEndNoteInfo( SwEndNoteInfo& rInfo, + std::u16string_view aContent, + bool bEndNote ) +{ + sal_Int32 nStrPos = 0; + for( int nPart = 0; nPart < 4; ++nPart ) + { + OUString aPart; + if( -1 != nStrPos ) + nStrPos = lcl_html_getNextPart( aPart, aContent, nStrPos ); + + switch( nPart ) + { + case 0: + rInfo.m_aFormat.SetNumberingType( bEndNote ? SVX_NUM_ROMAN_LOWER : SVX_NUM_ARABIC ); + if( !aPart.isEmpty() ) + rInfo.m_aFormat.SetNumberingType(SwHTMLParser::GetNumType( aPart, + rInfo.m_aFormat.GetNumberingType() )); + break; + + case 1: + rInfo.m_nFootnoteOffset = aPart.isEmpty() ? 0 : o3tl::narrowing<sal_uInt16>(aPart.toInt32()); + break; + + case 2: + rInfo.SetPrefix( aPart ); + break; + + case 3: + rInfo.SetSuffix( aPart ); + break; + } + } + + return nStrPos; +} + +void SwHTMLParser::FillEndNoteInfo( std::u16string_view aContent ) +{ + SwEndNoteInfo aInfo( m_xDoc->GetEndNoteInfo() ); + lcl_html_getEndNoteInfo( aInfo, aContent, true ); + m_xDoc->SetEndNoteInfo( aInfo ); +} + +void SwHTMLParser::FillFootNoteInfo( std::u16string_view aContent ) +{ + SwFootnoteInfo aInfo( m_xDoc->GetFootnoteInfo() ); + + sal_Int32 nStrPos = lcl_html_getEndNoteInfo( aInfo, aContent, false ); + + for( int nPart = 4; nPart < 8; ++nPart ) + { + OUString aPart; + if( -1 != nStrPos ) + nStrPos = lcl_html_getNextPart( aPart, aContent, nStrPos ); + + switch( nPart ) + { + case 4: + aInfo.m_eNum = FTNNUM_DOC; + if( !aPart.isEmpty() ) + { + switch( aPart[0] ) + { + case 'D': aInfo.m_eNum = FTNNUM_DOC; break; + case 'C': aInfo.m_eNum = FTNNUM_CHAPTER; break; + case 'P': aInfo.m_eNum = FTNNUM_PAGE; break; + } + } + break; + + case 5: + aInfo.m_ePos = FTNPOS_PAGE; + if( !aPart.isEmpty() ) + { + switch( aPart[0] ) + { + case 'C': aInfo.m_ePos = FTNPOS_CHAPTER; break; + case 'P': aInfo.m_ePos = FTNPOS_PAGE; break; + } + } + break; + + case 6: + aInfo.m_aQuoVadis = aPart; + break; + + case 7: + aInfo.m_aErgoSum = aPart; + break; + } + } + + m_xDoc->SetFootnoteInfo( aInfo ); +} + +void SwHTMLParser::InsertFootEndNote( const OUString& rName, bool bEndNote, + bool bFixed ) +{ + if( !m_pFootEndNoteImpl ) + m_pFootEndNoteImpl.reset(new SwHTMLFootEndNote_Impl); + + m_pFootEndNoteImpl->sName = rName; + if( m_pFootEndNoteImpl->sName.getLength() > 3 ) + m_pFootEndNoteImpl->sName = m_pFootEndNoteImpl->sName.copy( 0, m_pFootEndNoteImpl->sName.getLength() - 3 ); + m_pFootEndNoteImpl->sName = m_pFootEndNoteImpl->sName.toAsciiUpperCase(); + m_pFootEndNoteImpl->bEndNote = bEndNote; + m_pFootEndNoteImpl->bFixed = bFixed; + m_pFootEndNoteImpl->sContent.clear(); +} + +void SwHTMLParser::FinishFootEndNote() +{ + if( !m_pFootEndNoteImpl ) + return; + + SwFormatFootnote aFootnote( m_pFootEndNoteImpl->bEndNote ); + if( m_pFootEndNoteImpl->bFixed ) + aFootnote.SetNumStr( m_pFootEndNoteImpl->sContent ); + + m_xDoc->getIDocumentContentOperations().InsertPoolItem( *m_pPam, aFootnote ); + SwTextFootnote * const pTextFootnote = static_cast<SwTextFootnote *>( + m_pPam->GetPointNode().GetTextNode()->GetTextAttrForCharAt( + m_pPam->GetPoint()->GetContentIndex() - 1, RES_TXTATR_FTN ) ); + // In header and footer no footnotes can be inserted. + if (pTextFootnote) + m_pFootEndNoteImpl->aTextFootnotes.push_back(SwHTMLTextFootnote(m_pFootEndNoteImpl->sName,pTextFootnote)); + m_pFootEndNoteImpl->sName.clear(); + m_pFootEndNoteImpl->sContent.clear(); + m_pFootEndNoteImpl->bFixed = false; +} + +void SwHTMLParser::InsertFootEndNoteText() +{ + if( m_pFootEndNoteImpl && m_pFootEndNoteImpl->bFixed ) + m_pFootEndNoteImpl->sContent += aToken; +} + +SwNodeIndex *SwHTMLParser::GetFootEndNoteSection( const OUString& rName ) +{ + SwNodeIndex *pStartNodeIdx = nullptr; + + if (m_pFootEndNoteImpl) + { + OUString aName(rName.toAsciiUpperCase()); + + size_t nCount = m_pFootEndNoteImpl->aTextFootnotes.size(); + for(size_t i = 0; i < nCount; ++i) + { + if (m_pFootEndNoteImpl->aTextFootnotes[i].GetName() == aName) + { + pStartNodeIdx = const_cast<SwNodeIndex*>(m_pFootEndNoteImpl->aTextFootnotes[i].GetStartNode()); + m_pFootEndNoteImpl->aTextFootnotes.erase( m_pFootEndNoteImpl->aTextFootnotes.begin() + i ); + if (m_pFootEndNoteImpl->aTextFootnotes.empty()) + { + m_pFootEndNoteImpl.reset(); + } + + break; + } + } + } + + return pStartNodeIdx; +} + +SwHTMLWriter& OutHTML_SwFormatLineBreak(SwHTMLWriter& rWrt, const SfxPoolItem& rHt) +{ + const auto& rLineBreak = static_cast<const SwFormatLineBreak&>(rHt); + + HtmlWriter aWriter(rWrt.Strm(), rWrt.maNamespace); + aWriter.start(OOO_STRING_SVTOOLS_HTML_linebreak ""_ostr); + switch (rLineBreak.GetValue()) + { + case SwLineBreakClear::NONE: + aWriter.attribute(OOO_STRING_SVTOOLS_HTML_O_clear, "none"); + break; + case SwLineBreakClear::LEFT: + aWriter.attribute(OOO_STRING_SVTOOLS_HTML_O_clear, "left"); + break; + case SwLineBreakClear::RIGHT: + aWriter.attribute(OOO_STRING_SVTOOLS_HTML_O_clear, "right"); + break; + case SwLineBreakClear::ALL: + aWriter.attribute(OOO_STRING_SVTOOLS_HTML_O_clear, "all"); + break; + } + aWriter.end(); + return rWrt; +} + +SwHTMLWriter& OutHTML_SwFormatFootnote( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ) +{ + SwFormatFootnote& rFormatFootnote = const_cast<SwFormatFootnote&>(static_cast<const SwFormatFootnote&>(rHt)); + SwTextFootnote *pTextFootnote = rFormatFootnote.GetTextFootnote(); + if( !pTextFootnote ) + return rWrt; + + OUString sFootnoteName, sClass; + size_t nPos; + if( rFormatFootnote.IsEndNote() ) + { + nPos = rWrt.m_xFootEndNotes ? rWrt.m_xFootEndNotes->size() : 0; + OSL_ENSURE( nPos == static_cast<size_t>(rWrt.m_nFootNote + rWrt.m_nEndNote), + "OutHTML_SwFormatFootnote: wrong position" ); + sClass = OOO_STRING_SVTOOLS_HTML_sdendnote_anc; + sFootnoteName = OOO_STRING_SVTOOLS_HTML_sdendnote + OUString::number( static_cast<sal_Int32>(++rWrt.m_nEndNote) ); + } + else + { + nPos = rWrt.m_nFootNote; + sClass = OOO_STRING_SVTOOLS_HTML_sdfootnote_anc; + sFootnoteName = OOO_STRING_SVTOOLS_HTML_sdfootnote + OUString::number( static_cast<sal_Int32>(++rWrt.m_nFootNote)); + } + + if( !rWrt.m_xFootEndNotes ) + rWrt.m_xFootEndNotes.emplace(); + rWrt.m_xFootEndNotes->insert( rWrt.m_xFootEndNotes->begin() + nPos, pTextFootnote ); + + OStringBuffer sOut; + OString aTag = rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_anchor; + sOut.append("<" + aTag + " " OOO_STRING_SVTOOLS_HTML_O_class "=\""); + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( rWrt.Strm(), sClass ); + sOut.append("\" " OOO_STRING_SVTOOLS_HTML_O_name "=\""); + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( rWrt.Strm(), sFootnoteName ); + sOut.append(OOO_STRING_SVTOOLS_HTML_FTN_anchor "\" " + OOO_STRING_SVTOOLS_HTML_O_href "=\"#"); + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( rWrt.Strm(), sFootnoteName ); + sOut.append(OOO_STRING_SVTOOLS_HTML_FTN_symbol "\""); + if( !rFormatFootnote.GetNumStr().isEmpty() ) + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_sdfixed); + sOut.append(">"); + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_superscript )); + + HTMLOutFuncs::Out_String( rWrt.Strm(), rFormatFootnote.GetViewNumStr(*rWrt.m_pDoc, nullptr) ); + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_superscript), false ); + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_anchor), false ); + + return rWrt; +} + +void SwHTMLWriter::OutFootEndNotes() +{ + OSL_ENSURE( m_xFootEndNotes, + "SwHTMLWriter::OutFootEndNotes(): unnecessary call" ); + if( !m_xFootEndNotes ) + return; + +#if OSL_DEBUG_LEVEL > 0 + sal_uInt16 nFootnote = m_nFootNote, nEn = m_nEndNote; +#endif + m_nFootNote = 0; + m_nEndNote = 0; + + for( auto *pTextFootnote : *m_xFootEndNotes ) + { + m_pFormatFootnote = &pTextFootnote->GetFootnote(); + + OUString sFootnoteName; + if( m_pFormatFootnote->IsEndNote() ) + { + sFootnoteName = OOO_STRING_SVTOOLS_HTML_sdendnote + OUString::number(static_cast<sal_Int32>(++m_nEndNote)); + } + else + { + sFootnoteName = OOO_STRING_SVTOOLS_HTML_sdfootnote + OUString::number(static_cast<sal_Int32>(++m_nFootNote)); + } + + if (IsLFPossible()) + OutNewLine(); + OString sOut = + "<" + GetNamespace() + OOO_STRING_SVTOOLS_HTML_division + " " OOO_STRING_SVTOOLS_HTML_O_id "=\""; + Strm().WriteOString( sOut ); + HTMLOutFuncs::Out_String( Strm(), sFootnoteName ); + Strm().WriteOString( "\">" ); + + SetLFPossible(true); + IncIndentLevel(); // indent content of <DIV> + + OSL_ENSURE( pTextFootnote, "SwHTMLWriter::OutFootEndNotes: SwTextFootnote is missing" ); + const SwNodeIndex *pSttNdIdx = pTextFootnote->GetStartNode(); + OSL_ENSURE( pSttNdIdx, + "SwHTMLWriter::OutFootEndNotes: StartNode-Index is missing" ); + if( pSttNdIdx ) + { + HTMLSaveData aSaveData( *this, pSttNdIdx->GetIndex()+1, + pSttNdIdx->GetNode().EndOfSectionIndex(), false ); + Out_SwDoc( m_pCurrentPam.get() ); + } + + DecIndentLevel(); // indent content of <DIV> + if (IsLFPossible()) + OutNewLine(); + HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + OOO_STRING_SVTOOLS_HTML_division), false ); + SetLFPossible(true); + + OSL_ENSURE( !m_pFormatFootnote, + "SwHTMLWriter::OutFootEndNotes: Footnote was not output" ); + if( m_pFormatFootnote ) + { + if( m_pFormatFootnote->IsEndNote() ) + m_nEndNote++; + else + m_nFootNote++; + + m_pFormatFootnote = nullptr; + } + } + +#if OSL_DEBUG_LEVEL > 0 + OSL_ENSURE( nFootnote == m_nFootNote, + "SwHTMLWriter::OutFootEndNotes: Number of footnotes does not match" ); + OSL_ENSURE( nEn == m_nEndNote, + "SwHTMLWriter::OutFootEndNotes: Number of endnotes does not match" ); +#endif + + m_xFootEndNotes.reset(); + m_nFootNote = m_nEndNote = 0; +} + +OUString SwHTMLWriter::GetFootEndNoteSym( const SwFormatFootnote& rFormatFootnote ) +{ + const SwEndNoteInfo * pInfo = nullptr; + if( rFormatFootnote.GetNumStr().isEmpty() ) + pInfo = rFormatFootnote.IsEndNote() ? &m_pDoc->GetEndNoteInfo() + : &m_pDoc->GetFootnoteInfo(); + + OUString sRet; + if( pInfo ) + sRet = pInfo->GetPrefix(); + sRet += rFormatFootnote.GetViewNumStr(*m_pDoc, nullptr); + if( pInfo ) + sRet += pInfo->GetSuffix(); + + return sRet; +} + +void SwHTMLWriter::OutFootEndNoteSym( const SwFormatFootnote& rFormatFootnote, + const OUString& rNum, + sal_uInt16 nScript ) +{ + const SwEndNoteInfo *pInfo; + + OUString sFootnoteName, sClass; + if( rFormatFootnote.IsEndNote() ) + { + sClass = OOO_STRING_SVTOOLS_HTML_sdendnote_sym; + sFootnoteName = OOO_STRING_SVTOOLS_HTML_sdendnote + + OUString::number(static_cast<sal_Int32>(m_nEndNote)); + pInfo = &m_pDoc->GetEndNoteInfo(); + } + else + { + sClass = OOO_STRING_SVTOOLS_HTML_sdfootnote_sym; + sFootnoteName = OOO_STRING_SVTOOLS_HTML_sdfootnote + + OUString::number(static_cast<sal_Int32>(m_nFootNote)); + pInfo = &m_pDoc->GetFootnoteInfo(); + } + + const SwCharFormat *pSymCharFormat = pInfo->GetCharFormat( *m_pDoc ); + if( pSymCharFormat && 0 != m_aScriptTextStyles.count( pSymCharFormat->GetName() ) ) + { + switch( nScript ) + { + case CSS1_OUTMODE_WESTERN: + sClass += "-western"; + break; + case CSS1_OUTMODE_CJK: + sClass += "-cjk"; + break; + case CSS1_OUTMODE_CTL: + sClass += "-ctl"; + break; + } + } + + OStringBuffer sOut("<" + + GetNamespace() + OOO_STRING_SVTOOLS_HTML_anchor " " + OOO_STRING_SVTOOLS_HTML_O_class "=\""); + Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( Strm(), sClass ); + sOut.append("\" " OOO_STRING_SVTOOLS_HTML_O_name "=\""); + Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( Strm(), sFootnoteName ); + sOut.append(OOO_STRING_SVTOOLS_HTML_FTN_symbol "\" " + OOO_STRING_SVTOOLS_HTML_O_href "=\"#"); + Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( Strm(), sFootnoteName ); + sOut.append(OOO_STRING_SVTOOLS_HTML_FTN_anchor "\">"); + Strm().WriteOString( sOut ); + sOut.setLength(0); + + HTMLOutFuncs::Out_String( Strm(), rNum ); + HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + OOO_STRING_SVTOOLS_HTML_anchor), false ); +} + +static int lcl_html_fillEndNoteInfo( const SwEndNoteInfo& rInfo, + OUString *pParts, + bool bEndNote ) +{ + int nParts = 0; + sal_Int16 eFormat = rInfo.m_aFormat.GetNumberingType(); + if( (bEndNote ? SVX_NUM_ROMAN_LOWER : SVX_NUM_ARABIC) != eFormat ) + { + const char *pStr = SwHTMLWriter::GetNumFormat( eFormat ); + if( pStr ) + { + pParts[0] = OUString::createFromAscii( pStr ); + nParts = 1; + } + } + if( rInfo.m_nFootnoteOffset > 0 ) + { + pParts[1] = OUString::number(rInfo.m_nFootnoteOffset); + nParts = 2; + } + if( !rInfo.GetPrefix().isEmpty() ) + { + pParts[2] = rInfo.GetPrefix(); + nParts = 3; + } + if( !rInfo.GetSuffix().isEmpty() ) + { + pParts[3] = rInfo.GetSuffix(); + nParts = 4; + } + + return nParts; +} + +static void lcl_html_outFootEndNoteInfo( SwHTMLWriter& rWrt, OUString const *pParts, + int nParts, const char *pName ) +{ + OUStringBuffer aContent; + for( int i=0; i<nParts; ++i ) + { + OUString aTmp( pParts[i] ); + aTmp = aTmp.replaceAll( "\\", "\\\\" ); + aTmp = aTmp.replaceAll( ";", "\\;" ); + if( i > 0 ) + aContent.append(";"); + aContent.append(aTmp); + } + + rWrt.OutNewLine(); + OString sOut = + "<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_meta " " + OOO_STRING_SVTOOLS_HTML_O_name "=\"" + pName + + "\" " OOO_STRING_SVTOOLS_HTML_O_content "=\""; + rWrt.Strm().WriteOString( sOut ); + HTMLOutFuncs::Out_String( rWrt.Strm(), aContent.makeStringAndClear() ); + rWrt.Strm().WriteOString( "\">" ); +} + +void SwHTMLWriter::OutFootEndNoteInfo() +{ + // Number type (1 or i) + // Offset (0) + // Before it + // Behind it + // Doc/Page/Chap (D) + // Position (S) + // Next page + // Beginning + + { + const SwFootnoteInfo& rInfo = m_pDoc->GetFootnoteInfo(); + OUString aParts[8]; + int nParts = lcl_html_fillEndNoteInfo( rInfo, aParts, false ); + if( rInfo.m_eNum != FTNNUM_DOC ) + { + aParts[4] = rInfo.m_eNum == FTNNUM_CHAPTER ? std::u16string_view( u"C" ) : std::u16string_view( u"P" ); + nParts = 5; + } + if( rInfo.m_ePos != FTNPOS_PAGE) + { + aParts[5] = "C"; + nParts = 6; + } + if( !rInfo.m_aQuoVadis.isEmpty() ) + { + aParts[6] = rInfo.m_aQuoVadis; + nParts = 7; + } + if( !rInfo.m_aErgoSum.isEmpty() ) + { + aParts[7] = rInfo.m_aErgoSum; + nParts = 8; + } + if( nParts > 0 ) + lcl_html_outFootEndNoteInfo( *this, aParts, nParts, + OOO_STRING_SVTOOLS_HTML_META_sdfootnote ); + } + + { + const SwEndNoteInfo& rInfo = m_pDoc->GetEndNoteInfo(); + OUString aParts[4]; + const int nParts = lcl_html_fillEndNoteInfo( rInfo, aParts, true ); + if( nParts > 0 ) + lcl_html_outFootEndNoteInfo( *this, aParts, nParts, + OOO_STRING_SVTOOLS_HTML_META_sdendnote ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlgrin.cxx b/sw/source/filter/html/htmlgrin.cxx new file mode 100644 index 0000000000..072b8945d2 --- /dev/null +++ b/sw/source/filter/html/htmlgrin.cxx @@ -0,0 +1,1574 @@ +/* -*- 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 <memory> +#include <hintids.hxx> +#include <comphelper/string.hxx> +#include <comphelper/documentinfo.hxx> +#include <vcl/svapp.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <svl/stritem.hxx> +#include <svl/urihelper.hxx> +#include <svl/languageoptions.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/langitem.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/event.hxx> +#include <vcl/imap.hxx> +#include <svtools/htmltokn.h> +#include <svtools/htmlkywd.hxx> +#include <unotools/eventcfg.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <o3tl/string_view.hxx> + +#include <fmtornt.hxx> +#include <fmturl.hxx> +#include <fmtsrnd.hxx> +#include <fmtinfmt.hxx> +#include <fmtcntnt.hxx> +#include <fmtanchr.hxx> +#include <fmtfsize.hxx> +#include <charatr.hxx> +#include <frmfmt.hxx> +#include <charfmt.hxx> +#include <docsh.hxx> +#include <pam.hxx> +#include <doc.hxx> +#include <ndtxt.hxx> +#include <shellio.hxx> +#include <poolfmt.hxx> +#include <IMark.hxx> +#include <ndgrf.hxx> +#include "htmlnum.hxx" +#include "swcss1.hxx" +#include "swhtml.hxx" +#include <numrule.hxx> +#include <IDocumentMarkAccess.hxx> +#include <frameformats.hxx> + +#include <vcl/graphicfilter.hxx> +#include <tools/UnitConversion.hxx> +#include <tools/urlobj.hxx> +#include <unotools/securityoptions.hxx> + +using namespace ::com::sun::star; + +HTMLOptionEnum<sal_Int16> const aHTMLImgHAlignTable[] = +{ + { OOO_STRING_SVTOOLS_HTML_AL_left, text::HoriOrientation::LEFT }, + { OOO_STRING_SVTOOLS_HTML_AL_right, text::HoriOrientation::RIGHT }, + { nullptr, 0 } +}; + +HTMLOptionEnum<sal_Int16> const aHTMLImgVAlignTable[] = +{ + { OOO_STRING_SVTOOLS_HTML_VA_top, text::VertOrientation::LINE_TOP }, + { OOO_STRING_SVTOOLS_HTML_VA_texttop, text::VertOrientation::CHAR_TOP }, + { OOO_STRING_SVTOOLS_HTML_VA_middle, text::VertOrientation::CENTER }, + { OOO_STRING_SVTOOLS_HTML_AL_center, text::VertOrientation::CENTER }, + { OOO_STRING_SVTOOLS_HTML_VA_absmiddle, text::VertOrientation::LINE_CENTER }, + { OOO_STRING_SVTOOLS_HTML_VA_bottom, text::VertOrientation::TOP }, + { OOO_STRING_SVTOOLS_HTML_VA_baseline, text::VertOrientation::TOP }, + { OOO_STRING_SVTOOLS_HTML_VA_absbottom, text::VertOrientation::LINE_BOTTOM }, + { nullptr, 0 } +}; + +ImageMap *SwHTMLParser::FindImageMap( std::u16string_view rName ) const +{ + OSL_ENSURE( rName[0] != '#', "FindImageMap: name begins with '#'!" ); + + if (m_pImageMaps) + { + for (const auto &rpIMap : *m_pImageMaps) + { + if (o3tl::equalsIgnoreAsciiCase(rName, rpIMap->GetName())) + { + return rpIMap.get(); + } + } + } + return nullptr; +} + +void SwHTMLParser::ConnectImageMaps() +{ + SwNodes& rNds = m_xDoc->GetNodes(); + // on the first node of section #1 + SwNodeOffset nIdx = rNds.GetEndOfAutotext().StartOfSectionIndex() + 1; + SwNodeOffset nEndIdx = rNds.GetEndOfAutotext().GetIndex(); + + SwGrfNode* pGrfNd; + while( m_nMissingImgMaps > 0 && nIdx < nEndIdx ) + { + SwNode *pNd = rNds[nIdx + 1]; + pGrfNd = pNd->GetGrfNode(); + if( nullptr != pGrfNd ) + { + SwFrameFormat *pFormat = pGrfNd->GetFlyFormat(); + SwFormatURL aURL( pFormat->GetURL() ); + const ImageMap *pIMap = aURL.GetMap(); + if( pIMap && pIMap->GetIMapObjectCount()==0 ) + { + // The (empty) image map of the node will be either + // replaced with found image map or deleted. + ImageMap *pNewIMap = + FindImageMap( pIMap->GetName() ); + aURL.SetMap( pNewIMap ); + pFormat->SetFormatAttr( aURL ); + if( !pGrfNd->IsScaleImageMap() ) + { + // meanwhile the graphic size is known or the + // graphic don't need scaling + pGrfNd->ScaleImageMap(); + } + m_nMissingImgMaps--; // search a map less + } + } + nIdx = rNds[nIdx]->EndOfSectionIndex() + 1; + } +} + +void SwHTMLParser::SetAnchorAndAdjustment( sal_Int16 eVertOri, + sal_Int16 eHoriOri, + const SvxCSS1PropertyInfo &rCSS1PropInfo, + SfxItemSet& rFrameItemSet ) +{ + const SfxItemSet *pCntnrItemSet = nullptr; + auto i = m_aContexts.size(); + while( !pCntnrItemSet && i > m_nContextStMin ) + pCntnrItemSet = m_aContexts[--i]->GetFrameItemSet(); + + if( pCntnrItemSet ) + { + // If we are in a container then the anchoring of the container is used. + rFrameItemSet.Put( *pCntnrItemSet ); + } + else if( SwCSS1Parser::MayBePositioned( rCSS1PropInfo, true ) ) + { + // If the alignment can be set via CSS1 options we use them. + SetAnchorAndAdjustment( rCSS1PropInfo, rFrameItemSet ); + } + else + { + // Otherwise the alignment is set correspondingly the normal HTML options. + SetAnchorAndAdjustment( eVertOri, eHoriOri, rFrameItemSet ); + } +} + +void SwHTMLParser::SetAnchorAndAdjustment( sal_Int16 eVertOri, + sal_Int16 eHoriOri, + SfxItemSet& rFrameSet, + bool bDontAppend ) +{ + bool bMoveBackward = false; + SwFormatAnchor aAnchor( RndStdIds::FLY_AS_CHAR ); + sal_Int16 eVertRel = text::RelOrientation::FRAME; + + if( text::HoriOrientation::NONE != eHoriOri ) + { + // determine paragraph indent + sal_uInt16 nLeftSpace = 0, nRightSpace = 0; + short nIndent = 0; + GetMarginsFromContextWithNumberBullet( nLeftSpace, nRightSpace, nIndent ); + + // determine horizontal alignment and wrapping + sal_Int16 eHoriRel; + css::text::WrapTextMode eSurround; + switch( eHoriOri ) + { + case text::HoriOrientation::LEFT: + eHoriRel = nLeftSpace ? text::RelOrientation::PRINT_AREA : text::RelOrientation::FRAME; + eSurround = css::text::WrapTextMode_RIGHT; + break; + case text::HoriOrientation::RIGHT: + eHoriRel = nRightSpace ? text::RelOrientation::PRINT_AREA : text::RelOrientation::FRAME; + eSurround = css::text::WrapTextMode_LEFT; + break; + case text::HoriOrientation::CENTER: // for tables + eHoriRel = text::RelOrientation::FRAME; + eSurround = css::text::WrapTextMode_NONE; + break; + default: + eHoriRel = text::RelOrientation::FRAME; + eSurround = css::text::WrapTextMode_PARALLEL; + break; + } + + // Create a new paragraph, if the current one has frames + // anchored at paragraph/at char without wrapping. + if( !bDontAppend && HasCurrentParaFlys( true ) ) + { + // When the paragraph only contains graphics then there + // is no need for bottom margin. Since here also with use of + // styles no margin should be created, set attributes to + // override! + sal_uInt16 nUpper=0, nLower=0; + GetULSpaceFromContext( nUpper, nLower ); + InsertAttr( SvxULSpaceItem( nUpper, 0, RES_UL_SPACE ), true ); + + AppendTextNode( AM_NOSPACE ); + + if( nUpper ) + { + NewAttr(m_xAttrTab, &m_xAttrTab->pULSpace, SvxULSpaceItem(0, nLower, RES_UL_SPACE)); + m_aParaAttrs.push_back( m_xAttrTab->pULSpace ); + EndAttr( m_xAttrTab->pULSpace, false ); + } + } + + // determine vertical alignment and anchoring + const sal_Int32 nContent = m_pPam->GetPoint()->GetContentIndex(); + if( nContent ) + { + aAnchor.SetType( RndStdIds::FLY_AT_CHAR ); + bMoveBackward = true; + eVertOri = text::VertOrientation::CHAR_BOTTOM; + eVertRel = text::RelOrientation::CHAR; + } + else + { + aAnchor.SetType( RndStdIds::FLY_AT_PARA ); + eVertOri = text::VertOrientation::TOP; + eVertRel = text::RelOrientation::PRINT_AREA; + } + + rFrameSet.Put( SwFormatHoriOrient( 0, eHoriOri, eHoriRel) ); + + rFrameSet.Put( SwFormatSurround( eSurround ) ); + } + rFrameSet.Put( SwFormatVertOrient( 0, eVertOri, eVertRel) ); + + if( bMoveBackward ) + m_pPam->Move( fnMoveBackward ); + + if (aAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR && !m_pPam->GetPointNode().GetTextNode()) + { + eState = SvParserState::Error; + return; + } + + aAnchor.SetAnchor( m_pPam->GetPoint() ); + + if( bMoveBackward ) + m_pPam->Move( fnMoveForward ); + + rFrameSet.Put( aAnchor ); +} + +void SwHTMLParser::RegisterFlyFrame( SwFrameFormat *pFlyFormat ) +{ + // automatically anchored frames must be moved forward by one position + if( RES_DRAWFRMFMT != pFlyFormat->Which() && + (RndStdIds::FLY_AT_PARA == pFlyFormat->GetAnchor().GetAnchorId()) && + css::text::WrapTextMode_THROUGH == pFlyFormat->GetSurround().GetSurround() ) + { + m_aMoveFlyFrames.emplace_back(std::make_unique<SwHTMLFrameFormatListener>(pFlyFormat)); + m_aMoveFlyCnts.push_back( m_pPam->GetPoint()->GetContentIndex() ); + } +} + +/* */ + +void SwHTMLParser::GetDefaultScriptType( ScriptType& rType, + OUString& rTypeStr ) const +{ + SwDocShell *pDocSh = m_xDoc->GetDocShell(); + SvKeyValueIterator* pHeaderAttrs = pDocSh ? pDocSh->GetHeaderAttributes() + : nullptr; + rType = GetScriptType( pHeaderAttrs ); + rTypeStr = GetScriptTypeString( pHeaderAttrs ); +} + +namespace +{ + bool allowAccessLink(const SwDoc& rDoc) + { + OUString sReferer; + SfxObjectShell * sh = rDoc.GetPersist(); + if (sh != nullptr && sh->HasName()) + { + sReferer = sh->GetMedium()->GetName(); + } + return !SvtSecurityOptions::isUntrustedReferer(sReferer); + } +} + +/* */ + +void SwHTMLParser::InsertImage() +{ + // and now analyze + OUString sAltNm, aId, aClass, aStyle, aMap, sHTMLGrfName; + OUString sGrfNm; + OUString aGraphicData; + sal_Int16 eVertOri = text::VertOrientation::TOP; + sal_Int16 eHoriOri = text::HoriOrientation::NONE; + bool bWidthProvided=false, bHeightProvided=false; + tools::Long nWidth=0, nHeight=0; + tools::Long nVSpace=0, nHSpace=0; + + sal_uInt16 nBorder = (m_xAttrTab->pINetFormat ? 1 : 0); + bool bIsMap = false; + bool bPercentWidth = false; + bool bPercentHeight = false; + OUString sWidthAsString, sHeightAsString; + SvxMacroItem aMacroItem(RES_FRMMACRO); + + ScriptType eDfltScriptType; + OUString sDfltScriptType; + GetDefaultScriptType( eDfltScriptType, sDfltScriptType ); + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + SvMacroItemId nEvent = SvMacroItemId::NONE; + ScriptType eScriptType2 = eDfltScriptType; + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::SRC: + sGrfNm = rOption.GetString(); + if( !InternalImgToPrivateURL(sGrfNm) ) + sGrfNm = INetURLObject::GetAbsURL( m_sBaseURL, sGrfNm ); + break; + case HtmlOptionId::DATA: + aGraphicData = rOption.GetString(); + if (!InternalImgToPrivateURL(aGraphicData)) + aGraphicData = INetURLObject::GetAbsURL( + m_sBaseURL, SwHTMLParser::StripQueryFromPath(m_sBaseURL, aGraphicData)); + break; + case HtmlOptionId::ALIGN: + eVertOri = + rOption.GetEnum( aHTMLImgVAlignTable, + text::VertOrientation::TOP ); + eHoriOri = + rOption.GetEnum( aHTMLImgHAlignTable ); + break; + case HtmlOptionId::WIDTH: + // for now only store as pixel value! + nWidth = rOption.GetNumber(); + sWidthAsString = rOption.GetString(); + bPercentWidth = (sWidthAsString.indexOf('%') != -1); + if( bPercentWidth && nWidth>100 ) + nWidth = 100; + // width|height = "auto" means viewing app decides the size + // i.e. proceed as if no particular size was provided + bWidthProvided = (sWidthAsString != "auto"); + break; + case HtmlOptionId::HEIGHT: + // for now only store as pixel value! + nHeight = rOption.GetNumber(); + sHeightAsString = rOption.GetString(); + bPercentHeight = (sHeightAsString.indexOf('%') != -1); + if( bPercentHeight && nHeight>100 ) + nHeight = 100; + // the same as above w/ HtmlOptionId::WIDTH + bHeightProvided = (sHeightAsString != "auto"); + break; + case HtmlOptionId::VSPACE: + nVSpace = rOption.GetNumber(); + break; + case HtmlOptionId::HSPACE: + nHSpace = rOption.GetNumber(); + break; + case HtmlOptionId::ALT: + sAltNm = rOption.GetString(); + break; + case HtmlOptionId::BORDER: + nBorder = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + break; + case HtmlOptionId::ISMAP: + bIsMap = true; + break; + case HtmlOptionId::USEMAP: + aMap = rOption.GetString(); + break; + case HtmlOptionId::NAME: + sHTMLGrfName = rOption.GetString(); + break; + + case HtmlOptionId::SDONLOAD: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONLOAD: + nEvent = SvMacroItemId::OnImageLoadDone; + goto IMAGE_SETEVENT; + + case HtmlOptionId::SDONABORT: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONABORT: + nEvent = SvMacroItemId::OnImageLoadCancel; + goto IMAGE_SETEVENT; + + case HtmlOptionId::SDONERROR: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONERROR: + nEvent = SvMacroItemId::OnImageLoadError; + goto IMAGE_SETEVENT; +IMAGE_SETEVENT: + { + OUString sTmp( rOption.GetString() ); + if( !sTmp.isEmpty() ) + { + sTmp = convertLineEnd(sTmp, GetSystemLineEnd()); + OUString sScriptType; + if( EXTENDED_STYPE == eScriptType2 ) + sScriptType = sDfltScriptType; + aMacroItem.SetMacro( nEvent, + SvxMacro( sTmp, sScriptType, eScriptType2 )); + } + } + break; + default: break; + } + } + + if (sGrfNm.isEmpty() && !aGraphicData.isEmpty()) + sGrfNm = aGraphicData; + + if( sGrfNm.isEmpty() ) + return; + + // When we are in an ordered list and the paragraph is still empty and not + // numbered, it may be a graphic for a bullet list. + if( !m_pPam->GetPoint()->GetContentIndex() && + GetNumInfo().GetDepth() > 0 && GetNumInfo().GetDepth() <= MAXLEVEL && + !m_aBulletGrfs[GetNumInfo().GetDepth()-1].isEmpty() && + m_aBulletGrfs[GetNumInfo().GetDepth()-1]==sGrfNm ) + { + SwTextNode* pTextNode = m_pPam->GetPointNode().GetTextNode(); + + if( pTextNode && ! pTextNode->IsCountedInList()) + { + OSL_ENSURE( pTextNode->GetActualListLevel() == GetNumInfo().GetLevel(), + "Numbering level is wrong" ); + + pTextNode->SetCountedInList( true ); + + // It's necessary to invalidate the rule, because between the reading + // of LI and the graphic an EndAction could be called. + if( GetNumInfo().GetNumRule() ) + GetNumInfo().GetNumRule()->SetInvalidRule( true ); + + // Set the style again, so that indent of the first line is correct. + SetTextCollAttrs(); + + return; + } + } + + Graphic aGraphic; + INetURLObject aGraphicURL( sGrfNm ); + if( aGraphicURL.GetProtocol() == INetProtocol::Data ) + { + std::unique_ptr<SvMemoryStream> const pStream(aGraphicURL.getData()); + if (pStream) + { + GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter(); + aGraphic = rFilter.ImportUnloadedGraphic(*pStream); + sGrfNm.clear(); + + if (!sGrfNm.isEmpty()) + { + if (ERRCODE_NONE == rFilter.ImportGraphic(aGraphic, u"", *pStream)) + sGrfNm.clear(); + } + } + } + else if (m_sBaseURL.isEmpty() || !aGraphicData.isEmpty()) + { + // sBaseURL is empty if the source is clipboard + // aGraphicData is non-empty for <object data="..."> -> not a linked graphic. + if (ERRCODE_NONE == GraphicFilter::GetGraphicFilter().ImportGraphic(aGraphic, aGraphicURL)) + sGrfNm.clear(); + } + + if (!sGrfNm.isEmpty()) + { + aGraphic.SetDefaultType(); + } + + if (!nHeight || !nWidth) + { + Size aPixelSize = aGraphic.GetSizePixel(Application::GetDefaultDevice()); + if (!bWidthProvided) + nWidth = aPixelSize.Width(); + if (!bHeightProvided) + nHeight = aPixelSize.Height(); + // tdf#142781 - calculate the width/height keeping the aspect ratio + if (bWidthProvided && !bHeightProvided && aPixelSize.Width()) + { + if (bPercentWidth) + { + nHeight = SwFormatFrameSize::SYNCED; + bPercentHeight = true; + } + else + { + nHeight = nWidth * aPixelSize.Height() / aPixelSize.Width(); + } + } + else if (!bWidthProvided && bHeightProvided && aPixelSize.Height()) + { + if (bPercentHeight) + { + nWidth = SwFormatFrameSize::SYNCED; + bPercentWidth = true; + } + else + { + nWidth = nHeight * aPixelSize.Width() / aPixelSize.Height(); + } + } + } + + SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + if( HasStyleOptions( aStyle, aId, aClass ) ) + ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo ); + + SfxItemSetFixed<RES_FRMATR_BEGIN, RES_FRMATR_END-1> aFrameSet( m_xDoc->GetAttrPool() ); + if( !IsNewDoc() ) + Reader::ResetFrameFormatAttrs( aFrameSet ); + + // set the border + tools::Long nHBorderWidth = 0, nVBorderWidth = 0; + if( nBorder ) + { + nHBorderWidth = static_cast<tools::Long>(nBorder); + nVBorderWidth = static_cast<tools::Long>(nBorder); + SvxCSS1Parser::PixelToTwip( nVBorderWidth, nHBorderWidth ); + + ::editeng::SvxBorderLine aHBorderLine( nullptr, nHBorderWidth ); + ::editeng::SvxBorderLine aVBorderLine( nullptr, nVBorderWidth ); + + if( m_xAttrTab->pINetFormat ) + { + const OUString& rURL = + static_cast<const SwFormatINetFormat&>(m_xAttrTab->pINetFormat->GetItem()).GetValue(); + + m_pCSS1Parser->SetATagStyles(); + sal_uInt16 nPoolId = static_cast< sal_uInt16 >(m_xDoc->IsVisitedURL( rURL ) + ? RES_POOLCHR_INET_VISIT + : RES_POOLCHR_INET_NORMAL); + const SwCharFormat *pCharFormat = m_pCSS1Parser->GetCharFormatFromPool( nPoolId ); + aHBorderLine.SetColor( pCharFormat->GetColor().GetValue() ); + aVBorderLine.SetColor( aHBorderLine.GetColor() ); + } + else + { + const SvxColorItem& rColorItem = m_xAttrTab->pFontColor ? + static_cast<const SvxColorItem &>(m_xAttrTab->pFontColor->GetItem()) : + m_xDoc->GetDefault(RES_CHRATR_COLOR); + aHBorderLine.SetColor( rColorItem.GetValue() ); + aVBorderLine.SetColor( aHBorderLine.GetColor() ); + } + + SvxBoxItem aBoxItem( RES_BOX ); + aBoxItem.SetLine( &aHBorderLine, SvxBoxItemLine::TOP ); + aBoxItem.SetLine( &aHBorderLine, SvxBoxItemLine::BOTTOM ); + aBoxItem.SetLine( &aVBorderLine, SvxBoxItemLine::LEFT ); + aBoxItem.SetLine( &aVBorderLine, SvxBoxItemLine::RIGHT ); + aFrameSet.Put( aBoxItem ); + } + + SetAnchorAndAdjustment( eVertOri, eHoriOri, aPropInfo, aFrameSet ); + + SetSpace( Size( nHSpace, nVSpace), aItemSet, aPropInfo, aFrameSet ); + + // set other CSS1 attributes + SetFrameFormatAttrs( aItemSet, HtmlFrameFormatFlags::Box, aFrameSet ); + + Size aTwipSz( bPercentWidth ? 0 : nWidth, bPercentHeight ? 0 : nHeight ); + if( aTwipSz.Width() || aTwipSz.Height() ) + { + if (bWidthProvided || bHeightProvided || // attributes imply pixel! + aGraphic.GetPrefMapMode().GetMapUnit() == MapUnit::MapPixel) + { + aTwipSz = o3tl::convert(aTwipSz, o3tl::Length::px, o3tl::Length::twip); + } + else + { // some bitmaps may have a size in metric units (e.g. PNG); use that + assert(aGraphic.GetPrefMapMode().GetMapUnit() < MapUnit::MapPixel); + aTwipSz = o3tl::convert(aGraphic.GetPrefSize(), + MapToO3tlLength(aGraphic.GetPrefMapMode().GetMapUnit()), + o3tl::Length::twip); + } + } + + // convert CSS1 size to "normal" size + switch( aPropInfo.m_eWidthType ) + { + case SVX_CSS1_LTYPE_TWIP: + aTwipSz.setWidth( aPropInfo.m_nWidth ); + nWidth = 1; // != 0 + bPercentWidth = false; + break; + case SVX_CSS1_LTYPE_PERCENTAGE: + aTwipSz.setWidth( 0 ); + nWidth = aPropInfo.m_nWidth; + bPercentWidth = true; + break; + default: + ; + } + switch( aPropInfo.m_eHeightType ) + { + case SVX_CSS1_LTYPE_TWIP: + aTwipSz.setHeight( aPropInfo.m_nHeight ); + nHeight = 1; // != 0 + bPercentHeight = false; + break; + case SVX_CSS1_LTYPE_PERCENTAGE: + aTwipSz.setHeight( 0 ); + nHeight = aPropInfo.m_nHeight; + bPercentHeight = true; + break; + default: + ; + } + + Size aGrfSz( 0, 0 ); + bool bSetTwipSize = true; // Set Twip-Size on Node? + bool bChangeFrameSize = false; // Change frame format later? + bool bRequestGrfNow = false; + bool bSetScaleImageMap = false; + sal_uInt8 nPercentWidth = 0, nPercentHeight = 0; + + // bPercentWidth / bPercentHeight means we have a percent size. If that's not the case and we have no + // size from nWidth / nHeight either, then inspect the image header. + bool bRelWidthScale = bPercentWidth && nWidth == SwFormatFrameSize::SYNCED; + bool bNeedWidth = (!bPercentWidth && !nWidth) || bRelWidthScale; + bool bRelHeightScale = bPercentHeight && nHeight == SwFormatFrameSize::SYNCED; + bool bNeedHeight = (!bPercentHeight && !nHeight) || bRelHeightScale; + if ((bNeedWidth || bNeedHeight) && !bFuzzing && allowAccessLink(*m_xDoc)) + { + GraphicDescriptor aDescriptor(aGraphicURL); + if (aDescriptor.Detect(/*bExtendedInfo=*/true)) + { + // Try to use size info from the image header before defaulting to + // HTML_DFLT_IMG_WIDTH/HEIGHT. + aTwipSz + = o3tl::convert(aDescriptor.GetSizePixel(), o3tl::Length::px, o3tl::Length::twip); + if (!bPercentWidth && !nWidth) + { + nWidth = aTwipSz.getWidth(); + } + if (!bPercentHeight && !nHeight) + { + nHeight = aTwipSz.getHeight(); + } + } + } + + if( !(nWidth && !bRelWidthScale) || !(nHeight && !bRelHeightScale) ) + { + // When the graphic is in a table, it will be requested immediately, + // so that it is available before the table is layouted. + if (m_xTable && !nWidth) + { + bRequestGrfNow = true; + IncGrfsThatResizeTable(); + } + + // The frame size is set later + bChangeFrameSize = true; + aGrfSz = aTwipSz; + if( !nWidth && !nHeight ) + { + aTwipSz.setWidth( HTML_DFLT_IMG_WIDTH ); + aTwipSz.setHeight( HTML_DFLT_IMG_HEIGHT ); + } + else if( nWidth ) + { + // a percentage value + if( bPercentWidth ) + { + nPercentWidth = static_cast<sal_uInt8>(nWidth); + nPercentHeight = 255; + } + else + { + aTwipSz.setHeight( HTML_DFLT_IMG_HEIGHT ); + } + } + else if( nHeight ) + { + if( bPercentHeight ) + { + nPercentHeight = static_cast<sal_uInt8>(nHeight); + nPercentWidth = 255; + } + else + { + aTwipSz.setWidth( HTML_DFLT_IMG_WIDTH ); + } + } + } + else + { + // Width and height were given and don't need to be set + bSetTwipSize = false; + + if( bPercentWidth ) + nPercentWidth = static_cast<sal_uInt8>(nWidth); + + if( bPercentHeight ) + nPercentHeight = static_cast<sal_uInt8>(nHeight); + } + + // set image map + aMap = comphelper::string::stripEnd(aMap, ' '); + if( !aMap.isEmpty() ) + { + // Since we only know local image maps we just use everything + // after # as name + sal_Int32 nPos = aMap.indexOf( '#' ); + OUString aName; + if ( -1 == nPos ) + aName = aMap ; + else + aName = aMap.copy(nPos+1); + + ImageMap *pImgMap = FindImageMap( aName ); + if( pImgMap ) + { + SwFormatURL aURL; aURL.SetMap( pImgMap );// is copied + + bSetScaleImageMap = !nPercentWidth || !nPercentHeight; + aFrameSet.Put( aURL ); + } + else + { + ImageMap aEmptyImgMap( aName ); + SwFormatURL aURL; aURL.SetMap( &aEmptyImgMap );// is copied + aFrameSet.Put( aURL ); + m_nMissingImgMaps++; // image maps are missing + + // the graphic has to scaled during SetTwipSize, if we didn't + // set a size on the node or the size doesn't match the graphic size. + bSetScaleImageMap = true; + } + } + + // observe minimum values !! + bool bRelSizeScale = bRelWidthScale || bRelHeightScale; + if( nPercentWidth ) + { + OSL_ENSURE( !aTwipSz.Width() || bRelSizeScale, + "Why is a width set if we already have percentage value?" ); + aTwipSz.setWidth( aGrfSz.Width() ? aGrfSz.Width() + : HTML_DFLT_IMG_WIDTH ); + } + else + { + aTwipSz.AdjustWidth(2*nVBorderWidth ); + if( aTwipSz.Width() < MINFLY ) + aTwipSz.setWidth( MINFLY ); + } + if( nPercentHeight ) + { + OSL_ENSURE( !aTwipSz.Height() || bRelSizeScale, + "Why is a height set if we already have percentage value?" ); + aTwipSz.setHeight( aGrfSz.Height() ? aGrfSz.Height() + : HTML_DFLT_IMG_HEIGHT ); + } + else + { + aTwipSz.AdjustHeight(2*nHBorderWidth ); + if( aTwipSz.Height() < MINFLY ) + aTwipSz.setHeight( MINFLY ); + } + + SwFormatFrameSize aFrameSize( SwFrameSize::Fixed, aTwipSz.Width(), aTwipSz.Height() ); + aFrameSize.SetWidthPercent( nPercentWidth ); + aFrameSize.SetHeightPercent( nPercentHeight ); + aFrameSet.Put( aFrameSize ); + + const SwNodeType eNodeType = m_pPam->GetPointNode().GetNodeType(); + if (eNodeType != SwNodeType::Text && eNodeType != SwNodeType::Table) + return; + + // passing empty sGrfNm here, means we don't want the graphic to be linked + SwFrameFormat *const pFlyFormat = + m_xDoc->getIDocumentContentOperations().InsertGraphic( + *m_pPam, sGrfNm, OUString(), &aGraphic, + &aFrameSet, nullptr, nullptr); + SwGrfNode *pGrfNd = m_xDoc->GetNodes()[ pFlyFormat->GetContent().GetContentIdx() + ->GetIndex()+1 ]->GetGrfNode(); + + if( !sHTMLGrfName.isEmpty() ) + { + pFlyFormat->SetFormatName( sHTMLGrfName ); + + // maybe jump to graphic + if( JumpToMarks::Graphic == m_eJumpTo && sHTMLGrfName == m_sJmpMark ) + { + m_bChkJumpMark = true; + m_eJumpTo = JumpToMarks::NONE; + } + } + + if (pGrfNd) + { + if( !sAltNm.isEmpty() ) + pGrfNd->SetTitle( sAltNm ); + + if( bSetTwipSize ) + pGrfNd->SetTwipSize( aGrfSz ); + + pGrfNd->SetChgTwipSize( bChangeFrameSize ); + + if( bSetScaleImageMap ) + pGrfNd->SetScaleImageMap( true ); + } + + if( m_xAttrTab->pINetFormat ) + { + const SwFormatINetFormat &rINetFormat = + static_cast<const SwFormatINetFormat&>(m_xAttrTab->pINetFormat->GetItem()); + + SwFormatURL aURL( pFlyFormat->GetURL() ); + + aURL.SetURL( rINetFormat.GetValue(), bIsMap ); + aURL.SetTargetFrameName( rINetFormat.GetTargetFrame() ); + aURL.SetName( rINetFormat.GetName() ); + pFlyFormat->SetFormatAttr( aURL ); + + { + static const SvMacroItemId aEvents[] = { + SvMacroItemId::OnMouseOver, + SvMacroItemId::OnClick, + SvMacroItemId::OnMouseOut }; + + for( SvMacroItemId id : aEvents ) + { + const SvxMacro *pMacro = rINetFormat.GetMacro( id ); + if( nullptr != pMacro ) + aMacroItem.SetMacro( id, *pMacro ); + } + } + + if ((RndStdIds::FLY_AS_CHAR == pFlyFormat->GetAnchor().GetAnchorId()) && + m_xAttrTab->pINetFormat->GetStartParagraph() == + m_pPam->GetPoint()->GetNode() && + m_xAttrTab->pINetFormat->GetStartContent() == + m_pPam->GetPoint()->GetContentIndex() - 1 ) + { + // the attribute was insert right before as-character anchored + // graphic, therefore we move it + m_xAttrTab->pINetFormat->SetStart( *m_pPam->GetPoint() ); + + // When the attribute is also an anchor, we'll insert + // a bookmark before the graphic, because SwFormatURL + // isn't an anchor. + if( !rINetFormat.GetName().isEmpty() ) + { + m_pPam->Move( fnMoveBackward ); + InsertBookmark( rINetFormat.GetName() ); + m_pPam->Move( fnMoveForward ); + } + } + + } + else if (!m_aEmbedURL.isEmpty()) + { + // This is an inner <object> image and the outer <object> has a URL for us. Set that on the + // image. + SwFormatURL aURL(pFlyFormat->GetURL()); + aURL.SetURL(m_aEmbedURL, bIsMap); + m_aEmbedURL.clear(); + pFlyFormat->SetFormatAttr(aURL); + } + + if( !aMacroItem.GetMacroTable().empty() ) + { + NotifyMacroEventRead(); + pFlyFormat->SetFormatAttr( aMacroItem ); + } + + // tdf#87083 If the graphic has not been loaded yet, then load it now. + // Otherwise it may be loaded during the first paint of the object and it + // will be too late to adapt the size of the graphic at that point. + if (bRequestGrfNow && pGrfNd) + { + Size aUpdatedSize = pGrfNd->GetTwipSize(); //trigger a swap-in + SAL_WARN_IF(!aUpdatedSize.Width() || !aUpdatedSize.Height(), "sw.html", "html image with no width or height"); + } + + // maybe create frames and register auto bound frames + RegisterFlyFrame( pFlyFormat ); + + if( !aId.isEmpty() ) + InsertBookmark( aId ); +} + +/* */ + +void SwHTMLParser::InsertBodyOptions() +{ + m_xDoc->SetTextFormatColl( *m_pPam, + m_pCSS1Parser->GetTextCollFromPool( RES_POOLCOLL_TEXT ) ); + + OUString aBackGround, aId, aStyle, aLang, aDir; + Color aBGColor, aTextColor, aLinkColor, aVLinkColor; + bool bBGColor=false, bTextColor=false; + bool bLinkColor=false, bVLinkColor=false; + + ScriptType eDfltScriptType; + OUString sDfltScriptType; + GetDefaultScriptType( eDfltScriptType, sDfltScriptType ); + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + ScriptType eScriptType2 = eDfltScriptType; + OUString aEvent; + bool bSetEvent = false; + + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::BACKGROUND: + aBackGround = rOption.GetString(); + break; + case HtmlOptionId::BGCOLOR: + rOption.GetColor( aBGColor ); + bBGColor = true; + break; + case HtmlOptionId::TEXT: + rOption.GetColor( aTextColor ); + bTextColor = true; + break; + case HtmlOptionId::LINK: + rOption.GetColor( aLinkColor ); + bLinkColor = true; + break; + case HtmlOptionId::VLINK: + rOption.GetColor( aVLinkColor ); + bVLinkColor = true; + break; + + case HtmlOptionId::SDONLOAD: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONLOAD: + aEvent = GlobalEventConfig::GetEventName( GlobalEventId::OPENDOC ); + bSetEvent = true; + break; + + case HtmlOptionId::SDONUNLOAD: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONUNLOAD: + aEvent = GlobalEventConfig::GetEventName( GlobalEventId::PREPARECLOSEDOC ); + bSetEvent = true; + break; + + case HtmlOptionId::SDONFOCUS: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONFOCUS: + aEvent = GlobalEventConfig::GetEventName( GlobalEventId::ACTIVATEDOC ); + bSetEvent = true; + break; + + case HtmlOptionId::SDONBLUR: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONBLUR: + aEvent = GlobalEventConfig::GetEventName( GlobalEventId::DEACTIVATEDOC ); + bSetEvent = true; + break; + + case HtmlOptionId::ONERROR: + break; + + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + bTextColor = true; + break; + case HtmlOptionId::LANG: + aLang = rOption.GetString(); + break; + case HtmlOptionId::DIR: + aDir = rOption.GetString(); + break; + default: break; + } + + if( bSetEvent ) + { + const OUString& rEvent = rOption.GetString(); + if( !rEvent.isEmpty() ) + InsertBasicDocEvent( aEvent, rEvent, eScriptType2, + sDfltScriptType ); + } + } + + if( bTextColor && !m_pCSS1Parser->IsBodyTextSet() ) + { + // The font colour is set in the default style + m_pCSS1Parser->GetTextCollFromPool( RES_POOLCOLL_STANDARD ) + ->SetFormatAttr( SvxColorItem(aTextColor, RES_CHRATR_COLOR) ); + m_pCSS1Parser->SetBodyTextSet(); + } + + // Prepare the items for the page style (background, frame) + // If BrushItem already set values must remain! + std::unique_ptr<SvxBrushItem> aBrushItem( m_pCSS1Parser->makePageDescBackground() ); + bool bSetBrush = false; + + if( bBGColor && !m_pCSS1Parser->IsBodyBGColorSet() ) + { + // background colour from "BGCOLOR" + OUString aLink; + if( !aBrushItem->GetGraphicLink().isEmpty() ) + aLink = aBrushItem->GetGraphicLink(); + SvxGraphicPosition ePos = aBrushItem->GetGraphicPos(); + + aBrushItem->SetColor( aBGColor ); + + if( !aLink.isEmpty() ) + { + aBrushItem->SetGraphicLink( aLink ); + aBrushItem->SetGraphicPos( ePos ); + } + bSetBrush = true; + m_pCSS1Parser->SetBodyBGColorSet(); + } + + if( !aBackGround.isEmpty() && !m_pCSS1Parser->IsBodyBackgroundSet() ) + { + // background graphic from "BACKGROUND" + aBrushItem->SetGraphicLink( INetURLObject::GetAbsURL( m_sBaseURL, aBackGround ) ); + aBrushItem->SetGraphicPos( GPOS_TILED ); + bSetBrush = true; + m_pCSS1Parser->SetBodyBackgroundSet(); + } + + if( !aStyle.isEmpty() || !aDir.isEmpty() ) + { + SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + OUString aDummy; + ParseStyleOptions( aStyle, aDummy, aDummy, aItemSet, aPropInfo, nullptr, &aDir ); + + // Some attributes have to set on the page style, in fact the ones + // which aren't inherited + m_pCSS1Parser->SetPageDescAttrs( bSetBrush ? aBrushItem.get() : nullptr, + &aItemSet ); + + static const TypedWhichId<SvxFontHeightItem> aWhichIds[3] = { RES_CHRATR_FONTSIZE, + RES_CHRATR_CJK_FONTSIZE, + RES_CHRATR_CTL_FONTSIZE }; + for(auto const & i : aWhichIds) + { + const SvxFontHeightItem *pItem = aItemSet.GetItemIfSet( i, false ); + if( pItem && pItem->GetProp() != 100) + { + sal_uInt32 nHeight = + ( m_aFontHeights[2] * pItem->GetProp() ) / 100; + SvxFontHeightItem aNewItem( nHeight, 100, i ); + aItemSet.Put( aNewItem ); + } + } + + // all remaining options can be set on the default style + m_pCSS1Parser->GetTextCollFromPool( RES_POOLCOLL_STANDARD ) + ->SetFormatAttr( aItemSet ); + } + else if( bSetBrush ) + { + m_pCSS1Parser->SetPageDescAttrs( aBrushItem.get() ); + } + + if( bLinkColor && !m_pCSS1Parser->IsBodyLinkSet() ) + { + SwCharFormat *pCharFormat = + m_pCSS1Parser->GetCharFormatFromPool(RES_POOLCHR_INET_NORMAL); + pCharFormat->SetFormatAttr( SvxColorItem(aLinkColor, RES_CHRATR_COLOR) ); + m_pCSS1Parser->SetBodyLinkSet(); + } + if( bVLinkColor && !m_pCSS1Parser->IsBodyVLinkSet() ) + { + SwCharFormat *pCharFormat = + m_pCSS1Parser->GetCharFormatFromPool(RES_POOLCHR_INET_VISIT); + pCharFormat->SetFormatAttr( SvxColorItem(aVLinkColor, RES_CHRATR_COLOR) ); + m_pCSS1Parser->SetBodyVLinkSet(); + } + if( !aLang.isEmpty() ) + { + LanguageType eLang = LanguageTag::convertToLanguageTypeWithFallback( aLang ); + if( LANGUAGE_DONTKNOW != eLang ) + { + TypedWhichId<SvxLanguageItem> nWhich(0); + switch( SvtLanguageOptions::GetScriptTypeOfLanguage( eLang ) ) + { + case SvtScriptType::LATIN: + nWhich = RES_CHRATR_LANGUAGE; + break; + case SvtScriptType::ASIAN: + nWhich = RES_CHRATR_CJK_LANGUAGE; + break; + case SvtScriptType::COMPLEX: + nWhich = RES_CHRATR_CTL_LANGUAGE; + break; + default: break; + } + if( nWhich ) + { + SvxLanguageItem aLanguage( eLang, nWhich ); + aLanguage.SetWhich( nWhich ); + m_xDoc->SetDefault( aLanguage ); + } + } + } + + if( !aId.isEmpty() ) + InsertBookmark( aId ); +} + +/* */ + +void SwHTMLParser::NewAnchor() +{ + // end previous link if there was one + std::unique_ptr<HTMLAttrContext> xOldCntxt(PopContext(HtmlTokenId::ANCHOR_ON)); + if (xOldCntxt) + { + // and maybe end attributes + EndContext(xOldCntxt.get()); + } + + SvxMacroTableDtor aMacroTable; + OUString sHRef, aName, sTarget; + OUString aId, aStyle, aClass, aLang, aDir; + bool bHasHRef = false, bFixed = false; + + ScriptType eDfltScriptType; + OUString sDfltScriptType; + GetDefaultScriptType( eDfltScriptType, sDfltScriptType ); + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + SvMacroItemId nEvent = SvMacroItemId::NONE; + ScriptType eScriptType2 = eDfltScriptType; + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::NAME: + aName = rOption.GetString(); + break; + + case HtmlOptionId::HREF: + sHRef = rOption.GetString(); + bHasHRef = true; + break; + case HtmlOptionId::TARGET: + sTarget = rOption.GetString(); + break; + + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::SDFIXED: + bFixed = true; + break; + case HtmlOptionId::LANG: + aLang = rOption.GetString(); + break; + case HtmlOptionId::DIR: + aDir = rOption.GetString(); + break; + + case HtmlOptionId::SDONCLICK: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONCLICK: + nEvent = SvMacroItemId::OnClick; + goto ANCHOR_SETEVENT; + + case HtmlOptionId::SDONMOUSEOVER: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONMOUSEOVER: + nEvent = SvMacroItemId::OnMouseOver; + goto ANCHOR_SETEVENT; + + case HtmlOptionId::SDONMOUSEOUT: + eScriptType2 = STARBASIC; + [[fallthrough]]; + case HtmlOptionId::ONMOUSEOUT: + nEvent = SvMacroItemId::OnMouseOut; + goto ANCHOR_SETEVENT; +ANCHOR_SETEVENT: + { + OUString sTmp( rOption.GetString() ); + if( !sTmp.isEmpty() ) + { + sTmp = convertLineEnd(sTmp, GetSystemLineEnd()); + OUString sScriptType; + if( EXTENDED_STYPE == eScriptType2 ) + sScriptType = sDfltScriptType; + aMacroTable.Insert( nEvent, SvxMacro( sTmp, sScriptType, eScriptType2 )); + } + } + break; + default: break; + } + } + + // Jump targets, which match our implicit targets, + // here we throw out rigorously. + if( !aName.isEmpty() ) + { + OUString sDecoded( INetURLObject::decode( aName, + INetURLObject::DecodeMechanism::Unambiguous )); + sal_Int32 nPos = sDecoded.lastIndexOf( cMarkSeparator ); + if( nPos != -1 ) + { + OUString sCmp= sDecoded.copy(nPos+1).replaceAll(" ",""); + if( !sCmp.isEmpty() ) + { + sCmp = sCmp.toAsciiLowerCase(); + if( sCmp == "region" || + sCmp == "frame" || + sCmp == "graphic" || + sCmp == "ole" || + sCmp == "table" || + sCmp == "outline" || + sCmp == "text" ) + { + aName.clear(); + } + } + } + } + + // create a new context + std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(HtmlTokenId::ANCHOR_ON)); + + bool bEnAnchor = false, bFootnoteAnchor = false, bFootnoteEnSymbol = false; + OUString aFootnoteName; + OUString aStrippedClass( aClass ); + SwCSS1Parser::GetScriptFromClass( aStrippedClass, false ); + if( aStrippedClass.getLength() >=9 && bHasHRef && sHRef.getLength() > 1 && + ('s' == aStrippedClass[0] || 'S' == aStrippedClass[0]) && + ('d' == aStrippedClass[1] || 'D' == aStrippedClass[1]) ) + { + if( aStrippedClass.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_sdendnote_anc ) ) + bEnAnchor = true; + else if( aStrippedClass.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_sdfootnote_anc ) ) + bFootnoteAnchor = true; + else if( aStrippedClass.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_sdendnote_sym ) || + aStrippedClass.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_sdfootnote_sym ) ) + bFootnoteEnSymbol = true; + if( bEnAnchor || bFootnoteAnchor || bFootnoteEnSymbol ) + { + aFootnoteName = sHRef.copy( 1 ); + aClass.clear(); + aStrippedClass.clear(); + aName.clear(); + bHasHRef = false; + } + } + + // Styles parsen + if( HasStyleOptions( aStyle, aId, aStrippedClass, &aLang, &aDir ) ) + { + SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + + if( ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo, &aLang, &aDir ) ) + { + DoPositioning(aItemSet, aPropInfo, xCntxt.get()); + InsertAttrs(aItemSet, aPropInfo, xCntxt.get(), true); + } + } + + if( bHasHRef ) + { + if( !sHRef.isEmpty() ) + { + sHRef = URIHelper::SmartRel2Abs( INetURLObject(m_sBaseURL), sHRef, Link<OUString *, bool>(), false ); + } + else + { + // use directory if empty URL + INetURLObject aURLObj( m_aPathToFile ); + sHRef = aURLObj.GetPartBeforeLastName(); + } + + m_pCSS1Parser->SetATagStyles(); + SwFormatINetFormat aINetFormat( sHRef, sTarget ); + aINetFormat.SetName( aName ); + + if( !aMacroTable.empty() ) + { + NotifyMacroEventRead(); + aINetFormat.SetMacroTable( &aMacroTable ); + } + + // set the default attribute + InsertAttr(&m_xAttrTab->pINetFormat, aINetFormat, xCntxt.get()); + } + else if( !aName.isEmpty() ) + { + InsertBookmark( aName ); + } + + if( bEnAnchor || bFootnoteAnchor ) + { + InsertFootEndNote( aFootnoteName, bEnAnchor, bFixed ); + m_bInFootEndNoteAnchor = m_bCallNextToken = true; + } + else if( bFootnoteEnSymbol ) + { + m_bInFootEndNoteSymbol = m_bCallNextToken = true; + } + + // save context + PushContext(xCntxt); +} + +void SwHTMLParser::EndAnchor() +{ + if( m_bInFootEndNoteAnchor ) + { + FinishFootEndNote(); + m_bInFootEndNoteAnchor = false; + } + else if( m_bInFootEndNoteSymbol ) + { + m_bInFootEndNoteSymbol = false; + } + + EndTag( HtmlTokenId::ANCHOR_OFF ); +} + +/* */ + +void SwHTMLParser::InsertBookmark( const OUString& rName ) +{ + HTMLAttr* pTmp = new HTMLAttr( *m_pPam->GetPoint(), + SfxStringItem(RES_FLTR_BOOKMARK, rName), nullptr, std::shared_ptr<HTMLAttrTable>()); + m_aSetAttrTab.push_back( pTmp ); +} + +bool SwHTMLParser::HasCurrentParaBookmarks( bool bIgnoreStack ) const +{ + bool bHasMarks = false; + SwNodeOffset nNodeIdx = m_pPam->GetPoint()->GetNodeIndex(); + + // first step: are there still bookmark in the attribute-stack? + // bookmarks are added to the end of the stack - thus we only have + // to check the last bookmark + if( !bIgnoreStack ) + { + for( auto i = m_aSetAttrTab.size(); i; ) + { + HTMLAttr* pAttr = m_aSetAttrTab[ --i ]; + if( RES_FLTR_BOOKMARK == pAttr->m_pItem->Which() ) + { + if( pAttr->GetStartParagraphIdx() == nNodeIdx ) + bHasMarks = true; + break; + } + } + } + + if( !bHasMarks ) + { + // second step: when we didn't find a bookmark, check if there is one set already + IDocumentMarkAccess* const pMarkAccess = m_xDoc->getIDocumentMarkAccess(); + for(IDocumentMarkAccess::const_iterator_t ppMark = pMarkAccess->getAllMarksBegin(); + ppMark != pMarkAccess->getAllMarksEnd(); + ++ppMark) + { + const ::sw::mark::IMark* pBookmark = *ppMark; + + const SwNodeOffset nBookNdIdx = pBookmark->GetMarkPos().GetNodeIndex(); + if( nBookNdIdx==nNodeIdx ) + { + bHasMarks = true; + break; + } + else if( nBookNdIdx > nNodeIdx ) + break; + } + } + + return bHasMarks; +} + +/* */ + +void SwHTMLParser::StripTrailingPara() +{ + bool bSetSmallFont = false; + + SwContentNode* pCNd = m_pPam->GetPointContentNode(); + SwNodeOffset nNodeIdx = m_pPam->GetPoint()->GetNodeIndex(); + if( !m_pPam->GetPoint()->GetContentIndex() ) + { + if( pCNd && pCNd->StartOfSectionIndex() + 2 < + pCNd->EndOfSectionIndex() && CanRemoveNode(nNodeIdx)) + { + + for(sw::SpzFrameFormat* pSpz: *m_xDoc->GetSpzFrameFormats()) + { + SwFormatAnchor const*const pAnchor = &pSpz->GetAnchor(); + SwNode const*const pAnchorNode = pAnchor->GetAnchorNode(); + if (pAnchorNode && + ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) && + pAnchorNode->GetIndex() == nNodeIdx ) + + return; // we can't delete the node + } + + SetAttr( false ); // the still open attributes must be + // closed before the node is deleted, + // otherwise the last index is dangling + + if( pCNd->Len() && pCNd->IsTextNode() ) + { + // fields were inserted into the node, now they have + // to be moved + SwTextNode *pPrvNd = m_xDoc->GetNodes()[nNodeIdx-1]->GetTextNode(); + if( pPrvNd ) + { + SwContentIndex aSrc( pCNd, 0 ); + pCNd->GetTextNode()->CutText( pPrvNd, aSrc, pCNd->Len() ); + } + } + + // now we have to move maybe existing bookmarks + IDocumentMarkAccess* const pMarkAccess = m_xDoc->getIDocumentMarkAccess(); + for(IDocumentMarkAccess::const_iterator_t ppMark = pMarkAccess->getAllMarksBegin(); + ppMark != pMarkAccess->getAllMarksEnd(); + ++ppMark) + { + ::sw::mark::IMark* pMark = *ppMark; + + SwNodeOffset nBookNdIdx = pMark->GetMarkPos().GetNodeIndex(); + if(nBookNdIdx==nNodeIdx) + { + SwNodeIndex nNewNdIdx(m_pPam->GetPoint()->GetNode()); + SwContentNode* pNd = SwNodes::GoPrevious(&nNewNdIdx); + if(!pNd) + { + OSL_ENSURE(false, "Oops, where is my predecessor node?"); + return; + } + // #i81002# - refactoring + // Do not directly manipulate member of <SwBookmark> + { + const SwPaM aPaM(*pNd, pNd->Len()); + pMarkAccess->repositionMark(*ppMark, aPaM); + } + } + else if( nBookNdIdx > nNodeIdx ) + break; + } + + SwNode& rDelNode = m_pPam->GetPoint()->GetNode(); + m_pPam->Move( fnMoveBackward, GoInNode ); + m_pPam->SetMark(); + m_pPam->DeleteMark(); + m_xDoc->GetNodes().Delete( rDelNode ); + } + else if (pCNd && pCNd->IsTextNode() && m_xTable) + { + // In empty cells we set a small font, so that the cell doesn't + // get higher than the graphic resp. as low as possible. + bSetSmallFont = true; + } + } + else if( pCNd && pCNd->IsTextNode() && m_xTable && + pCNd->StartOfSectionIndex()+2 == + pCNd->EndOfSectionIndex() ) + { + // When the cell contains only as-character anchored graphics/frames, + // then we also set a small font. + bSetSmallFont = true; + SwTextNode* pTextNd = pCNd->GetTextNode(); + + sal_Int32 nPos = m_pPam->GetPoint()->GetContentIndex(); + while( bSetSmallFont && nPos>0 ) + { + --nPos; + bSetSmallFont = + (CH_TXTATR_BREAKWORD == pTextNd->GetText()[nPos]) && + (nullptr != pTextNd->GetTextAttrForCharAt( nPos, RES_TXTATR_FLYCNT )); + } + } + + if( bSetSmallFont ) + { + // Added default to CJK and CTL + SvxFontHeightItem aFontHeight( 40, 100, RES_CHRATR_FONTSIZE ); + pCNd->SetAttr( aFontHeight ); + SvxFontHeightItem aFontHeightCJK( 40, 100, RES_CHRATR_CJK_FONTSIZE ); + pCNd->SetAttr( aFontHeightCJK ); + SvxFontHeightItem aFontHeightCTL( 40, 100, RES_CHRATR_CTL_FONTSIZE ); + pCNd->SetAttr( aFontHeightCTL ); + } +} + +void SwHTMLParser::NotifyMacroEventRead() +{ + if (m_bNotifyMacroEventRead) + return; + SwDocShell *pDocSh = m_xDoc->GetDocShell(); + if (!pDocSh) + return; + uno::Reference<frame::XModel> const xModel(pDocSh->GetBaseModel()); + comphelper::DocumentInfo::notifyMacroEventRead(xModel); + m_bNotifyMacroEventRead = true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlnum.cxx b/sw/source/filter/html/htmlnum.cxx new file mode 100644 index 0000000000..8fa120a630 --- /dev/null +++ b/sw/source/filter/html/htmlnum.cxx @@ -0,0 +1,93 @@ +/* -*- 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 "htmlnum.hxx" +#include <ndtxt.hxx> +#include <doc.hxx> + +void SwHTMLNumRuleInfo::Set(const SwTextNode& rTextNd) +{ + const SwNumRule* pTextNdNumRule(rTextNd.GetNumRule()); + if (pTextNdNumRule && pTextNdNumRule != rTextNd.GetDoc().GetOutlineNumRule()) + { + m_pNumRule = const_cast<SwNumRule*>(pTextNdNumRule); + m_nDeep = o3tl::narrowing<sal_uInt16>(m_pNumRule ? rTextNd.GetActualListLevel() + 1 : 0); + m_bNumbered = rTextNd.IsCountedInList(); + // #i57919# - correction of refactoring done by cws swnumtree: + // <bRestart> has to be set to <true>, if numbering is restarted at this + // text node and the start value equals <USHRT_MAX>. + // Start value <USHRT_MAX> indicates, that at this text node the numbering + // is restarted with the value given at the corresponding level. + m_bRestart = rTextNd.IsListRestart() && !rTextNd.HasAttrListRestartValue(); + } + else + { + m_pNumRule = nullptr; + m_nDeep = 0; + m_bNumbered = m_bRestart = false; + } +} + +// Restart flag is only effective when this level is not below the previous +bool SwHTMLNumRuleInfo::IsRestart(const SwHTMLNumRuleInfo& rPrev) const +{ + // calling this, when the rules are different, makes no sense + assert(rPrev.GetNumRule() == GetNumRule()); + + // An example ODF when the restart flag is set, but has no effect: + // <text:list text:style-name="L1"> + // <text:list-item> + // <text:p>l1</text:p> + // <text:list> + // <text:list-item> + // <text:p>l2</text:p> + // </text:list-item> + // <text:list-item> + // <text:p>l2</text:p> + // </text:list-item> + // </text:list> + // <text:list> + // <text:list-item> + // <text:list> + // <text:list-item> + // <text:p>l3</text:p> + // </text:list-item> + // </text:list> + // </text:list-item> + // </text:list> + // </text:list-item> + // </text:list> + // In this case, "l3" is in a separate sublist than "l2", and so the "l3" node gets the + // "list restart" property. But the document rendering would be + // 1. l1 + // 1.1. l2 + // 1.2. l2 + // 1.2.1. l3 + // and the second-level numbering will not actually restart at the "l3" node. + // + // TODO/LATER: note that restarting may happen at different levels. In the code using this + // function, the level is reset to 0 whenever a restart is detected. And also, there is no + // code to actually descend to that new level (close corresponding li/ul/ol elements). + + if (rPrev.GetDepth() < GetDepth()) + return false; // No matter if the restart flag is set, it is not effective for subitems + return m_bRestart; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlnum.hxx b/sw/source/filter/html/htmlnum.hxx new file mode 100644 index 0000000000..670f08229d --- /dev/null +++ b/sw/source/filter/html/htmlnum.hxx @@ -0,0 +1,125 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_FILTER_HTML_HTMLNUM_HXX +#define INCLUDED_SW_SOURCE_FILTER_HTML_HTMLNUM_HXX + +#include <swtypes.hxx> +#include <string.h> + +#define HTML_NUMBER_BULLET_MARGINLEFT (o3tl::toTwips(125, o3tl::Length::mm10)) +#define HTML_NUMBER_BULLET_INDENT (-o3tl::toTwips(5, o3tl::Length::mm)) + +class SwTextNode; +class SwNumRule; + +// TODO: Unicode: Are these characters the correct ones? +#define HTML_BULLETCHAR_DISC (0xe008) +#define HTML_BULLETCHAR_CIRCLE (0xe009) +#define HTML_BULLETCHAR_SQUARE (0xe00b) + +class SwHTMLNumRuleInfo +{ + sal_uInt16 m_aNumStarts[MAXLEVEL]; + SwNumRule * m_pNumRule; // current numbering + sal_uInt16 m_nDeep; // current numbering depth (1, 2, 3, ...) + bool m_bRestart; // Export: restart numbering + bool m_bNumbered; // Export: paragraph is numbered + +public: + + inline void Set( const SwHTMLNumRuleInfo& rInf ); + void Set( const SwTextNode& rTextNd ); + + SwHTMLNumRuleInfo() : + m_pNumRule( nullptr ), m_nDeep( 0 ), + m_bRestart( false ), m_bNumbered( false ) + { + memset( &m_aNumStarts, 0xff, sizeof( m_aNumStarts ) ); + } + + SwHTMLNumRuleInfo( const SwHTMLNumRuleInfo& rInf ) : + m_pNumRule( rInf.m_pNumRule ), m_nDeep( rInf.m_nDeep ), + m_bRestart( rInf.m_bRestart ), m_bNumbered( rInf.m_bNumbered ) + { + memcpy( &m_aNumStarts, &rInf.m_aNumStarts, sizeof( m_aNumStarts ) ); + } + + explicit SwHTMLNumRuleInfo( const SwTextNode& rTextNd ) { Set( rTextNd ); } + inline SwHTMLNumRuleInfo& operator=( const SwHTMLNumRuleInfo& rInf ); + + inline void Clear(); + + void SetNumRule( const SwNumRule *pRule ) { m_pNumRule = const_cast<SwNumRule *>(pRule); } + SwNumRule *GetNumRule() { return m_pNumRule; } + const SwNumRule *GetNumRule() const { return m_pNumRule; } + + void SetDepth( sal_uInt16 nDepth ) { m_nDeep = nDepth; } + sal_uInt16 GetDepth() const { return m_nDeep; } + void IncDepth() { ++m_nDeep; } + void DecDepth() { if (m_nDeep!=0) --m_nDeep; } + inline sal_uInt8 GetLevel() const; + + bool IsRestart(const SwHTMLNumRuleInfo& rPrev) const; + + bool IsNumbered() const { return m_bNumbered; } + + inline void SetNodeStartValue( sal_uInt8 nLvl, sal_uInt16 nVal=USHRT_MAX ); + sal_uInt16 GetNodeStartValue( sal_uInt8 nLvl ) const { return m_aNumStarts[nLvl]; } +}; + +inline SwHTMLNumRuleInfo& SwHTMLNumRuleInfo::operator=( + const SwHTMLNumRuleInfo& rInf ) +{ + Set( rInf ); + return *this; +} + +inline void SwHTMLNumRuleInfo::Set( const SwHTMLNumRuleInfo& rInf ) +{ + m_pNumRule = rInf.m_pNumRule; + m_nDeep = rInf.m_nDeep; + m_bRestart = rInf.m_bRestart; + m_bNumbered = rInf.m_bNumbered; + memcpy( &m_aNumStarts, &rInf.m_aNumStarts, sizeof( m_aNumStarts ) ); +} + +inline void SwHTMLNumRuleInfo::Clear() +{ + m_pNumRule = nullptr; + m_nDeep = 0; + m_bRestart = m_bNumbered = false; + memset( &m_aNumStarts, 0xff, sizeof( m_aNumStarts ) ); +} + +inline sal_uInt8 SwHTMLNumRuleInfo::GetLevel() const +{ + return + static_cast<sal_uInt8>( m_pNumRule!=nullptr && m_nDeep != 0 + ? ( m_nDeep<=MAXLEVEL ? m_nDeep-1 : MAXLEVEL - 1 ) + : 0 ); +} + +inline void SwHTMLNumRuleInfo::SetNodeStartValue( sal_uInt8 nLvl, sal_uInt16 nVal ) +{ + m_aNumStarts[nLvl] = nVal; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlnumreader.cxx b/sw/source/filter/html/htmlnumreader.cxx new file mode 100644 index 0000000000..c21a45e877 --- /dev/null +++ b/sw/source/filter/html/htmlnumreader.cxx @@ -0,0 +1,620 @@ +/* -*- 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/VertOrientation.hpp> +#include <hintids.hxx> +#include <svtools/htmltokn.h> +#include <svtools/htmlkywd.hxx> +#include <svl/urihelper.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/lrspitem.hxx> +#include <vcl/svapp.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <numrule.hxx> +#include <doc.hxx> +#include <docary.hxx> +#include <poolfmt.hxx> +#include <ndtxt.hxx> +#include <paratr.hxx> + +#include "htmlnum.hxx" +#include "swcss1.hxx" +#include "swhtml.hxx" + +using namespace css; + +// <UL TYPE=...> +HTMLOptionEnum<sal_UCS4> const aHTMLULTypeTable[] = +{ + { OOO_STRING_SVTOOLS_HTML_ULTYPE_disc, HTML_BULLETCHAR_DISC }, + { OOO_STRING_SVTOOLS_HTML_ULTYPE_circle, HTML_BULLETCHAR_CIRCLE }, + { OOO_STRING_SVTOOLS_HTML_ULTYPE_square, HTML_BULLETCHAR_SQUARE }, + { nullptr, 0 } +}; + + +void SwHTMLParser::NewNumberBulletList( HtmlTokenId nToken ) +{ + SwHTMLNumRuleInfo& rInfo = GetNumInfo(); + + // Create a new paragraph + bool bSpace = (rInfo.GetDepth() + m_nDefListDeep) == 0; + if( m_pPam->GetPoint()->GetContentIndex() ) + AppendTextNode( bSpace ? AM_SPACE : AM_NOSPACE, false ); + else if( bSpace ) + AddParSpace(); + + // Increment the numbering depth + rInfo.IncDepth(); + sal_uInt8 nLevel = static_cast<sal_uInt8>( (rInfo.GetDepth() <= MAXLEVEL ? rInfo.GetDepth() + : MAXLEVEL) - 1 ); + + // Create rules if needed + if( !rInfo.GetNumRule() ) + { + sal_uInt16 nPos = m_xDoc->MakeNumRule( m_xDoc->GetUniqueNumRuleName() ); + rInfo.SetNumRule( m_xDoc->GetNumRuleTable()[nPos] ); + } + + // Change the format for this level if that hasn't happened yet for this level + bool bNewNumFormat = rInfo.GetNumRule()->GetNumFormat( nLevel ) == nullptr; + bool bChangeNumFormat = false; + + // Create the default numbering format + SwNumFormat aNumFormat( rInfo.GetNumRule()->Get(nLevel) ); + rInfo.SetNodeStartValue( nLevel ); + if( bNewNumFormat ) + { + sal_uInt16 nChrFormatPoolId = 0; + if( HtmlTokenId::ORDERLIST_ON == nToken ) + { + aNumFormat.SetNumberingType(SVX_NUM_ARABIC); + nChrFormatPoolId = RES_POOLCHR_NUM_LEVEL; + } + else + { + // We'll set a default style because the UI does the same. This meant a 9pt font, which + // was not the case in Netscape. That didn't bother anyone so far + // #i63395# - Only apply user defined default bullet font + if ( numfunc::IsDefBulletFontUserDefined() ) + { + aNumFormat.SetBulletFont( &numfunc::GetDefBulletFont() ); + } + aNumFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL); + aNumFormat.SetBulletChar( cBulletChar ); + nChrFormatPoolId = RES_POOLCHR_BULLET_LEVEL; + } + + sal_Int32 nAbsLSpace = HTML_NUMBER_BULLET_MARGINLEFT; + + sal_Int32 nFirstLineIndent = HTML_NUMBER_BULLET_INDENT; + if( nLevel > 0 ) + { + const SwNumFormat& rPrevNumFormat = rInfo.GetNumRule()->Get( nLevel-1 ); + nAbsLSpace = nAbsLSpace + rPrevNumFormat.GetAbsLSpace(); + nFirstLineIndent = rPrevNumFormat.GetFirstLineOffset(); + } + aNumFormat.SetAbsLSpace( nAbsLSpace ); + aNumFormat.SetFirstLineOffset( nFirstLineIndent ); + aNumFormat.SetCharFormat( m_pCSS1Parser->GetCharFormatFromPool(nChrFormatPoolId) ); + + bChangeNumFormat = true; + } + else if( 1 != aNumFormat.GetStart() ) + { + // If the layer has already been used, the start value may need to be set hard to the paragraph. + rInfo.SetNodeStartValue( nLevel, 1 ); + } + + // and set that in the options + OUString aId, aStyle, aClass, aLang, aDir; + OUString aBulletSrc; + sal_Int16 eVertOri = text::VertOrientation::NONE; + sal_uInt16 nWidth=USHRT_MAX, nHeight=USHRT_MAX; + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::TYPE: + if( bNewNumFormat && !rOption.GetString().isEmpty() ) + { + switch( nToken ) + { + case HtmlTokenId::ORDERLIST_ON: + bChangeNumFormat = true; + switch( rOption.GetString()[0] ) + { + case 'A': aNumFormat.SetNumberingType(SVX_NUM_CHARS_UPPER_LETTER); break; + case 'a': aNumFormat.SetNumberingType(SVX_NUM_CHARS_LOWER_LETTER); break; + case 'I': aNumFormat.SetNumberingType(SVX_NUM_ROMAN_UPPER); break; + case 'i': aNumFormat.SetNumberingType(SVX_NUM_ROMAN_LOWER); break; + default: bChangeNumFormat = false; + } + break; + + case HtmlTokenId::UNORDERLIST_ON: + aNumFormat.SetBulletChar( rOption.GetEnum( + aHTMLULTypeTable,aNumFormat.GetBulletChar() ) ); + bChangeNumFormat = true; + break; + default: break; + } + } + break; + case HtmlOptionId::START: + { + sal_uInt16 nStart = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + if( bNewNumFormat ) + { + aNumFormat.SetStart( nStart ); + bChangeNumFormat = true; + } + else + { + rInfo.SetNodeStartValue( nLevel, nStart ); + } + } + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::LANG: + aLang = rOption.GetString(); + break; + case HtmlOptionId::DIR: + aDir = rOption.GetString(); + break; + case HtmlOptionId::SRC: + if( bNewNumFormat ) + { + aBulletSrc = rOption.GetString(); + if( !InternalImgToPrivateURL(aBulletSrc) ) + aBulletSrc = URIHelper::SmartRel2Abs( INetURLObject( m_sBaseURL ), aBulletSrc, Link<OUString *, bool>(), false ); + } + break; + case HtmlOptionId::WIDTH: + nWidth = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + break; + case HtmlOptionId::HEIGHT: + nHeight = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + break; + case HtmlOptionId::ALIGN: + eVertOri = rOption.GetEnum( aHTMLImgVAlignTable, eVertOri ); + break; + default: break; + } + } + + if( !aBulletSrc.isEmpty() ) + { + // A bullet list with graphics + aNumFormat.SetNumberingType(SVX_NUM_BITMAP); + + // Create the graphic as a brush + SvxBrushItem aBrushItem( RES_BACKGROUND ); + aBrushItem.SetGraphicLink( aBulletSrc ); + aBrushItem.SetGraphicPos( GPOS_AREA ); + + // Only set size if given a width and a height + Size aTwipSz( nWidth, nHeight), *pTwipSz=nullptr; + if( nWidth!=USHRT_MAX && nHeight!=USHRT_MAX ) + { + aTwipSz = o3tl::convert(aTwipSz, o3tl::Length::px, o3tl::Length::twip); + pTwipSz = &aTwipSz; + } + + // Only set orientation if given one + aNumFormat.SetGraphicBrush( &aBrushItem, pTwipSz, + text::VertOrientation::NONE!=eVertOri ? &eVertOri : nullptr); + + // Remember the graphic to not put it into the paragraph + m_aBulletGrfs[nLevel] = aBulletSrc; + bChangeNumFormat = true; + } + else + m_aBulletGrfs[nLevel].clear(); + + // don't number the current paragraph (for now) + { + sal_uInt8 nLvl = nLevel; + SetNodeNum( nLvl ); + } + + // create a new context + std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(nToken)); + + // Parse styles + if( HasStyleOptions( aStyle, aId, aClass, &aLang, &aDir ) ) + { + SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + + if( ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo, &aLang, &aDir ) ) + { + if( bNewNumFormat ) + { + if( aPropInfo.m_bLeftMargin ) + { + // Default indent has already been added + tools::Long nAbsLSpace = + aNumFormat.GetAbsLSpace() - HTML_NUMBER_BULLET_MARGINLEFT; + if( aPropInfo.m_nLeftMargin < 0 && + nAbsLSpace < -aPropInfo.m_nLeftMargin ) + nAbsLSpace = 0U; + else if( aPropInfo.m_nLeftMargin > SHRT_MAX || + nAbsLSpace + aPropInfo.m_nLeftMargin > SHRT_MAX ) + nAbsLSpace = SHRT_MAX; + else + nAbsLSpace = nAbsLSpace + aPropInfo.m_nLeftMargin; + + aNumFormat.SetAbsLSpace( nAbsLSpace ); + bChangeNumFormat = true; + } + if( aPropInfo.m_bTextIndent ) + { + short nTextIndent = + aItemSet.Get(RES_MARGIN_FIRSTLINE).GetTextFirstLineOffset(); + aNumFormat.SetFirstLineOffset( nTextIndent ); + bChangeNumFormat = true; + } + if( aPropInfo.m_bNumbering ) + { + aNumFormat.SetNumberingType(aPropInfo.m_nNumberingType); + bChangeNumFormat = true; + } + if( aPropInfo.m_bBullet ) + { + aNumFormat.SetBulletChar( aPropInfo.m_cBulletChar ); + bChangeNumFormat = true; + } + } + aPropInfo.m_bLeftMargin = aPropInfo.m_bTextIndent = false; + if( !aPropInfo.m_bRightMargin ) + aItemSet.ClearItem(RES_MARGIN_RIGHT); // superfluous? + + // #i89812# - Perform change to list style before calling <DoPositioning(..)>, + // because <DoPositioning(..)> may open a new context and thus may + // clear the <SwHTMLNumRuleInfo> instance hold by local variable <rInfo>. + if( bChangeNumFormat ) + { + rInfo.GetNumRule()->Set( nLevel, aNumFormat ); + m_xDoc->ChgNumRuleFormats( *rInfo.GetNumRule() ); + bChangeNumFormat = false; + } + + DoPositioning(aItemSet, aPropInfo, xCntxt.get()); + + InsertAttrs(aItemSet, aPropInfo, xCntxt.get()); + } + } + + if( bChangeNumFormat ) + { + rInfo.GetNumRule()->Set( nLevel, aNumFormat ); + m_xDoc->ChgNumRuleFormats( *rInfo.GetNumRule() ); + } + + PushContext(xCntxt); + + // set attributes to the current template + SetTextCollAttrs(m_aContexts.back().get()); +} + +void SwHTMLParser::EndNumberBulletList( HtmlTokenId nToken ) +{ + SwHTMLNumRuleInfo& rInfo = GetNumInfo(); + + // A new paragraph needs to be created, when + // - the current one isn't empty (it contains text or paragraph-bound objects) + // - the current one is numbered + bool bAppend = m_pPam->GetPoint()->GetContentIndex() > 0; + if( !bAppend ) + { + SwTextNode* pTextNode = m_pPam->GetPointNode().GetTextNode(); + + bAppend = (pTextNode && ! pTextNode->IsOutline() && pTextNode->IsCountedInList()) || + + HasCurrentParaFlys(); + } + + bool bSpace = (rInfo.GetDepth() + m_nDefListDeep) == 1; + if( bAppend ) + AppendTextNode( bSpace ? AM_SPACE : AM_NOSPACE, false ); + else if( bSpace ) + AddParSpace(); + + // get current context from stack + std::unique_ptr<HTMLAttrContext> xCntxt(nToken != HtmlTokenId::NONE ? PopContext(getOnToken(nToken)) : nullptr); + + // Don't end a list because of a token, if the context wasn't created or mustn't be ended + if( rInfo.GetDepth()>0 && (nToken == HtmlTokenId::NONE || xCntxt) ) + { + rInfo.DecDepth(); + if( !rInfo.GetDepth() ) // was that the last level? + { + // The formats not yet modified are now modified, to ease editing + const SwNumFormat *pRefNumFormat = nullptr; + bool bChanged = false; + for( sal_uInt16 i=0; i<MAXLEVEL; i++ ) + { + const SwNumFormat *pNumFormat = rInfo.GetNumRule()->GetNumFormat(i); + if( pNumFormat ) + { + pRefNumFormat = pNumFormat; + } + else if( pRefNumFormat ) + { + SwNumFormat aNumFormat( rInfo.GetNumRule()->Get(i) ); + aNumFormat.SetNumberingType(pRefNumFormat->GetNumberingType() != SVX_NUM_BITMAP + ? pRefNumFormat->GetNumberingType() : SVX_NUM_CHAR_SPECIAL); + if( SVX_NUM_CHAR_SPECIAL == aNumFormat.GetNumberingType() ) + { + // #i63395# - Only apply user defined default bullet font + if ( numfunc::IsDefBulletFontUserDefined() ) + { + aNumFormat.SetBulletFont( &numfunc::GetDefBulletFont() ); + } + aNumFormat.SetBulletChar( cBulletChar ); + } + aNumFormat.SetAbsLSpace( (i+1) * HTML_NUMBER_BULLET_MARGINLEFT ); + aNumFormat.SetFirstLineOffset( HTML_NUMBER_BULLET_INDENT ); + aNumFormat.SetCharFormat( pRefNumFormat->GetCharFormat() ); + rInfo.GetNumRule()->Set( i, aNumFormat ); + bChanged = true; + } + } + if( bChanged ) + m_xDoc->ChgNumRuleFormats( *rInfo.GetNumRule() ); + + // On the last append, the NumRule item and NodeNum object were copied. + // Now we need to delete them. ResetAttr deletes the NodeNum object as well + if (SwTextNode *pTextNode = m_pPam->GetPointNode().GetTextNode()) + pTextNode->ResetAttr(RES_PARATR_NUMRULE); + + rInfo.Clear(); + } + else + { + // the next paragraph not numbered first + SetNodeNum( rInfo.GetLevel() ); + } + } + + // end attributes + bool bSetAttrs = false; + if (xCntxt) + { + EndContext(xCntxt.get()); + xCntxt.reset(); + bSetAttrs = true; + } + + if( nToken != HtmlTokenId::NONE ) + SetTextCollAttrs(); + + if( bSetAttrs ) + SetAttr(); // Set paragraph attributes asap because of Javascript + +} + +void SwHTMLParser::NewNumberBulletListItem( HtmlTokenId nToken ) +{ + sal_uInt8 nLevel = GetNumInfo().GetLevel(); + OUString aId, aStyle, aClass, aLang, aDir; + sal_uInt16 nStart = HtmlTokenId::LISTHEADER_ON != nToken + ? GetNumInfo().GetNodeStartValue( nLevel ) + : USHRT_MAX; + if( USHRT_MAX != nStart ) + GetNumInfo().SetNodeStartValue( nLevel ); + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::VALUE: + nStart = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + break; + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::LANG: + aLang = rOption.GetString(); + break; + case HtmlOptionId::DIR: + aDir = rOption.GetString(); + break; + default: break; + } + } + + // create a new paragraph + if( m_pPam->GetPoint()->GetContentIndex() ) + AppendTextNode( AM_NOSPACE, false ); + m_bNoParSpace = false; // no space in <LI>! + + SwTextNode* pTextNode = m_pPam->GetPointNode().GetTextNode(); + if (!pTextNode) + { + SAL_WARN("sw.html", "No Text-Node at PaM-Position"); + return; + } + + const bool bCountedInList = nToken != HtmlTokenId::LISTHEADER_ON; + + std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(nToken)); + + OUString aNumRuleName; + if( GetNumInfo().GetNumRule() ) + { + aNumRuleName = GetNumInfo().GetNumRule()->GetName(); + } + else + { + aNumRuleName = m_xDoc->GetUniqueNumRuleName(); + SwNumRule aNumRule( aNumRuleName, + SvxNumberFormat::LABEL_WIDTH_AND_POSITION ); + SwNumFormat aNumFormat( aNumRule.Get( 0 ) ); + // #i63395# - Only apply user defined default bullet font + if ( numfunc::IsDefBulletFontUserDefined() ) + { + aNumFormat.SetBulletFont( &numfunc::GetDefBulletFont() ); + } + aNumFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL); + aNumFormat.SetBulletChar( cBulletChar ); // the bullet character !! + aNumFormat.SetCharFormat( m_pCSS1Parser->GetCharFormatFromPool(RES_POOLCHR_BULLET_LEVEL) ); + aNumFormat.SetFirstLineOffset( HTML_NUMBER_BULLET_INDENT ); + aNumRule.Set( 0, aNumFormat ); + + m_xDoc->MakeNumRule( aNumRuleName, &aNumRule ); + + OSL_ENSURE( m_nOpenParaToken == HtmlTokenId::NONE, + "Now an open paragraph element is lost" ); + // We'll act like we're in a paragraph. On the next paragraph, at least numbering is gone, + // that's gonna be taken over by the next AppendTextNode + m_nOpenParaToken = nToken; + } + + static_cast<SwContentNode *>(pTextNode)->SetAttr( SwNumRuleItem(aNumRuleName) ); + pTextNode->SetAttrListLevel(nLevel); + // #i57656# - <IsCounted()> state of text node has to be adjusted accordingly. + if ( nLevel < MAXLEVEL ) + { + pTextNode->SetCountedInList( bCountedInList ); + } + // #i57919# + // correction of refactoring done by cws swnumtree + // - <nStart> contains the start value, if the numbering has to be restarted + // at this text node. Value <USHRT_MAX> indicates, that numbering isn't + // restarted at this text node + if ( nStart != USHRT_MAX ) + { + pTextNode->SetListRestart( true ); + pTextNode->SetAttrListRestartValue( nStart ); + } + + if( GetNumInfo().GetNumRule() ) + GetNumInfo().GetNumRule()->SetInvalidRule( true ); + + // parse styles + if( HasStyleOptions( aStyle, aId, aClass, &aLang, &aDir ) ) + { + SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + + if( ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo, &aLang, &aDir ) ) + { + DoPositioning(aItemSet, aPropInfo, xCntxt.get()); + InsertAttrs(aItemSet, aPropInfo, xCntxt.get()); + } + } + + PushContext(xCntxt); + + // set the new template + SetTextCollAttrs(m_aContexts.back().get()); + + // Refresh scroll bar + ShowStatline(); +} + +void SwHTMLParser::EndNumberBulletListItem( HtmlTokenId nToken, bool bSetColl ) +{ + // Create a new paragraph + if( nToken == HtmlTokenId::NONE && m_pPam->GetPoint()->GetContentIndex() ) + AppendTextNode( AM_NOSPACE ); + + // Get context to that token and pop it from stack + std::unique_ptr<HTMLAttrContext> xCntxt; + auto nPos = m_aContexts.size(); + nToken = getOnToken(nToken); + while (!xCntxt && nPos>m_nContextStMin) + { + HtmlTokenId nCntxtToken = m_aContexts[--nPos]->GetToken(); + switch( nCntxtToken ) + { + case HtmlTokenId::LI_ON: + case HtmlTokenId::LISTHEADER_ON: + if( nToken == HtmlTokenId::NONE || nToken == nCntxtToken ) + { + xCntxt = std::move(m_aContexts[nPos]); + m_aContexts.erase( m_aContexts.begin() + nPos ); + } + break; + case HtmlTokenId::ORDERLIST_ON: + case HtmlTokenId::UNORDERLIST_ON: + case HtmlTokenId::MENULIST_ON: + case HtmlTokenId::DIRLIST_ON: + // Don't care about LI/LH outside the current list + nPos = m_nContextStMin; + break; + default: break; + } + } + + // end attributes + if (xCntxt) + { + EndContext(xCntxt.get()); + SetAttr(); // set paragraph attributes asap because of Javascript + xCntxt.reset(); + } + + // set current template + if( bSetColl ) + SetTextCollAttrs(); +} + +void SwHTMLParser::SetNodeNum( sal_uInt8 nLevel ) +{ + SwTextNode* pTextNode = m_pPam->GetPointNode().GetTextNode(); + if (!pTextNode) + { + SAL_WARN("sw.html", "No Text-Node at PaM-Position"); + return; + } + + OSL_ENSURE( GetNumInfo().GetNumRule(), "No numbering rule" ); + const OUString& rName = GetNumInfo().GetNumRule()->GetName(); + static_cast<SwContentNode *>(pTextNode)->SetAttr( SwNumRuleItem(rName) ); + + pTextNode->SetAttrListLevel( nLevel ); + pTextNode->SetCountedInList( false ); + + // Invalidate NumRule, it may have been set valid because of an EndAction + GetNumInfo().GetNumRule()->SetInvalidRule( false ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlnumwriter.cxx b/sw/source/filter/html/htmlnumwriter.cxx new file mode 100644 index 0000000000..046747754a --- /dev/null +++ b/sw/source/filter/html/htmlnumwriter.cxx @@ -0,0 +1,336 @@ +/* -*- 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 <svtools/htmltokn.h> +#include <svtools/htmlkywd.hxx> +#include <svtools/htmlout.hxx> +#include <numrule.hxx> +#include <doc.hxx> +#include <ndtxt.hxx> +#include <pam.hxx> + +#include "htmlnum.hxx" +#include "wrthtml.hxx" + +#include <osl/diagnose.h> + +using namespace css; + + +void SwHTMLWriter::FillNextNumInfo() +{ + m_pNextNumRuleInfo = nullptr; + + SwNodeOffset nPos = m_pCurrentPam->GetPoint()->GetNodeIndex() + 1; + + bool bTable = false; + do + { + const SwNode* pNd = m_pDoc->GetNodes()[nPos]; + if( pNd->IsTextNode() ) + { + m_pNextNumRuleInfo.reset( new SwHTMLNumRuleInfo( *pNd->GetTextNode() ) ); + + // Before a table we keep the old level if the same numbering is + // continued after the table and no new numbering is started. + // The table will get the indentation that corresponds to its + // numbering level during import. + if( bTable && + m_pNextNumRuleInfo->GetNumRule()==GetNumInfo().GetNumRule() && + !m_pNextNumRuleInfo->IsRestart(GetNumInfo()) ) + { + m_pNextNumRuleInfo->SetDepth( GetNumInfo().GetDepth() ); + } + } + else if( pNd->IsTableNode() ) + { + // A table is skipped so the node after table is viewed. + nPos = pNd->EndOfSectionIndex() + 1; + bTable = true; + } + else + { + // In all other case the numbering is over. + m_pNextNumRuleInfo.reset(new SwHTMLNumRuleInfo); + } + } + while( !m_pNextNumRuleInfo ); +} + +void SwHTMLWriter::ClearNextNumInfo() +{ + m_pNextNumRuleInfo.reset(); +} + +void SwHTMLWriter::SetNextNumInfo( std::unique_ptr<SwHTMLNumRuleInfo> pNxt ) +{ + m_pNextNumRuleInfo = std::move(pNxt); +} + +SwHTMLWriter& OutHTML_NumberBulletListStart( SwHTMLWriter& rWrt, + const SwHTMLNumRuleInfo& rInfo ) +{ + SwHTMLNumRuleInfo& rPrevInfo = rWrt.GetNumInfo(); + bool bSameRule = rPrevInfo.GetNumRule() == rInfo.GetNumRule(); + if( bSameRule && rPrevInfo.GetDepth() >= rInfo.GetDepth() && + !rInfo.IsRestart(rPrevInfo) ) + { + return rWrt; + } + + bool bStartValue = false; + if( !bSameRule && rInfo.GetDepth() ) + { + OUString aName( rInfo.GetNumRule()->GetName() ); + if( 0 != rWrt.m_aNumRuleNames.count( aName ) ) + { + // The rule has been applied before + sal_Int16 eType = rInfo.GetNumRule() + ->Get( rInfo.GetDepth()-1 ).GetNumberingType(); + if( SVX_NUM_CHAR_SPECIAL != eType && SVX_NUM_BITMAP != eType ) + { + // If it's a numbering rule, the current number should be + // exported as start value, but only if there are no nodes + // within the numbering that have a lower level + bStartValue = true; + if( rInfo.GetDepth() > 1 ) + { + SwNodeOffset nPos = + rWrt.m_pCurrentPam->GetPoint()->GetNodeIndex() + 1; + do + { + const SwNode* pNd = rWrt.m_pDoc->GetNodes()[nPos]; + if( pNd->IsTextNode() ) + { + const SwTextNode *pTextNd = pNd->GetTextNode(); + if( !pTextNd->GetNumRule() ) + { + // node isn't numbered => check completed + break; + } + + OSL_ENSURE(! pTextNd->IsOutline(), + "outline not expected"); + + if( pTextNd->GetActualListLevel() + 1 < + rInfo.GetDepth() ) + { + if (rPrevInfo.GetDepth() == 0) + // previous node had no numbering => write start value + bStartValue = true; + else + // node is numbered, but level is lower + bStartValue = false; + // => check completed + break; + } + nPos++; + } + else if( pNd->IsTableNode() ) + { + // skip table + nPos = pNd->EndOfSectionIndex() + 1; + } + else + { + // end node or sections start node -> check + // completed + break; + } + } + while( true ); + } + } + } + else + { + rWrt.m_aNumRuleNames.insert( aName ); + } + } + + OSL_ENSURE( rWrt.m_nLastParaToken == HtmlTokenId::NONE, + "<PRE> was not closed before <OL>." ); + sal_uInt16 nPrevDepth = + (bSameRule && !rInfo.IsRestart(rPrevInfo)) ? rPrevInfo.GetDepth() : 0; + + for( sal_uInt16 i=nPrevDepth; i<rInfo.GetDepth(); i++ ) + { + rWrt.OutNewLine(); // <OL>/<UL> in a new row + + rWrt.m_aBulletGrfs[i].clear(); + OString sOut = "<" + rWrt.GetNamespace(); + if (rWrt.mbXHTML && i != nPrevDepth) + { + // for all skipped sublevels, add a li + sOut += OOO_STRING_SVTOOLS_HTML_li "><" + rWrt.GetNamespace(); + } + const SwNumFormat& rNumFormat = rInfo.GetNumRule()->Get( i ); + sal_Int16 eType = rNumFormat.GetNumberingType(); + if( SVX_NUM_CHAR_SPECIAL == eType ) + { + // unordered list: <UL> + sOut += OOO_STRING_SVTOOLS_HTML_unorderlist; + + // determine the type by the bullet character + const char *pStr = nullptr; + switch( rNumFormat.GetBulletChar() ) + { + case HTML_BULLETCHAR_DISC: + pStr = OOO_STRING_SVTOOLS_HTML_ULTYPE_disc; + break; + case HTML_BULLETCHAR_CIRCLE: + pStr = OOO_STRING_SVTOOLS_HTML_ULTYPE_circle; + break; + case HTML_BULLETCHAR_SQUARE: + pStr = OOO_STRING_SVTOOLS_HTML_ULTYPE_square; + break; + } + + if( pStr ) + { + sOut += OString::Concat(" " OOO_STRING_SVTOOLS_HTML_O_type "=\"") + pStr + "\""; + } + } + else if( SVX_NUM_BITMAP == eType ) + { + // Unordered list: <UL> + sOut += OOO_STRING_SVTOOLS_HTML_unorderlist; + } + else + { + // Ordered list: <OL> + sOut += OOO_STRING_SVTOOLS_HTML_orderlist; + + if (!rWrt.mbReqIF) // No 'type' nor 'start' attribute in ReqIF + { + // determine the type by the format + char cType = 0; + switch (eType) + { + case SVX_NUM_CHARS_UPPER_LETTER: + case SVX_NUM_CHARS_UPPER_LETTER_N: + cType = 'A'; + break; + case SVX_NUM_CHARS_LOWER_LETTER: + case SVX_NUM_CHARS_LOWER_LETTER_N: + cType = 'a'; + break; + case SVX_NUM_ROMAN_UPPER: + cType = 'I'; + break; + case SVX_NUM_ROMAN_LOWER: + cType = 'i'; + break; + } + if( cType ) + { + sOut += " " OOO_STRING_SVTOOLS_HTML_O_type "=\"" + OStringChar(cType) + "\""; + } + + sal_uInt16 nStartVal = rNumFormat.GetStart(); + if( bStartValue && 1 == nStartVal && i == rInfo.GetDepth()-1 ) + { + if ( rWrt.m_pCurrentPam->GetPointNode().GetTextNode()->GetNum() ) + { + nStartVal = static_cast< sal_uInt16 >( rWrt.m_pCurrentPam->GetPointNode() + .GetTextNode()->GetNumberVector()[i] ); + } + else + { + OSL_FAIL( "<OutHTML_NumberBulletListStart(..) - text node has no number." ); + } + } + if( nStartVal != 1 ) + { + sOut += " " OOO_STRING_SVTOOLS_HTML_O_start "=\"" + OString::number(static_cast<sal_Int32>(nStartVal)) + "\""; + } + } + } + + rWrt.Strm().WriteOString(sOut); + + if (eType == SVX_NUM_BITMAP) + OutHTML_BulletImage(rWrt, nullptr, rNumFormat.GetBrush(), rWrt.m_aBulletGrfs[i]); + + if( rWrt.m_bCfgOutStyles ) + OutCSS1_NumberBulletListStyleOpt( rWrt, *rInfo.GetNumRule(), static_cast<sal_uInt8>(i) ); + + rWrt.Strm().WriteChar( '>' ); + + rWrt.IncIndentLevel(); // indent content of <OL> + } + + return rWrt; +} + +SwHTMLWriter& OutHTML_NumberBulletListEnd( SwHTMLWriter& rWrt, + const SwHTMLNumRuleInfo& rNextInfo ) +{ + SwHTMLNumRuleInfo& rInfo = rWrt.GetNumInfo(); + bool bSameRule = rNextInfo.GetNumRule() == rInfo.GetNumRule(); + bool bListEnd = !bSameRule || rNextInfo.GetDepth() < rInfo.GetDepth() || rNextInfo.IsRestart(rInfo); + bool bNextIsSubitem = !bListEnd && rNextInfo.GetDepth() > rInfo.GetDepth(); + + // XHTML </li> for the list item content, if there is an open <li>. + if (bListEnd || (!bNextIsSubitem && rNextInfo.IsNumbered())) + { + HTMLOutFuncs::Out_AsciiTag( + rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_li), + false); + } + + if (!bListEnd) + { + return rWrt; + } + + OSL_ENSURE( rWrt.m_nLastParaToken == HtmlTokenId::NONE, + "<PRE> was not closed before </OL>." ); + sal_uInt16 nNextDepth = + (bSameRule && !rNextInfo.IsRestart(rInfo)) ? rNextInfo.GetDepth() : 0; + + // MIB 23.7.97: We must loop backwards, to get the right order of </OL>/</UL> + for( sal_uInt16 i=rInfo.GetDepth(); i>nNextDepth; i-- ) + { + rWrt.DecIndentLevel(); // indent content of <OL> + if (rWrt.IsLFPossible()) + rWrt.OutNewLine(); // </OL>/</UL> in a new line + + // a list is started or ended: + sal_Int16 eType = rInfo.GetNumRule()->Get( i-1 ).GetNumberingType(); + OString aTag; + if( SVX_NUM_CHAR_SPECIAL == eType || SVX_NUM_BITMAP == eType) + aTag = OOO_STRING_SVTOOLS_HTML_unorderlist ""_ostr; + else + aTag = OOO_STRING_SVTOOLS_HTML_orderlist ""_ostr; + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + aTag), false ); + if (rWrt.mbXHTML && (i != nNextDepth + 1 || (i != 1 && rNextInfo.IsNumbered()))) + { + // for all skipped sublevels, close a li + HTMLOutFuncs::Out_AsciiTag( + rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_li), + /*bOn=*/false); + } + rWrt.SetLFPossible(true); + } + + return rWrt; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlplug.cxx b/sw/source/filter/html/htmlplug.cxx new file mode 100644 index 0000000000..8b2ff71cd3 --- /dev/null +++ b/sw/source/filter/html/htmlplug.cxx @@ -0,0 +1,1765 @@ +/* -*- 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 <config_java.h> + +#include <hintids.hxx> +#include <rtl/strbuf.hxx> +#include <sal/log.hxx> +#include <svl/urihelper.hxx> +#include <vcl/svapp.hxx> +#include <sfx2/frmhtml.hxx> +#include <sfx2/frmhtmlw.hxx> +#include <sfx2/frmdescr.hxx> +#include <sot/storage.hxx> +#include <svx/xoutbmp.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/lrspitem.hxx> +#include <svtools/htmlout.hxx> +#include <svtools/htmlkywd.hxx> +#include <svtools/htmltokn.h> +#include <comphelper/diagnose_ex.hxx> +#include <IDocumentContentOperations.hxx> +#include <SwAppletImpl.hxx> +#include <fmtornt.hxx> +#include <fmtfsize.hxx> +#include <fmtsrnd.hxx> +#include <fmtanchr.hxx> +#include <fmtcntnt.hxx> +#include <frmfmt.hxx> + +#include <svl/ownlist.hxx> +#include <unotools/mediadescriptor.hxx> +#include <unotools/streamwrap.hxx> +#include <pam.hxx> +#include <doc.hxx> +#include <swerror.h> +#include <ndole.hxx> +#include <docsh.hxx> +#include "swhtml.hxx" +#include "wrthtml.hxx" +#include "htmlfly.hxx" +#include "swcss1.hxx" +#include "htmlreqifreader.hxx" +#include <unoframe.hxx> +#include <com/sun/star/embed/XClassifiedObject.hpp> +#include <com/sun/star/embed/Aspects.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/io/XActiveDataStreamer.hpp> +#include <com/sun/star/embed/XEmbedPersist2.hpp> +#include <com/sun/star/lang/XInitialization.hpp> + +#include <comphelper/embeddedobjectcontainer.hxx> +#include <comphelper/classids.hxx> +#include <rtl/uri.hxx> +#include <comphelper/storagehelper.hxx> +#include <vcl/graphicfilter.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <comphelper/propertysequence.hxx> +#include <filter/msfilter/msoleexp.hxx> +#include <comphelper/fileurl.hxx> +#include <o3tl/safeint.hxx> +#include <osl/file.hxx> +#include <comphelper/propertyvalue.hxx> +#include <svtools/HtmlWriter.hxx> + +using namespace com::sun::star; + + +#define HTML_DFLT_EMBED_WIDTH (o3tl::toTwips(125, o3tl::Length::mm10)) +#define HTML_DFLT_EMBED_HEIGHT (o3tl::toTwips(125, o3tl::Length::mm10)) + +#define HTML_DFLT_APPLET_WIDTH (o3tl::toTwips(125, o3tl::Length::mm10)) +#define HTML_DFLT_APPLET_HEIGHT (o3tl::toTwips(125, o3tl::Length::mm10)) + + +const HtmlFrmOpts HTML_FRMOPTS_EMBED_ALL = + HtmlFrmOpts::Alt | + HtmlFrmOpts::Size | + HtmlFrmOpts::Name; +const HtmlFrmOpts HTML_FRMOPTS_EMBED_CNTNR = + HTML_FRMOPTS_EMBED_ALL | + HtmlFrmOpts::AbsSize; +const HtmlFrmOpts HTML_FRMOPTS_EMBED = + HTML_FRMOPTS_EMBED_ALL | + HtmlFrmOpts::Align | + HtmlFrmOpts::Space | + HtmlFrmOpts::BrClear | + HtmlFrmOpts::Name; +const HtmlFrmOpts HTML_FRMOPTS_HIDDEN_EMBED = + HtmlFrmOpts::Alt | + HtmlFrmOpts::Name; + +const HtmlFrmOpts HTML_FRMOPTS_APPLET_ALL = + HtmlFrmOpts::Alt | + HtmlFrmOpts::Size; +const HtmlFrmOpts HTML_FRMOPTS_APPLET_CNTNR = + HTML_FRMOPTS_APPLET_ALL | + HtmlFrmOpts::AbsSize; +const HtmlFrmOpts HTML_FRMOPTS_APPLET = + HTML_FRMOPTS_APPLET_ALL | + HtmlFrmOpts::Align | + HtmlFrmOpts::Space | + HtmlFrmOpts::BrClear; + +const HtmlFrmOpts HTML_FRMOPTS_IFRAME_ALL = + HtmlFrmOpts::Alt | + HtmlFrmOpts::Size; +const HtmlFrmOpts HTML_FRMOPTS_IFRAME_CNTNR = + HTML_FRMOPTS_IFRAME_ALL | + HtmlFrmOpts::AbsSize; +const HtmlFrmOpts HTML_FRMOPTS_IFRAME = + HTML_FRMOPTS_IFRAME_ALL | + HtmlFrmOpts::Align | + HtmlFrmOpts::Space | + HtmlFrmOpts::Border | + HtmlFrmOpts::BrClear; + +const HtmlFrmOpts HTML_FRMOPTS_OLE_CSS1 = + HtmlFrmOpts::SAlign | + HtmlFrmOpts::SSpace; + +namespace +{ +/** + * Calculates a filename for an image, provided the HTML file name, the image + * itself and a wanted extension. + */ +OUString lcl_CalculateFileName(const OUString* pOrigFileName, const Graphic& rGraphic, + std::u16string_view rExtension) +{ + OUString aFileName; + + if (pOrigFileName) + aFileName = *pOrigFileName; + INetURLObject aURL(aFileName); + OUString aName = aURL.getBase() + "_" + + aURL.getExtension() + "_" + + OUString::number(rGraphic.GetChecksum(), 16); + aURL.setBase(aName); + aURL.setExtension(rExtension); + aFileName = aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE); + + return aFileName; +} +} + +void SwHTMLParser::SetFixSize( const Size& rPixSize, + const Size& rTwipDfltSize, + bool bPercentWidth, bool bPercentHeight, + SvxCSS1PropertyInfo const & rCSS1PropInfo, + SfxItemSet& rFlyItemSet ) +{ + // convert absolute size values into Twip + sal_uInt8 nPercentWidth = 0, nPercentHeight = 0; + Size aTwipSz( bPercentWidth || USHRT_MAX==rPixSize.Width() ? 0 : rPixSize.Width(), + bPercentHeight || USHRT_MAX==rPixSize.Height() ? 0 : rPixSize.Height() ); + if( aTwipSz.Width() || aTwipSz.Height() ) + { + aTwipSz = o3tl::convert(aTwipSz, o3tl::Length::px, o3tl::Length::twip); + } + + // process width + if( SVX_CSS1_LTYPE_PERCENTAGE == rCSS1PropInfo.m_eWidthType ) + { + nPercentWidth = static_cast<sal_uInt8>(rCSS1PropInfo.m_nWidth); + aTwipSz.setWidth( rTwipDfltSize.Width() ); + } + else if( SVX_CSS1_LTYPE_TWIP== rCSS1PropInfo.m_eWidthType ) + { + aTwipSz.setWidth( rCSS1PropInfo.m_nWidth ); + } + else if( bPercentWidth && rPixSize.Width() ) + { + nPercentWidth = static_cast<sal_uInt8>(rPixSize.Width()); + if (nPercentWidth > 100 && nPercentWidth != SwFormatFrameSize::SYNCED) + nPercentWidth = 100; + + aTwipSz.setWidth( rTwipDfltSize.Width() ); + } + else if( USHRT_MAX==rPixSize.Width() ) + { + aTwipSz.setWidth( rTwipDfltSize.Width() ); + } + if( aTwipSz.Width() < MINFLY ) + { + aTwipSz.setWidth( MINFLY ); + } + + // process height + if( SVX_CSS1_LTYPE_PERCENTAGE == rCSS1PropInfo.m_eHeightType ) + { + nPercentHeight = static_cast<sal_uInt8>(rCSS1PropInfo.m_nHeight); + aTwipSz.setHeight( rTwipDfltSize.Height() ); + } + else if( SVX_CSS1_LTYPE_TWIP== rCSS1PropInfo.m_eHeightType ) + { + aTwipSz.setHeight( rCSS1PropInfo.m_nHeight ); + } + else if( bPercentHeight && rPixSize.Height() ) + { + nPercentHeight = static_cast<sal_uInt8>(rPixSize.Height()); + if (nPercentHeight > 100 && nPercentHeight != SwFormatFrameSize::SYNCED) + nPercentHeight = 100; + + aTwipSz.setHeight( rTwipDfltSize.Height() ); + } + else if( USHRT_MAX==rPixSize.Height() ) + { + aTwipSz.setHeight( rTwipDfltSize.Height() ); + } + if( aTwipSz.Height() < MINFLY ) + { + aTwipSz.setHeight( MINFLY ); + } + + // set size + SwFormatFrameSize aFrameSize( SwFrameSize::Fixed, aTwipSz.Width(), aTwipSz.Height() ); + aFrameSize.SetWidthPercent( nPercentWidth ); + aFrameSize.SetHeightPercent( nPercentHeight ); + rFlyItemSet.Put( aFrameSize ); +} + +void SwHTMLParser::SetSpace( const Size& rPixSpace, + SfxItemSet& rCSS1ItemSet, + SvxCSS1PropertyInfo& rCSS1PropInfo, + SfxItemSet& rFlyItemSet ) +{ + sal_Int32 nLeftSpace = 0, nRightSpace = 0; + sal_uInt16 nUpperSpace = 0, nLowerSpace = 0; + if( rPixSpace.Width() || rPixSpace.Height() ) + { + nLeftSpace = nRightSpace = o3tl::convert(rPixSpace.Width(), o3tl::Length::px, o3tl::Length::twip); + nUpperSpace = nLowerSpace = o3tl::convert(rPixSpace.Height(), o3tl::Length::px, o3tl::Length::twip); + } + + // set left/right margin + // note: parser never creates SvxLeftMarginItem! must be converted + if (const SvxTextLeftMarginItem *const pLeft = rCSS1ItemSet.GetItemIfSet(RES_MARGIN_TEXTLEFT)) + { + if( rCSS1PropInfo.m_bLeftMargin ) + { + // should be SvxLeftMarginItem... "cast" it + nLeftSpace = pLeft->GetTextLeft(); + rCSS1PropInfo.m_bLeftMargin = false; + } + rCSS1ItemSet.ClearItem(RES_MARGIN_TEXTLEFT); + } + if (const SvxRightMarginItem *const pRight = rCSS1ItemSet.GetItemIfSet(RES_MARGIN_RIGHT)) + { + if( rCSS1PropInfo.m_bRightMargin ) + { + nRightSpace = pRight->GetRight(); + rCSS1PropInfo.m_bRightMargin = false; + } + rCSS1ItemSet.ClearItem(RES_MARGIN_RIGHT); + } + if( nLeftSpace > 0 || nRightSpace > 0 ) + { + SvxLRSpaceItem aLRItem( RES_LR_SPACE ); + aLRItem.SetLeft( std::max<sal_Int32>(nLeftSpace, 0) ); + aLRItem.SetRight( std::max<sal_Int32>(nRightSpace, 0) ); + rFlyItemSet.Put( aLRItem ); + if( nLeftSpace ) + { + const SwFormatHoriOrient& rHoriOri = + rFlyItemSet.Get( RES_HORI_ORIENT ); + if( text::HoriOrientation::NONE == rHoriOri.GetHoriOrient() ) + { + SwFormatHoriOrient aHoriOri( rHoriOri ); + aHoriOri.SetPos( aHoriOri.GetPos() + nLeftSpace ); + rFlyItemSet.Put( aHoriOri ); + } + } + } + + // set top/bottom margin + if( const SvxULSpaceItem *pULItem = rCSS1ItemSet.GetItemIfSet( RES_UL_SPACE ) ) + { + // if applicable remove the first line indent + if( rCSS1PropInfo.m_bTopMargin ) + { + nUpperSpace = pULItem->GetUpper(); + rCSS1PropInfo.m_bTopMargin = false; + } + if( rCSS1PropInfo.m_bBottomMargin ) + { + nLowerSpace = pULItem->GetLower(); + rCSS1PropInfo.m_bBottomMargin = false; + } + rCSS1ItemSet.ClearItem( RES_UL_SPACE ); + } + if( !(nUpperSpace || nLowerSpace) ) + return; + + SvxULSpaceItem aULItem( RES_UL_SPACE ); + aULItem.SetUpper( nUpperSpace ); + aULItem.SetLower( nLowerSpace ); + rFlyItemSet.Put( aULItem ); + if( nUpperSpace ) + { + const SwFormatVertOrient& rVertOri = + rFlyItemSet.Get( RES_VERT_ORIENT ); + if( text::VertOrientation::NONE == rVertOri.GetVertOrient() ) + { + SwFormatVertOrient aVertOri( rVertOri ); + aVertOri.SetPos( aVertOri.GetPos() + nUpperSpace ); + rFlyItemSet.Put( aVertOri ); + } + } +} + +OUString SwHTMLParser::StripQueryFromPath(std::u16string_view rBase, const OUString& rPath) +{ + if (!comphelper::isFileUrl(rBase)) + return rPath; + + sal_Int32 nIndex = rPath.indexOf('?'); + + if (nIndex != -1) + return rPath.copy(0, nIndex); + + return rPath; +} + +bool SwHTMLParser::InsertEmbed() +{ + OUString aURL, aType, aName, aAlt, aId, aStyle, aClass; + OUString aData; + Size aSize( USHRT_MAX, USHRT_MAX ); + Size aSpace( USHRT_MAX, USHRT_MAX ); + bool bPercentWidth = false, bPercentHeight = false, bHidden = false; + sal_Int16 eVertOri = text::VertOrientation::NONE; + sal_Int16 eHoriOri = text::HoriOrientation::NONE; + SvCommandList aCmdLst; + const HTMLOptions& rHTMLOptions = GetOptions(); + + // The options are read forwards, because the plug-ins expect them in this + // order. Still only the first value of an option may be regarded. + for (const auto & rOption : rHTMLOptions) + { + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::NAME: + aName = rOption.GetString(); + break; + case HtmlOptionId::SRC: + if( aURL.isEmpty() ) + aURL = rOption.GetString(); + break; + case HtmlOptionId::ALT: + aAlt = rOption.GetString(); + break; + case HtmlOptionId::TYPE: + if( aType.isEmpty() ) + aType = rOption.GetString(); + break; + case HtmlOptionId::ALIGN: + if( eVertOri==text::VertOrientation::NONE && eHoriOri==text::HoriOrientation::NONE ) + { + eVertOri = rOption.GetEnum( aHTMLImgVAlignTable, eVertOri ); + eHoriOri = rOption.GetEnum( aHTMLImgHAlignTable, eHoriOri ); + } + break; + case HtmlOptionId::WIDTH: + if( USHRT_MAX==aSize.Width() ) + { + bPercentWidth = (rOption.GetString().indexOf('%') != -1); + aSize.setWidth( static_cast<tools::Long>(rOption.GetNumber()) ); + } + break; + case HtmlOptionId::HEIGHT: + if( USHRT_MAX==aSize.Height() ) + { + bPercentHeight = (rOption.GetString().indexOf('%') != -1); + aSize.setHeight( static_cast<tools::Long>(rOption.GetNumber()) ); + } + break; + case HtmlOptionId::HSPACE: + if( USHRT_MAX==aSpace.Width() ) + aSpace.setWidth( static_cast<tools::Long>(rOption.GetNumber()) ); + break; + case HtmlOptionId::VSPACE: + if( USHRT_MAX==aSpace.Height() ) + aSpace.setHeight( static_cast<tools::Long>(rOption.GetNumber()) ); + break; + case HtmlOptionId::DATA: + if (m_bXHTML && aURL.isEmpty()) + aData = rOption.GetString(); + break; + case HtmlOptionId::UNKNOWN: + if (rOption.GetTokenString().equalsIgnoreAsciiCase( + OOO_STRING_SW_HTML_O_Hidden)) + { + bHidden = !rOption.GetString().equalsIgnoreAsciiCase( + "FALSE"); + } + break; + default: break; + } + + // All parameters are passed to the plug-in. + aCmdLst.Append( rOption.GetTokenString(), rOption.GetString() ); + } + + static const std::set<std::u16string_view> vAllowlist = { + u"image/png", + u"image/gif", + u"image/x-MS-bmp", + u"image/jpeg", + u"image/x-wmf", + u"image/svg+xml", + u"image/tiff", + u"image/x-emf", + u"image/bmp", + u"image/tif", + u"image/wmf", + }; + + if (vAllowlist.find(aType) != vAllowlist.end() && m_aEmbeds.empty()) + { + // Toplevel <object> for an image format -> that's an image, not an OLE object. + m_aEmbeds.push(nullptr); + return false; + } + + SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + if( HasStyleOptions( aStyle, aId, aClass ) ) + ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo ); + + // Convert the default values (except height/width, which is done by SetFrameSize()) + if( eVertOri==text::VertOrientation::NONE && eHoriOri==text::HoriOrientation::NONE ) + eVertOri = text::VertOrientation::TOP; + if( USHRT_MAX==aSpace.Width() ) + aSpace.setWidth( 0 ); + if( USHRT_MAX==aSpace.Height() ) + aSpace.setHeight( 0 ); + if( bHidden ) + { + // Size (0,0) will be changed to (MINFLY, MINFLY) in SetFrameSize() + aSize.setWidth( 0 ); aSize.setHeight( 0 ); + aSpace.setWidth( 0 ); aSpace.setHeight( 0 ); + bPercentWidth = bPercentHeight = false; + } + + // prepare the URL + INetURLObject aURLObj; + bool bHasURL = !aURL.isEmpty() && + aURLObj.SetURL( + URIHelper::SmartRel2Abs( + INetURLObject(m_sBaseURL), aURL, + URIHelper::GetMaybeFileHdl()) ); + bool bHasData = !aData.isEmpty(); + + try + { + // Strip query and everything after that for file:// URLs, browsers do + // the same. + aURLObj.SetURL(rtl::Uri::convertRelToAbs( + m_sBaseURL, SwHTMLParser::StripQueryFromPath(m_sBaseURL, aData))); + } + catch (const rtl::MalformedUriException& /*rException*/) + { + bHasData = false; + } + + // do not insert plugin if it has neither URL nor type + bool bHasType = !aType.isEmpty(); + if( !bHasURL && !bHasType && !bHasData ) + return true; + + if (!m_aEmbeds.empty()) + { + // Nested XHTML <object> element: points to replacement graphic. + SwOLENode* pOLENode = m_aEmbeds.top(); + if (!pOLENode) + { + // <object> is mapped to an image -> ignore replacement graphic. + return true; + } + + svt::EmbeddedObjectRef& rObj = pOLENode->GetOLEObj().GetObject(); + Graphic aGraphic; + if (GraphicFilter::GetGraphicFilter().ImportGraphic(aGraphic, aURLObj) != ERRCODE_NONE) + return true; + + rObj.SetGraphic(aGraphic, aType); + + // Set the size of the OLE frame to the size of the graphic. + SwFrameFormat* pFormat = pOLENode->GetFlyFormat(); + if (!pFormat) + return true; + SwAttrSet aAttrSet(pFormat->GetAttrSet()); + aAttrSet.ClearItem(RES_CNTNT); + Size aDefaultTwipSize(o3tl::convert(aGraphic.GetSizePixel(), o3tl::Length::px, o3tl::Length::twip)); + + if (aSize.Width() == USHRT_MAX && bPercentHeight) + { + // Height is relative, width is not set: keep aspect ratio. + aSize.setWidth(SwFormatFrameSize::SYNCED); + bPercentWidth = true; + } + if (aSize.Height() == USHRT_MAX && bPercentWidth) + { + // Width is relative, height is not set: keep aspect ratio. + aSize.setHeight(SwFormatFrameSize::SYNCED); + bPercentHeight = true; + } + + SetFixSize(aSize, aDefaultTwipSize, bPercentWidth, bPercentHeight, aPropInfo, aAttrSet); + pOLENode->GetDoc().SetFlyFrameAttr(*pFormat, aAttrSet); + return true; + } + + // create the plug-in + comphelper::EmbeddedObjectContainer aCnt; + OUString aObjName; + uno::Reference < embed::XEmbeddedObject > xObj; + if (!bHasData) + { + xObj = aCnt.CreateEmbeddedObject( SvGlobalName( SO3_PLUGIN_CLASSID ).GetByteSequence(), aObjName ); + if ( svt::EmbeddedObjectRef::TryRunningState( xObj ) ) + { + uno::Reference < beans::XPropertySet > xSet( xObj->getComponent(), uno::UNO_QUERY ); + if ( xSet.is() ) + { + if( bHasURL ) + xSet->setPropertyValue("PluginURL", uno::Any( aURL ) ); + if( bHasType ) + xSet->setPropertyValue("PluginMimeType", uno::Any( aType ) ); + + uno::Sequence < beans::PropertyValue > aProps; + aCmdLst.FillSequence( aProps ); + xSet->setPropertyValue("PluginCommands", uno::Any( aProps ) ); + + } + } + } + else if (SwDocShell* pDocSh = m_xDoc->GetDocShell()) + { + // Has non-empty data attribute in XHTML: map that to an OLE object. + uno::Reference<embed::XStorage> xStorage = pDocSh->GetStorage(); + aCnt.SwitchPersistence(xStorage); + aObjName = aCnt.CreateUniqueObjectName(); + { + OUString aEmbedURL = aURLObj.GetMainURL(INetURLObject::DecodeMechanism::NONE); + SvFileStream aFileStream(aEmbedURL, StreamMode::READ); + uno::Reference<io::XInputStream> xInStream; + SvMemoryStream aMemoryStream; + + // Allow any MIME type that starts with magic, unless a set of allowed types are + // specified. + auto it = m_aAllowedRTFOLEMimeTypes.find(aType); + if (m_aAllowedRTFOLEMimeTypes.empty() || it != m_aAllowedRTFOLEMimeTypes.end()) + { + OString aMagic("{\\object"_ostr); + OString aHeader(read_uInt8s_ToOString(aFileStream, aMagic.getLength())); + aFileStream.Seek(0); + if (aHeader == aMagic) + { + // OLE2 wrapped in RTF: either own format or real OLE2 embedding. + bool bOwnFormat = false; + if (SwReqIfReader::ExtractOleFromRtf(aFileStream, aMemoryStream, bOwnFormat)) + { + xInStream.set(new utl::OStreamWrapper(aMemoryStream)); + } + + if (bOwnFormat) + { + uno::Sequence<beans::PropertyValue> aMedium = comphelper::InitPropertySequence( + { { "InputStream", uno::Any(xInStream) }, + { "URL", uno::Any(OUString("private:stream")) }, + { "DocumentBaseURL", uno::Any(m_sBaseURL) } }); + xObj = aCnt.InsertEmbeddedObject(aMedium, aName, &m_sBaseURL); + } + else + { + // The type is now an OLE2 container, not the original XHTML type. + aType = "application/vnd.sun.star.oleobject"; + } + } + } + + if (!xInStream.is()) + { + // Object data is neither OLE2 in RTF, nor an image. Then map this to an URL that + // will be set on the inner image. + m_aEmbedURL = aEmbedURL; + // Signal success, so the outer object won't fall back to the image handler. + return true; + } + + if (!xObj.is()) + { + uno::Reference<io::XStream> xOutStream + = xStorage->openStreamElement(aObjName, embed::ElementModes::READWRITE); + if (aFileStream.IsOpen()) + comphelper::OStorageHelper::CopyInputToOutput(xInStream, + xOutStream->getOutputStream()); + + if (!aType.isEmpty()) + { + // Set media type of the native data. + uno::Reference<beans::XPropertySet> xOutStreamProps(xOutStream, uno::UNO_QUERY); + if (xOutStreamProps.is()) + xOutStreamProps->setPropertyValue("MediaType", uno::Any(aType)); + } + } + xObj = aCnt.GetEmbeddedObject(aObjName); + } + } + + SfxItemSetFixed<RES_FRMATR_BEGIN, RES_FRMATR_END-1> aFrameSet( m_xDoc->GetAttrPool() ); + if( !IsNewDoc() ) + Reader::ResetFrameFormatAttrs( aFrameSet ); + + // set the anchor + if( !bHidden ) + { + SetAnchorAndAdjustment( eVertOri, eHoriOri, aPropInfo, aFrameSet ); + } + else + { + SwFormatAnchor aAnchor( RndStdIds::FLY_AT_PARA ); + aAnchor.SetAnchor( m_pPam->GetPoint() ); + aFrameSet.Put( aAnchor ); + aFrameSet.Put( SwFormatHoriOrient( 0, text::HoriOrientation::LEFT, text::RelOrientation::FRAME) ); + aFrameSet.Put( SwFormatSurround( css::text::WrapTextMode_THROUGH ) ); + aFrameSet.Put( SwFormatVertOrient( 0, text::VertOrientation::TOP, text::RelOrientation::PRINT_AREA ) ); + } + + // and the size of the frame + Size aDfltSz( HTML_DFLT_EMBED_WIDTH, HTML_DFLT_EMBED_HEIGHT ); + SetFixSize( aSize, aDfltSz, bPercentWidth, bPercentHeight, aPropInfo, aFrameSet ); + SetSpace( aSpace, aItemSet, aPropInfo, aFrameSet ); + + // and insert into the document + uno::Reference<lang::XInitialization> xObjInitialization(xObj, uno::UNO_QUERY); + if (xObjInitialization.is()) + { + // Request that the native data of the embedded object is not modified + // during parsing. + uno::Sequence<beans::PropertyValue> aValues{ comphelper::makePropertyValue("StreamReadOnly", + true) }; + uno::Sequence<uno::Any> aArguments{ uno::Any(aValues) }; + xObjInitialization->initialize(aArguments); + } + SwFrameFormat* pFlyFormat = + m_xDoc->getIDocumentContentOperations().InsertEmbObject(*m_pPam, + ::svt::EmbeddedObjectRef(xObj, embed::Aspects::MSOLE_CONTENT), + &aFrameSet); + if (xObjInitialization.is()) + { + uno::Sequence<beans::PropertyValue> aValues{ comphelper::makePropertyValue("StreamReadOnly", + false) }; + uno::Sequence<uno::Any> aArguments{ uno::Any(aValues) }; + xObjInitialization->initialize(aArguments); + } + + // set name at FrameFormat + if( !aName.isEmpty() ) + pFlyFormat->SetFormatName( aName ); + + // set the alternative text + SwNoTextNode *pNoTextNd = + m_xDoc->GetNodes()[ pFlyFormat->GetContent().GetContentIdx() + ->GetIndex()+1 ]->GetNoTextNode(); + pNoTextNd->SetTitle( aAlt ); + + // if applicable create frames and register auto-bound frames + if( !bHidden ) + { + // HIDDEN plug-ins should stay paragraph-bound. Since RegisterFlyFrame() + // will change paragraph-bound frames with wrap-through into a + // character-bound frame, here we must create the frames by hand. + RegisterFlyFrame( pFlyFormat ); + } + + if (!bHasData) + return true; + + SwOLENode* pOLENode = pNoTextNd->GetOLENode(); + if (!pOLENode) + return true; + + m_aEmbeds.push(pOLENode); + + return true; +} + +#if HAVE_FEATURE_JAVA +void SwHTMLParser::NewObject() +{ + OUString aClassID; + OUString aStandBy, aId, aStyle, aClass; + Size aSize( USHRT_MAX, USHRT_MAX ); + Size aSpace( 0, 0 ); + sal_Int16 eVertOri = text::VertOrientation::TOP; + sal_Int16 eHoriOri = text::HoriOrientation::NONE; + + bool bPercentWidth = false, bPercentHeight = false, + bDeclare = false; + // create a new Command list + m_pAppletImpl.reset(new SwApplet_Impl( m_xDoc->GetAttrPool() )); + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::DECLARE: + bDeclare = true; + break; + case HtmlOptionId::CLASSID: + aClassID = rOption.GetString(); + break; + case HtmlOptionId::CODEBASE: + break; + case HtmlOptionId::DATA: + break; + case HtmlOptionId::TYPE: + break; + case HtmlOptionId::CODETYPE: + break; + case HtmlOptionId::ARCHIVE: + case HtmlOptionId::UNKNOWN: + break; + case HtmlOptionId::STANDBY: + aStandBy = rOption.GetString(); + break; + case HtmlOptionId::WIDTH: + bPercentWidth = (rOption.GetString().indexOf('%') != -1); + aSize.setWidth( static_cast<tools::Long>(rOption.GetNumber()) ); + break; + case HtmlOptionId::HEIGHT: + bPercentHeight = (rOption.GetString().indexOf('%') != -1); + aSize.setHeight( static_cast<tools::Long>(rOption.GetNumber()) ); + break; + case HtmlOptionId::ALIGN: + eVertOri = rOption.GetEnum( aHTMLImgVAlignTable, eVertOri ); + eHoriOri = rOption.GetEnum( aHTMLImgHAlignTable, eHoriOri ); + break; + case HtmlOptionId::USEMAP: + break; + case HtmlOptionId::NAME: + break; + case HtmlOptionId::HSPACE: + aSpace.setWidth( static_cast<tools::Long>(rOption.GetNumber()) ); + break; + case HtmlOptionId::VSPACE: + aSpace.setHeight( static_cast<tools::Long>(rOption.GetNumber()) ); + break; + case HtmlOptionId::BORDER: + break; + + case HtmlOptionId::SDONCLICK: + case HtmlOptionId::ONCLICK: + case HtmlOptionId::SDONMOUSEOVER: + case HtmlOptionId::ONMOUSEOVER: + case HtmlOptionId::SDONMOUSEOUT: + case HtmlOptionId::ONMOUSEOUT: + break; + default: break; + } + // All parameters are passed to the applet. + m_pAppletImpl->AppendParam( rOption.GetTokenString(), + rOption.GetString() ); + + } + + // Objects that are declared only are not evaluated. Moreover, only + // Java applets are supported. + bool bIsApplet = false; + + if( !bDeclare && aClassID.getLength() == 42 && + aClassID.startsWith("clsid:") ) + { + aClassID = aClassID.copy(6); + SvGlobalName aCID; + if( aCID.MakeId( aClassID ) ) + { + SvGlobalName aJavaCID( 0x8AD9C840UL, 0x044EU, 0x11D1U, 0xB3U, 0xE9U, + 0x00U, 0x80U, 0x5FU, 0x49U, 0x9DU, 0x93U ); + + bIsApplet = aJavaCID == aCID; + } + } + + if( !bIsApplet ) + { + m_pAppletImpl.reset(); + return; + } + + m_pAppletImpl->SetAltText( aStandBy ); + + SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + if( HasStyleOptions( aStyle, aId, aClass ) ) + ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo ); + + SfxItemSet& rFrameSet = m_pAppletImpl->GetItemSet(); + if( !IsNewDoc() ) + Reader::ResetFrameFormatAttrs( rFrameSet ); + + // set the anchor and the adjustment + SetAnchorAndAdjustment( eVertOri, eHoriOri, aPropInfo, rFrameSet ); + + // and still the size of the frame + Size aDfltSz( HTML_DFLT_APPLET_WIDTH, HTML_DFLT_APPLET_HEIGHT ); + SetFixSize( aSize, aDfltSz, bPercentWidth, bPercentHeight, aPropInfo, rFrameSet ); + SetSpace( aSpace, aItemSet, aPropInfo, rFrameSet ); +} +#endif + +void SwHTMLParser::EndObject() +{ +#if HAVE_FEATURE_JAVA + if( !m_pAppletImpl ) + return; + if( !m_pAppletImpl->CreateApplet( m_sBaseURL ) ) + return; + + m_pAppletImpl->FinishApplet(); + + // and insert into the document + SwFrameFormat* pFlyFormat = + m_xDoc->getIDocumentContentOperations().InsertEmbObject(*m_pPam, + ::svt::EmbeddedObjectRef( m_pAppletImpl->GetApplet(), embed::Aspects::MSOLE_CONTENT ), + &m_pAppletImpl->GetItemSet() ); + + // set the alternative name + SwNoTextNode *pNoTextNd = + m_xDoc->GetNodes()[ pFlyFormat->GetContent().GetContentIdx() + ->GetIndex()+1 ]->GetNoTextNode(); + pNoTextNd->SetTitle( m_pAppletImpl->GetAltText() ); + + // if applicable create frames and register auto-bound frames + RegisterFlyFrame( pFlyFormat ); + + m_pAppletImpl.reset(); +#else + (void) this; // Silence loplugin:staticmethods +#endif +} + +#if HAVE_FEATURE_JAVA +void SwHTMLParser::InsertApplet() +{ + OUString aCodeBase, aCode, aName, aAlt, aId, aStyle, aClass; + Size aSize( USHRT_MAX, USHRT_MAX ); + Size aSpace( 0, 0 ); + bool bPercentWidth = false, bPercentHeight = false, bMayScript = false; + sal_Int16 eVertOri = text::VertOrientation::TOP; + sal_Int16 eHoriOri = text::HoriOrientation::NONE; + + // create a new Command list + m_pAppletImpl.reset(new SwApplet_Impl( m_xDoc->GetAttrPool() )); + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::CODEBASE: + aCodeBase = rOption.GetString(); + break; + case HtmlOptionId::CODE: + aCode = rOption.GetString(); + break; + case HtmlOptionId::NAME: + aName = rOption.GetString(); + break; + case HtmlOptionId::ALT: + aAlt = rOption.GetString(); + break; + case HtmlOptionId::ALIGN: + eVertOri = rOption.GetEnum( aHTMLImgVAlignTable, eVertOri ); + eHoriOri = rOption.GetEnum( aHTMLImgHAlignTable, eHoriOri ); + break; + case HtmlOptionId::WIDTH: + bPercentWidth = (rOption.GetString().indexOf('%') != -1); + aSize.setWidth( static_cast<tools::Long>(rOption.GetNumber()) ); + break; + case HtmlOptionId::HEIGHT: + bPercentHeight = (rOption.GetString().indexOf('%') != -1); + aSize.setHeight( static_cast<tools::Long>(rOption.GetNumber()) ); + break; + case HtmlOptionId::HSPACE: + aSpace.setWidth( static_cast<tools::Long>(rOption.GetNumber()) ); + break; + case HtmlOptionId::VSPACE: + aSpace.setHeight( static_cast<tools::Long>(rOption.GetNumber()) ); + break; + case HtmlOptionId::MAYSCRIPT: + bMayScript = true; + break; + default: break; + } + + // All parameters are passed to the applet. + m_pAppletImpl->AppendParam( rOption.GetTokenString(), + rOption.GetString() ); + } + + if( aCode.isEmpty() ) + { + m_pAppletImpl.reset(); + return; + } + + if ( !aCodeBase.isEmpty() ) + aCodeBase = INetURLObject::GetAbsURL( m_sBaseURL, aCodeBase ); + m_pAppletImpl->CreateApplet( aCode, aName, bMayScript, aCodeBase, m_sBaseURL );//, aAlt ); + m_pAppletImpl->SetAltText( aAlt ); + + SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + if( HasStyleOptions( aStyle, aId, aClass ) ) + ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo ); + + SfxItemSet& rFrameSet = m_pAppletImpl->GetItemSet(); + if( !IsNewDoc() ) + Reader::ResetFrameFormatAttrs( rFrameSet ); + + // set the anchor and the adjustment + SetAnchorAndAdjustment( eVertOri, eHoriOri, aPropInfo, rFrameSet ); + + // and still the size or the frame + Size aDfltSz( HTML_DFLT_APPLET_WIDTH, HTML_DFLT_APPLET_HEIGHT ); + SetFixSize( aSize, aDfltSz, bPercentWidth, bPercentHeight, aPropInfo, rFrameSet ); + SetSpace( aSpace, aItemSet, aPropInfo, rFrameSet ); +} +#endif + +void SwHTMLParser::EndApplet() +{ +#if HAVE_FEATURE_JAVA + if( !m_pAppletImpl ) + return; + + m_pAppletImpl->FinishApplet(); + + // and insert into the document + SwFrameFormat* pFlyFormat = + m_xDoc->getIDocumentContentOperations().InsertEmbObject(*m_pPam, + ::svt::EmbeddedObjectRef( m_pAppletImpl->GetApplet(), embed::Aspects::MSOLE_CONTENT ), + &m_pAppletImpl->GetItemSet()); + + // set the alternative name + SwNoTextNode *pNoTextNd = + m_xDoc->GetNodes()[ pFlyFormat->GetContent().GetContentIdx() + ->GetIndex()+1 ]->GetNoTextNode(); + pNoTextNd->SetTitle( m_pAppletImpl->GetAltText() ); + + // if applicable create frames and register auto-bound frames + RegisterFlyFrame( pFlyFormat ); + + m_pAppletImpl.reset(); +#else + (void) this; +#endif +} + +void SwHTMLParser::InsertParam() +{ +#if HAVE_FEATURE_JAVA + if( !m_pAppletImpl ) + return; + + OUString aName, aValue; + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::NAME: + aName = rOption.GetString(); + break; + case HtmlOptionId::VALUE: + aValue = rOption.GetString(); + break; + default: break; + } + } + + if( aName.isEmpty() ) + return; + + m_pAppletImpl->AppendParam( aName, aValue ); +#else + (void) this; +#endif +} + +void SwHTMLParser::InsertFloatingFrame() +{ + OUString aAlt, aId, aStyle, aClass; + Size aSize( USHRT_MAX, USHRT_MAX ); + Size aSpace( 0, 0 ); + bool bPercentWidth = false, bPercentHeight = false; + sal_Int16 eVertOri = text::VertOrientation::TOP; + sal_Int16 eHoriOri = text::HoriOrientation::NONE; + + const HTMLOptions& rHTMLOptions = GetOptions(); + + // First fetch the options of the Writer-Frame-Format + for (const auto & rOption : rHTMLOptions) + { + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::ALT: + aAlt = rOption.GetString(); + break; + case HtmlOptionId::ALIGN: + eVertOri = rOption.GetEnum( aHTMLImgVAlignTable, eVertOri ); + eHoriOri = rOption.GetEnum( aHTMLImgHAlignTable, eHoriOri ); + break; + case HtmlOptionId::WIDTH: + bPercentWidth = (rOption.GetString().indexOf('%') != -1); + aSize.setWidth( static_cast<tools::Long>(rOption.GetNumber()) ); + break; + case HtmlOptionId::HEIGHT: + bPercentHeight = (rOption.GetString().indexOf('%') != -1); + aSize.setHeight( static_cast<tools::Long>(rOption.GetNumber()) ); + break; + case HtmlOptionId::HSPACE: + aSpace.setWidth( static_cast<tools::Long>(rOption.GetNumber()) ); + break; + case HtmlOptionId::VSPACE: + aSpace.setHeight( static_cast<tools::Long>(rOption.GetNumber()) ); + break; + default: break; + } + } + + // and now the ones for the SfxFrame + SfxFrameDescriptor aFrameDesc; + + SfxFrameHTMLParser::ParseFrameOptions( &aFrameDesc, rHTMLOptions, m_sBaseURL ); + + // create a floating frame + comphelper::EmbeddedObjectContainer aCnt; + OUString aObjName; + uno::Reference < embed::XEmbeddedObject > xObj = aCnt.CreateEmbeddedObject( SvGlobalName( SO3_IFRAME_CLASSID ).GetByteSequence(), aObjName ); + + try + { + // TODO/MBA: testing + if ( svt::EmbeddedObjectRef::TryRunningState( xObj ) ) + { + uno::Reference < beans::XPropertySet > xSet( xObj->getComponent(), uno::UNO_QUERY ); + if ( xSet.is() ) + { + const OUString& aName = aFrameDesc.GetName(); + ScrollingMode eScroll = aFrameDesc.GetScrollingMode(); + bool bHasBorder = aFrameDesc.HasFrameBorder(); + Size aMargin = aFrameDesc.GetMargin(); + + OUString sHRef = aFrameDesc.GetURL().GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + if (INetURLObject(sHRef).IsExoticProtocol()) + NotifyMacroEventRead(); + + xSet->setPropertyValue("FrameURL", uno::Any( sHRef ) ); + xSet->setPropertyValue("FrameName", uno::Any( aName ) ); + + if ( eScroll == ScrollingMode::Auto ) + xSet->setPropertyValue("FrameIsAutoScroll", + uno::Any( true ) ); + else + xSet->setPropertyValue("FrameIsScrollingMode", + uno::Any( eScroll == ScrollingMode::Yes ) ); + + xSet->setPropertyValue("FrameIsBorder", + uno::Any( bHasBorder ) ); + + xSet->setPropertyValue("FrameMarginWidth", + uno::Any( sal_Int32( aMargin.Width() ) ) ); + + xSet->setPropertyValue("FrameMarginHeight", + uno::Any( sal_Int32( aMargin.Height() ) ) ); + } + } + } + catch ( uno::Exception& ) + { + } + + SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + if( HasStyleOptions( aStyle, aId, aClass ) ) + ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo ); + + // fetch the ItemSet + SfxItemSetFixed<RES_FRMATR_BEGIN, RES_FRMATR_END-1> aFrameSet( m_xDoc->GetAttrPool() ); + if( !IsNewDoc() ) + Reader::ResetFrameFormatAttrs( aFrameSet ); + + // set the anchor and the adjustment + SetAnchorAndAdjustment( eVertOri, eHoriOri, aPropInfo, aFrameSet ); + + // and still the size of the frame + Size aDfltSz( HTML_DFLT_APPLET_WIDTH, HTML_DFLT_APPLET_HEIGHT ); + SetFixSize( aSize, aDfltSz, bPercentWidth, bPercentHeight, aPropInfo, aFrameSet ); + SetSpace( aSpace, aItemSet, aPropInfo, aFrameSet ); + + // and insert into the document + SwFrameFormat* pFlyFormat = + m_xDoc->getIDocumentContentOperations().InsertEmbObject(*m_pPam, + ::svt::EmbeddedObjectRef(xObj, embed::Aspects::MSOLE_CONTENT), + &aFrameSet); + + // set the alternative name + SwNoTextNode *pNoTextNd = + m_xDoc->GetNodes()[ pFlyFormat->GetContent().GetContentIdx() + ->GetIndex()+1 ]->GetNoTextNode(); + pNoTextNd->SetTitle( aAlt ); + + // if applicable create frames and register auto-bound frames + RegisterFlyFrame( pFlyFormat ); + + m_bInFloatingFrame = true; + + ++m_nFloatingFrames; +} + +SwHTMLFrameType SwHTMLWriter::GuessOLENodeFrameType( const SwNode& rNode ) +{ + SwHTMLFrameType eType = HTML_FRMTYPE_OLE; + + SwOLENode* pOLENode = const_cast<SwOLENode*>(rNode.GetOLENode()); + assert(pOLENode && "must exist"); + SwOLEObj& rObj = pOLENode->GetOLEObj(); + + uno::Reference < embed::XClassifiedObject > xClass = rObj.GetOleRef(); + SvGlobalName aClass( xClass->getClassID() ); + if( aClass == SvGlobalName( SO3_PLUGIN_CLASSID ) ) + { + eType = HTML_FRMTYPE_PLUGIN; + } + else if( aClass == SvGlobalName( SO3_IFRAME_CLASSID ) ) + { + eType = HTML_FRMTYPE_IFRAME; + } +#if HAVE_FEATURE_JAVA + else if( aClass == SvGlobalName( SO3_APPLET_CLASSID ) ) + { + eType = HTML_FRMTYPE_APPLET; + } +#endif + + return eType; +} + +SwHTMLWriter& OutHTML_FrameFormatOLENode( SwHTMLWriter& rWrt, const SwFrameFormat& rFrameFormat, + bool bInCntnr ) +{ + const SwFormatContent& rFlyContent = rFrameFormat.GetContent(); + SwNodeOffset nStt = rFlyContent.GetContentIdx()->GetIndex()+1; + SwOLENode *pOLENd = rWrt.m_pDoc->GetNodes()[ nStt ]->GetOLENode(); + + OSL_ENSURE( pOLENd, "OLE-Node expected" ); + if( !pOLENd ) + return rWrt; + + SwOLEObj &rObj = pOLENd->GetOLEObj(); + + uno::Reference < embed::XEmbeddedObject > xObj( rObj.GetOleRef() ); + if ( !svt::EmbeddedObjectRef::TryRunningState( xObj ) ) + return rWrt; + + uno::Reference < beans::XPropertySet > xSet( xObj->getComponent(), uno::UNO_QUERY ); + bool bHiddenEmbed = false; + + if( !xSet.is() ) + { + OSL_FAIL("Unknown Object" ); + return rWrt; + } + + HtmlFrmOpts nFrameOpts; + + // if possible output a line break before the "object" + if (rWrt.IsLFPossible()) + rWrt.OutNewLine( true ); + + if( !rFrameFormat.GetName().isEmpty() ) + rWrt.OutImplicitMark( rFrameFormat.GetName(), + "ole" ); + uno::Any aAny; + SvGlobalName aGlobName( xObj->getClassID() ); + OStringBuffer sOut("<"); + if( aGlobName == SvGlobalName( SO3_PLUGIN_CLASSID ) ) + { + // first the plug-in specifics + sOut.append(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_embed); + + OUString aStr; + OUString aURL; + aAny = xSet->getPropertyValue("PluginURL"); + if( (aAny >>= aStr) && !aStr.isEmpty() ) + { + aURL = URIHelper::simpleNormalizedMakeRelative( rWrt.GetBaseURL(), + aStr); + } + + if( !aURL.isEmpty() ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_src "=\""); + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( rWrt.Strm(), aURL ); + sOut.append('\"'); + } + + OUString aType; + aAny = xSet->getPropertyValue("PluginMimeType"); + if( (aAny >>= aType) && !aType.isEmpty() ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_type "=\""); + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( rWrt.Strm(), aType ); + sOut.append('\"'); + } + + if ((RndStdIds::FLY_AT_PARA == rFrameFormat.GetAnchor().GetAnchorId()) && + css::text::WrapTextMode_THROUGH == rFrameFormat.GetSurround().GetSurround() ) + { + // A HIDDEN plug-in + sOut.append(" " OOO_STRING_SW_HTML_O_Hidden); + nFrameOpts = HTML_FRMOPTS_HIDDEN_EMBED; + bHiddenEmbed = true; + } + else + { + nFrameOpts = bInCntnr ? HTML_FRMOPTS_EMBED_CNTNR + : HTML_FRMOPTS_EMBED; + } + } + else if( aGlobName == SvGlobalName( SO3_APPLET_CLASSID ) ) + { + // or the applet specifics + + sOut.append(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_applet); + + // CODEBASE + OUString aCd; + aAny = xSet->getPropertyValue("AppletCodeBase"); + if( (aAny >>= aCd) && !aCd.isEmpty() ) + { + OUString sCodeBase( URIHelper::simpleNormalizedMakeRelative(rWrt.GetBaseURL(), aCd) ); + if( !sCodeBase.isEmpty() ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_codebase "=\""); + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( rWrt.Strm(), sCodeBase ); + sOut.append('\"'); + } + } + + // CODE + OUString aClass; + aAny = xSet->getPropertyValue("AppletCode"); + aAny >>= aClass; + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_code "=\""); + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( rWrt.Strm(), aClass ); + sOut.append('\"'); + + // NAME + OUString aAppletName; + aAny = xSet->getPropertyValue("AppletName"); + aAny >>= aAppletName; + if( !aAppletName.isEmpty() ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_name "=\""); + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( rWrt.Strm(), aAppletName ); + sOut.append('\"'); + } + + bool bScript = false; + aAny = xSet->getPropertyValue("AppletIsScript"); + aAny >>= bScript; + if( bScript ) + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_mayscript); + + nFrameOpts = bInCntnr ? HTML_FRMOPTS_APPLET_CNTNR + : HTML_FRMOPTS_APPLET; + } + else + { + // or the Floating-Frame specifics + + sOut.append(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_iframe); + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + + SfxFrameHTMLWriter::Out_FrameDescriptor( rWrt.Strm(), rWrt.GetBaseURL(), + xSet ); + + nFrameOpts = bInCntnr ? HTML_FRMOPTS_IFRAME_CNTNR + : HTML_FRMOPTS_IFRAME; + } + + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + + // ALT, WIDTH, HEIGHT, HSPACE, VSPACE, ALIGN + if( rWrt.IsHTMLMode( HTMLMODE_ABS_POS_FLY ) && !bHiddenEmbed ) + nFrameOpts |= HTML_FRMOPTS_OLE_CSS1; + OString aEndTags = rWrt.OutFrameFormatOptions( rFrameFormat, pOLENd->GetTitle(), nFrameOpts ); + if( rWrt.IsHTMLMode( HTMLMODE_ABS_POS_FLY ) && !bHiddenEmbed ) + rWrt.OutCSS1_FrameFormatOptions( rFrameFormat, nFrameOpts ); + + if( aGlobName == SvGlobalName( SO3_APPLET_CLASSID ) ) + { + // output the parameters of applets as separate tags + // and write a </APPLET> + + uno::Sequence < beans::PropertyValue > aProps; + aAny = xSet->getPropertyValue("AppletCommands"); + aAny >>= aProps; + + SvCommandList aCommands; + aCommands.FillFromSequence( aProps ); + std::vector<sal_uLong> aParams; + size_t i = aCommands.size(); + while( i > 0 ) + { + const SvCommand& rCommand = aCommands[ --i ]; + const OUString& rName = rCommand.GetCommand(); + SwHtmlOptType nType = SwApplet_Impl::GetOptionType( rName, true ); + if( SwHtmlOptType::TAG == nType ) + { + const OUString& rValue = rCommand.GetArgument(); + rWrt.Strm().WriteChar( ' ' ); + HTMLOutFuncs::Out_String( rWrt.Strm(), rName ); + rWrt.Strm().WriteOString( "=\"" ); + HTMLOutFuncs::Out_String( rWrt.Strm(), rValue ).WriteChar( '\"' ); + } + else if( SwHtmlOptType::PARAM == nType ) + { + aParams.push_back( i ); + } + } + + rWrt.Strm().WriteChar( '>' ); + + rWrt.IncIndentLevel(); // indent the applet content + + size_t ii = aParams.size(); + while( ii > 0 ) + { + const SvCommand& rCommand = aCommands[ aParams[--ii] ]; + const OUString& rName = rCommand.GetCommand(); + const OUString& rValue = rCommand.GetArgument(); + rWrt.OutNewLine(); + sOut.append( + "<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_param + " " OOO_STRING_SVTOOLS_HTML_O_name + "=\""); + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( rWrt.Strm(), rName ); + sOut.append("\" " OOO_STRING_SVTOOLS_HTML_O_value "=\""); + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( rWrt.Strm(), rValue ).WriteOString( "\">" ); + } + + rWrt.DecIndentLevel(); // indent the applet content + if( aCommands.size() ) + rWrt.OutNewLine(); + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_applet), false ); + } + else if( aGlobName == SvGlobalName( SO3_PLUGIN_CLASSID ) ) + { + // write plug-ins parameters as options + + uno::Sequence < beans::PropertyValue > aProps; + aAny = xSet->getPropertyValue("PluginCommands"); + aAny >>= aProps; + + SvCommandList aCommands; + aCommands.FillFromSequence( aProps ); + for( size_t i = 0; i < aCommands.size(); i++ ) + { + const SvCommand& rCommand = aCommands[ i ]; + const OUString& rName = rCommand.GetCommand(); + + if( SwApplet_Impl::GetOptionType( rName, false ) == SwHtmlOptType::TAG ) + { + const OUString& rValue = rCommand.GetArgument(); + rWrt.Strm().WriteChar( ' ' ); + HTMLOutFuncs::Out_String( rWrt.Strm(), rName ); + rWrt.Strm().WriteOString( "=\"" ); + HTMLOutFuncs::Out_String( rWrt.Strm(), rValue ).WriteChar( '\"' ); + } + } + rWrt.Strm().WriteChar( '>' ); + } + else + { + // and for Floating-Frames just output another </IFRAME> + + rWrt.Strm().WriteChar( '>' ); + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_iframe), false ); + } + + if( !aEndTags.isEmpty() ) + rWrt.Strm().WriteOString( aEndTags ); + + return rWrt; +} + +static void OutHTMLGraphic(SwHTMLWriter& rWrt, const SwFrameFormat& rFrameFormat, SwOLENode* pOLENd, + const Graphic& rGraphic, bool bObjectOpened, bool bInCntnr) +{ + OUString aGraphicURL; + OUString aMimeType; + if (!rWrt.mbEmbedImages) + { + const OUString* pTempFileName = rWrt.GetOrigFileName(); + if (pTempFileName) + aGraphicURL = *pTempFileName; + + OUString aFilterName(u"JPG"_ustr); + XOutFlags nFlags = XOutFlags::UseGifIfPossible | XOutFlags::UseNativeIfPossible; + + if (bObjectOpened) + { + aFilterName = u"PNG"_ustr; + nFlags = XOutFlags::NONE; + aMimeType = u"image/png"_ustr; + + if (rGraphic.GetType() == GraphicType::NONE) + { + // The OLE Object has no replacement image, write a stub. + aGraphicURL = lcl_CalculateFileName(rWrt.GetOrigFileName(), rGraphic, u"png"); + osl::File aFile(aGraphicURL); + aFile.open(osl_File_OpenFlag_Create); + aFile.close(); + } + } + + ErrCode nErr = XOutBitmap::WriteGraphic(rGraphic, aGraphicURL, aFilterName, nFlags); + if (nErr) // error, don't write anything + { + rWrt.m_nWarn = WARN_SWG_POOR_LOAD; + if (bObjectOpened) // Still at least close the tag. + rWrt.Strm().WriteOString( + Concat2View("</" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_object ">")); + return; + } + aGraphicURL = URIHelper::SmartRel2Abs(INetURLObject(rWrt.GetBaseURL()), aGraphicURL, + URIHelper::GetMaybeFileHdl()); + } + HtmlFrmOpts nFlags = bInCntnr ? HtmlFrmOpts::GenImgAllMask : HtmlFrmOpts::GenImgMask; + if (bObjectOpened) + nFlags |= HtmlFrmOpts::Replacement; + HtmlWriter aHtml(rWrt.Strm(), rWrt.maNamespace); + OutHTML_ImageStart(aHtml, rWrt, rFrameFormat, aGraphicURL, rGraphic, pOLENd->GetTitle(), + pOLENd->GetTwipSize(), nFlags, "ole", nullptr, aMimeType); + OutHTML_ImageEnd(aHtml, rWrt); +} + +static void OutHTMLStartObject(SwHTMLWriter& rWrt, const OUString& rFileName, const OUString& rFileType) +{ + OUString aFileName = URIHelper::simpleNormalizedMakeRelative(rWrt.GetBaseURL(), rFileName); + + if (rWrt.IsLFPossible()) + rWrt.OutNewLine(); + rWrt.Strm().WriteOString( + Concat2View("<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_object)); + rWrt.Strm().WriteOString(Concat2View(" data=\"" + aFileName.toUtf8() + "\"")); + if (!rFileType.isEmpty()) + rWrt.Strm().WriteOString(Concat2View(" type=\"" + rFileType.toUtf8() + "\"")); + rWrt.Strm().WriteOString(">"); + rWrt.SetLFPossible(true); +} + +static void OutHTMLEndObject(SwHTMLWriter& rWrt) +{ + rWrt.Strm().WriteOString( + Concat2View("</" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_object ">")); +} + +static bool TrySaveFormulaAsPDF(SwHTMLWriter& rWrt, const SwFrameFormat& rFrameFormat, + SwOLENode* pOLENd, bool bWriteReplacementGraphic, bool bInCntnr) +{ + if (!rWrt.mbReqIF) + return false; + if (!rWrt.m_bExportFormulasAsPDF) + return false; + + auto xTextContent = SwXTextEmbeddedObject::CreateXTextEmbeddedObject( + *rWrt.m_pDoc, const_cast<SwFrameFormat*>(&rFrameFormat)); + uno::Reference<frame::XStorable> xStorable(xTextContent->getEmbeddedObject(), uno::UNO_QUERY); + uno::Reference<lang::XServiceInfo> xServiceInfo(xStorable, uno::UNO_QUERY); + if (!xServiceInfo) + return false; + if (!xServiceInfo->supportsService(u"com.sun.star.formula.FormulaProperties"_ustr)) + return false; + + Graphic aGraphic(xTextContent->getReplacementGraphic()); + OUString aFileName = lcl_CalculateFileName(rWrt.GetOrigFileName(), aGraphic, u"pdf"); + + utl::MediaDescriptor aDescr; + aDescr[u"FilterName"_ustr] <<= u"math_pdf_Export"_ustr; + // Properties from starmath/inc/unomodel.hxx + aDescr[u"FilterData"_ustr] <<= comphelper::InitPropertySequence({ + { u"TitleRow"_ustr, css::uno::Any(false) }, + { u"FormulaText"_ustr, css::uno::Any(false) }, + { u"Border"_ustr, css::uno::Any(false) }, + { u"PrintFormat"_ustr, css::uno::Any(sal_Int32(1)) }, // PRINT_SIZE_SCALED + }); + xStorable->storeToURL(aFileName, aDescr.getAsConstPropertyValueList()); + + OutHTMLStartObject(rWrt, aFileName, u"application/pdf"_ustr); + + if (bWriteReplacementGraphic) + OutHTMLGraphic(rWrt, rFrameFormat, pOLENd, aGraphic, true, bInCntnr); + + OutHTMLEndObject(rWrt); + + return true; +} + +SwHTMLWriter& OutHTML_FrameFormatOLENodeGrf( SwHTMLWriter& rWrt, const SwFrameFormat& rFrameFormat, + bool bInCntnr, bool bWriteReplacementGraphic ) +{ + const SwFormatContent& rFlyContent = rFrameFormat.GetContent(); + SwNodeOffset nStt = rFlyContent.GetContentIdx()->GetIndex()+1; + SwOLENode *pOLENd = rWrt.m_pDoc->GetNodes()[ nStt ]->GetOLENode(); + + OSL_ENSURE( pOLENd, "OLE-Node expected" ); + if( !pOLENd ) + return rWrt; + + if (rWrt.mbSkipImages) + { + // If we skip images, embedded objects would be completely lost. + // Instead, try to use the HTML export of the embedded object. + auto xTextContent = SwXTextEmbeddedObject::CreateXTextEmbeddedObject(*rWrt.m_pDoc, const_cast<SwFrameFormat*>(&rFrameFormat)); + uno::Reference<frame::XStorable> xStorable(xTextContent->getEmbeddedObject(), uno::UNO_QUERY); + SAL_WARN_IF(!xStorable.is(), "sw.html", "OutHTML_FrameFormatOLENodeGrf: no embedded object"); + + // Figure out what is the filter name of the embedded object. + OUString aFilter; + if (uno::Reference<lang::XServiceInfo> xServiceInfo{ xStorable, uno::UNO_QUERY }) + { + if (xServiceInfo->supportsService("com.sun.star.sheet.SpreadsheetDocument")) + aFilter = "HTML (StarCalc)"; + else if (xServiceInfo->supportsService("com.sun.star.text.TextDocument")) + aFilter = "HTML (StarWriter)"; + } + + if (!aFilter.isEmpty()) + { + try + { + // FIXME: exception for the simplest test document, too + SvMemoryStream aStream; + uno::Reference<io::XOutputStream> xOutputStream(new utl::OStreamWrapper(aStream)); + utl::MediaDescriptor aMediaDescriptor; + aMediaDescriptor["FilterName"] <<= aFilter; + aMediaDescriptor["FilterOptions"] <<= OUString("SkipHeaderFooter"); + aMediaDescriptor["OutputStream"] <<= xOutputStream; + xStorable->storeToURL("private:stream", aMediaDescriptor.getAsConstPropertyValueList()); + SAL_WARN_IF(aStream.GetSize()>=o3tl::make_unsigned(SAL_MAX_INT32), "sw.html", "Stream can't fit in OString"); + OString aData(static_cast<const char*>(aStream.GetData()), static_cast<sal_Int32>(aStream.GetSize())); + // Wrap output in a <span> tag to avoid 'HTML parser error: Unexpected end tag: p' + HTMLOutFuncs::Out_AsciiTag(rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_span)); + rWrt.Strm().WriteOString(aData); + HTMLOutFuncs::Out_AsciiTag(rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_span), false); + } + catch ( uno::Exception& ) + { + } + } + + return rWrt; + } + + if (TrySaveFormulaAsPDF(rWrt, rFrameFormat, pOLENd, bWriteReplacementGraphic, bInCntnr)) + return rWrt; + + if ( !pOLENd->GetGraphic() ) + { + SAL_WARN("sw.html", "Unexpected missing OLE fallback graphic"); + return rWrt; + } + + Graphic aGraphic( *pOLENd->GetGraphic() ); + + SwDocShell* pDocSh = rWrt.m_pDoc->GetDocShell(); + bool bObjectOpened = false; + OUString aRTFType = "text/rtf"; + if (!rWrt.m_aRTFOLEMimeType.isEmpty()) + { + aRTFType = rWrt.m_aRTFOLEMimeType; + } + + if (rWrt.mbXHTML && pDocSh) + { + // Map native data to an outer <object> element. + + // Calculate the file name, which is meant to be the same as the + // replacement image, just with a .ole extension. + OUString aFileName = lcl_CalculateFileName(rWrt.GetOrigFileName(), aGraphic, u"ole"); + + // Write the data. + SwOLEObj& rOLEObj = pOLENd->GetOLEObj(); + uno::Reference<embed::XEmbeddedObject> xEmbeddedObject = rOLEObj.GetOleRef(); + OUString aFileType; + SvFileStream aOutStream(aFileName, StreamMode::WRITE); + uno::Reference<io::XActiveDataStreamer> xStreamProvider; + uno::Reference<embed::XEmbedPersist2> xOwnEmbedded; + if (xEmbeddedObject.is()) + { + xStreamProvider.set(xEmbeddedObject, uno::UNO_QUERY); + xOwnEmbedded.set(xEmbeddedObject, uno::UNO_QUERY); + } + if (xStreamProvider.is()) + { + // Real OLE2 case: OleEmbeddedObject. + uno::Reference<io::XInputStream> xStream(xStreamProvider->getStream(), uno::UNO_QUERY); + if (xStream.is()) + { + std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(xStream)); + if (SwReqIfReader::WrapOleInRtf(*pStream, aOutStream, *pOLENd, rFrameFormat)) + { + // Data always wrapped in RTF. + aFileType = aRTFType; + } + } + } + else if (xOwnEmbedded.is()) + { + // Our own embedded object: OCommonEmbeddedObject. + SvxMSExportOLEObjects aOLEExp(0); + // Trigger the load of the OLE object if needed, otherwise we can't + // export it. + pOLENd->GetTwipSize(); + SvMemoryStream aMemory; + tools::SvRef<SotStorage> pStorage = new SotStorage(aMemory); + aOLEExp.ExportOLEObject(rOLEObj.GetObject(), *pStorage); + pStorage->Commit(); + aMemory.Seek(0); + if (SwReqIfReader::WrapOleInRtf(aMemory, aOutStream, *pOLENd, rFrameFormat)) + { + // Data always wrapped in RTF. + aFileType = aRTFType; + } + } + else + { + // Otherwise the native data is just a grab-bag: ODummyEmbeddedObject. + const OUString& aStreamName = rOLEObj.GetCurrentPersistName(); + uno::Reference<embed::XStorage> xStorage = pDocSh->GetStorage(); + uno::Reference<io::XStream> xInStream; + try + { + // Even the native data may be missing. + xInStream = xStorage->openStreamElement(aStreamName, embed::ElementModes::READ); + } catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("sw.html", "OutHTML_FrameFormatOLENodeGrf: failed to open stream element"); + } + if (xInStream.is()) + { + uno::Reference<io::XStream> xOutStream(new utl::OStreamWrapper(aOutStream)); + comphelper::OStorageHelper::CopyInputToOutput(xInStream->getInputStream(), + xOutStream->getOutputStream()); + } + + uno::Reference<beans::XPropertySet> xOutStreamProps(xInStream, uno::UNO_QUERY); + if (xOutStreamProps.is()) + xOutStreamProps->getPropertyValue("MediaType") >>= aFileType; + if (!aRTFType.isEmpty()) + { + aFileType = aRTFType; + } + } + + // Refer to this data. + OutHTMLStartObject(rWrt, aFileName, aFileType); + bObjectOpened = true; + } + + if (!bObjectOpened || bWriteReplacementGraphic) + OutHTMLGraphic(rWrt, rFrameFormat, pOLENd, aGraphic, bObjectOpened, bInCntnr); + + if (bObjectOpened) + // Close native data. + OutHTMLEndObject(rWrt); + + return rWrt; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlreqifreader.cxx b/sw/source/filter/html/htmlreqifreader.cxx new file mode 100644 index 0000000000..8df0d5b483 --- /dev/null +++ b/sw/source/filter/html/htmlreqifreader.cxx @@ -0,0 +1,652 @@ +/* -*- 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 "htmlreqifreader.hxx" + +#include <comphelper/scopeguard.hxx> +#include <filter/msfilter/rtfutil.hxx> +#include <rtl/strbuf.hxx> +#include <sot/storage.hxx> +#include <svtools/parrtf.hxx> +#include <svtools/rtfkeywd.hxx> +#include <svtools/rtftoken.h> +#include <tools/stream.hxx> +#include <filter/msfilter/msdffimp.hxx> +#include <vcl/cvtgrf.hxx> +#include <ndole.hxx> +#include <sal/log.hxx> +#include <vcl/FilterConfigItem.hxx> +#include <vcl/wmf.hxx> +#include <comphelper/propertyvalue.hxx> +#include <fmtfsize.hxx> +#include <frmfmt.hxx> + +using namespace com::sun::star; + +namespace +{ +/// RTF parser that just extracts a single OLE2 object from a file. +class ReqIfRtfReader : public SvRTFParser +{ +public: + ReqIfRtfReader(SvStream& rStream); + void NextToken(int nToken) override; + bool WriteObjectData(SvStream& rOLE); + +private: + bool m_bInObjData = false; + OStringBuffer m_aHex; +}; + +ReqIfRtfReader::ReqIfRtfReader(SvStream& rStream) + : SvRTFParser(rStream) +{ +} + +void ReqIfRtfReader::NextToken(int nToken) +{ + switch (nToken) + { + case '}': + m_bInObjData = false; + break; + case RTF_TEXTTOKEN: + if (m_bInObjData) + m_aHex.append(OUStringToOString(aToken, RTL_TEXTENCODING_ASCII_US)); + break; + case RTF_OBJDATA: + m_bInObjData = true; + break; + } +} + +bool ReqIfRtfReader::WriteObjectData(SvStream& rOLE) +{ + return msfilter::rtfutil::ExtractOLE2FromObjdata(m_aHex.makeStringAndClear(), rOLE); +} + +/// Looks up what OLE1 calls the ClassName, see [MS-OLEDS] 2.3.8 CompObjStream. +OString ExtractOLEClassName(const tools::SvRef<SotStorage>& xStorage) +{ + OString aRet; + + tools::SvRef<SotStorageStream> pCompObj = xStorage->OpenSotStream("\1CompObj"); + if (!pCompObj) + return aRet; + + pCompObj->Seek(0); + pCompObj->SeekRel(28); // Header + if (!pCompObj->good()) + return aRet; + + sal_uInt32 nData; + pCompObj->ReadUInt32(nData); // AnsiUserType + pCompObj->SeekRel(nData); + if (!pCompObj->good()) + return aRet; + + pCompObj->ReadUInt32(nData); // AnsiClipboardFormat + pCompObj->SeekRel(nData); + if (!pCompObj->good()) + return aRet; + + pCompObj->ReadUInt32(nData); // Reserved1 + return read_uInt8s_ToOString(*pCompObj, nData - 1); // -1 because it is null-terminated +} + +/// Parses the presentation stream of an OLE2 storage. +bool ParseOLE2Presentation(SvStream& rOle2, sal_uInt32& nWidth, sal_uInt32& nHeight, + SvStream& rPresentationData) +{ + // See [MS-OLEDS] 2.3.4, OLEPresentationStream + rOle2.Seek(0); + tools::SvRef<SotStorage> pStorage = new SotStorage(rOle2); + tools::SvRef<SotStorageStream> xOle2Presentation + = pStorage->OpenSotStream("\002OlePres000", StreamMode::STD_READ); + + // Read AnsiClipboardFormat. + sal_uInt32 nMarkerOrLength = 0; + xOle2Presentation->ReadUInt32(nMarkerOrLength); + if (nMarkerOrLength != 0xffffffff) + // FormatOrAnsiString is not present + return false; + sal_uInt32 nFormatOrAnsiLength = 0; + xOle2Presentation->ReadUInt32(nFormatOrAnsiLength); + if (nFormatOrAnsiLength != 0x00000003) // CF_METAFILEPICT + return false; + + // Read TargetDeviceSize. + sal_uInt32 nTargetDeviceSize = 0; + xOle2Presentation->ReadUInt32(nTargetDeviceSize); + if (nTargetDeviceSize != 0x00000004) + // TargetDevice is present + return false; + + sal_uInt32 nAspect = 0; + xOle2Presentation->ReadUInt32(nAspect); + sal_uInt32 nLindex = 0; + xOle2Presentation->ReadUInt32(nLindex); + sal_uInt32 nAdvf = 0; + xOle2Presentation->ReadUInt32(nAdvf); + sal_uInt32 nReserved1 = 0; + xOle2Presentation->ReadUInt32(nReserved1); + xOle2Presentation->ReadUInt32(nWidth); + xOle2Presentation->ReadUInt32(nHeight); + sal_uInt32 nSize = 0; + xOle2Presentation->ReadUInt32(nSize); + + // Read Data. + if (nSize > xOle2Presentation->remainingSize()) + return false; + + if (nSize <= 64) + { + SAL_WARN("sw.html", + "ParseOLE2Presentation: ignoring potentially broken small preview: size is " + << nSize); + return false; + } + + std::vector<char> aBuffer(nSize); + xOle2Presentation->ReadBytes(aBuffer.data(), aBuffer.size()); + rPresentationData.WriteBytes(aBuffer.data(), aBuffer.size()); + + return true; +} + +/** + * Inserts an OLE1 header before an OLE2 storage, assuming that the storage has an Ole10Native + * stream. + */ +OString InsertOLE1HeaderFromOle10NativeStream(const tools::SvRef<SotStorage>& xStorage, + SwOLENode& rOLENode, SvStream& rOle1) +{ + tools::SvRef<SotStorageStream> xOle1Stream + = xStorage->OpenSotStream("\1Ole10Native", StreamMode::STD_READ); + sal_uInt32 nOle1Size = 0; + xOle1Stream->ReadUInt32(nOle1Size); + + OString aClassName; + if (xStorage->GetClassName() == SvGlobalName(0x0003000A, 0, 0, 0xc0, 0, 0, 0, 0, 0, 0, 0x46)) + { + aClassName = "PBrush"_ostr; + } + else + { + if (xStorage->GetClassName() + != SvGlobalName(0x0003000C, 0, 0, 0xc0, 0, 0, 0, 0, 0, 0, 0x46)) + { + SAL_WARN("sw.html", "InsertOLE1HeaderFromOle10NativeStream: unexpected class id: " + << xStorage->GetClassName().GetHexName()); + } + aClassName = "Package"_ostr; + } + + // Write ObjectHeader, see [MS-OLEDS] 2.2.4. + rOle1.Seek(0); + // OLEVersion. + rOle1.WriteUInt32(0x00000501); + + // FormatID is EmbeddedObject. + rOle1.WriteUInt32(0x00000002); + + // ClassName + rOle1.WriteUInt32(aClassName.isEmpty() ? 0 : aClassName.getLength() + 1); + if (!aClassName.isEmpty()) + { + rOle1.WriteOString(aClassName); + // Null terminated pascal string. + rOle1.WriteChar(0); + } + + // TopicName. + rOle1.WriteUInt32(0); + + // ItemName. + rOle1.WriteUInt32(0); + + // NativeDataSize + rOle1.WriteUInt32(nOle1Size); + + // Write the actual native data. + rOle1.WriteStream(*xOle1Stream, nOle1Size); + + // Write Presentation. + if (!rOLENode.GetGraphic()) + { + return aClassName; + } + + const Graphic& rGraphic = *rOLENode.GetGraphic(); + Size aSize = rOLENode.GetTwipSize(); + SvMemoryStream aGraphicStream; + if (GraphicConverter::Export(aGraphicStream, rGraphic, ConvertDataFormat::WMF) != ERRCODE_NONE) + { + return aClassName; + } + + auto pGraphicAry = static_cast<const sal_uInt8*>(aGraphicStream.GetData()); + sal_uInt64 nPresentationData = aGraphicStream.TellEnd(); + msfilter::rtfutil::StripMetafileHeader(pGraphicAry, nPresentationData); + + // OLEVersion. + rOle1.WriteUInt32(0x00000501); + // FormatID: constant means the ClassName field is present. + rOle1.WriteUInt32(0x00000005); + // ClassName: null terminated pascal string. + OString aPresentationClassName("METAFILEPICT"_ostr); + rOle1.WriteUInt32(aPresentationClassName.getLength() + 1); + rOle1.WriteOString(aPresentationClassName); + rOle1.WriteChar(0); + // Width. + rOle1.WriteUInt32(aSize.getWidth()); + // Height. + rOle1.WriteUInt32(aSize.getHeight() * -1); + // PresentationDataSize + rOle1.WriteUInt32(8 + nPresentationData); + // Reserved1-4. + rOle1.WriteUInt16(0x0008); + rOle1.WriteUInt16(0x31b1); + rOle1.WriteUInt16(0x1dd9); + rOle1.WriteUInt16(0x0000); + rOle1.WriteBytes(pGraphicAry, nPresentationData); + + return aClassName; +} + +/** + * Writes an OLE1 header and data from rOle2 to rOle1. + * + * In case rOle2 has presentation data, then its size is written to nWidth/nHeight. Otherwise + * nWidth/nHeight/pPresentationData/nPresentationData is used for the presentation data. + */ +OString InsertOLE1Header(SvStream& rOle2, SvStream& rOle1, sal_uInt32& nWidth, sal_uInt32& nHeight, + SwOLENode& rOLENode, const sal_uInt8* pPresentationData, + sal_uInt64 nPresentationData) +{ + rOle2.Seek(0); + tools::SvRef<SotStorage> xStorage(new SotStorage(rOle2)); + if (xStorage->GetError() != ERRCODE_NONE) + return {}; + + if (xStorage->IsStream("\1Ole10Native")) + { + return InsertOLE1HeaderFromOle10NativeStream(xStorage, rOLENode, rOle1); + } + + OString aClassName = ExtractOLEClassName(xStorage); + + // Write ObjectHeader, see [MS-OLEDS] 2.2.4. + rOle1.Seek(0); + // OLEVersion. + rOle1.WriteUInt32(0x00000501); + + // FormatID is EmbeddedObject. + rOle1.WriteUInt32(0x00000002); + + // ClassName + rOle1.WriteUInt32(aClassName.isEmpty() ? 0 : aClassName.getLength() + 1); + if (!aClassName.isEmpty()) + { + rOle1.WriteOString(aClassName); + // Null terminated pascal string. + rOle1.WriteChar(0); + } + + // TopicName. + rOle1.WriteUInt32(0); + + // ItemName. + rOle1.WriteUInt32(0); + + // NativeDataSize + rOle1.WriteUInt32(rOle2.TellEnd()); + + // Write the actual native data. + rOle2.Seek(0); + rOle1.WriteStream(rOle2); + + // Write Presentation. + SvMemoryStream aPresentationData; + // OLEVersion. + rOle1.WriteUInt32(0x00000501); + // FormatID: constant means the ClassName field is present. + rOle1.WriteUInt32(0x00000005); + // ClassName: null terminated pascal string. + OString aPresentationClassName("METAFILEPICT"_ostr); + rOle1.WriteUInt32(aPresentationClassName.getLength() + 1); + rOle1.WriteOString(aPresentationClassName); + rOle1.WriteChar(0); + const sal_uInt8* pBytes = nullptr; + sal_uInt64 nBytes = 0; + if (ParseOLE2Presentation(rOle2, nWidth, nHeight, aPresentationData)) + { + // Take presentation data for OLE1 from OLE2. + pBytes = static_cast<const sal_uInt8*>(aPresentationData.GetData()); + nBytes = aPresentationData.Tell(); + } + else + { + // Take presentation data for OLE1 from RTF. + pBytes = pPresentationData; + nBytes = nPresentationData; + } + // Width. + rOle1.WriteUInt32(nWidth); + // Height. + rOle1.WriteUInt32(nHeight * -1); + // PresentationDataSize: size of (reserved fields + pBytes). + rOle1.WriteUInt32(8 + nBytes); + // Reserved1-4. + rOle1.WriteUInt16(0x0008); + rOle1.WriteUInt16(0x31b1); + rOle1.WriteUInt16(0x1dd9); + rOle1.WriteUInt16(0x0000); + rOle1.WriteBytes(pBytes, nBytes); + + return aClassName; +} + +/// Writes presentation data with the specified size to rRtf as an RTF hexdump. +void WrapOleGraphicInRtf(SvStream& rRtf, sal_uInt32 nWidth, sal_uInt32 nHeight, + const sal_uInt8* pPresentationData, sal_uInt64 nPresentationData) +{ + // Start result. + rRtf.WriteOString("{" OOO_STRING_SVTOOLS_RTF_RESULT); + + // Start pict. + rRtf.WriteOString("{" OOO_STRING_SVTOOLS_RTF_PICT); + + rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_WMETAFILE "8"); + rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_PICW); + rRtf.WriteOString(OString::number(nWidth)); + rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_PICH); + rRtf.WriteOString(OString::number(nHeight)); + rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_PICWGOAL); + rRtf.WriteOString(OString::number(nWidth)); + rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_PICHGOAL); + rRtf.WriteOString(OString::number(nHeight)); + if (pPresentationData) + { + rRtf.WriteOString(SAL_NEWLINE_STRING); + msfilter::rtfutil::WriteHex(pPresentationData, nPresentationData, &rRtf); + } + + // End pict. + rRtf.WriteOString("}"); + + // End result. + rRtf.WriteOString("}"); +} +} + +namespace SwReqIfReader +{ +bool ExtractOleFromRtf(SvStream& rRtf, SvStream& rOle, bool& bOwnFormat) +{ + // Add missing header/footer. + SvMemoryStream aRtf; + aRtf.WriteOString("{\\rtf1"); + aRtf.WriteStream(rRtf); + aRtf.WriteOString("}"); + aRtf.Seek(0); + + // Read the RTF markup. + tools::SvRef<ReqIfRtfReader> xReader(new ReqIfRtfReader(aRtf)); + SvParserState eState = xReader->CallParser(); + if (eState == SvParserState::Error) + return false; + + // Write the OLE2 data. + if (!xReader->WriteObjectData(rOle)) + return false; + + tools::SvRef<SotStorage> pStorage = new SotStorage(rOle); + OUString aFilterName = SvxMSDffManager::GetFilterNameFromClassID(pStorage->GetClassName()); + bOwnFormat = !aFilterName.isEmpty(); + if (!bOwnFormat) + { + // Real OLE2 data, we're done. + rOle.Seek(0); + return true; + } + + // ODF-in-OLE2 case, extract actual data. + SvMemoryStream aMemory; + SvxMSDffManager::ExtractOwnStream(*pStorage, aMemory); + rOle.Seek(0); + aMemory.Seek(0); + rOle.WriteStream(aMemory); + // Stream length is current position + 1. + rOle.SetStreamSize(aMemory.GetSize() + 1); + rOle.Seek(0); + return true; +} + +bool WrapOleInRtf(SvStream& rOle2, SvStream& rRtf, SwOLENode& rOLENode, + const SwFrameFormat& rFormat) +{ + sal_uInt64 nPos = rOle2.Tell(); + comphelper::ScopeGuard g([&rOle2, nPos] { rOle2.Seek(nPos); }); + + // Write OLE1 header, then the RTF wrapper. + SvMemoryStream aOLE1; + + // Prepare presentation data early, so it's available to both OLE1 and RTF. + Size aSize = rFormat.GetFrameSize().GetSize(); + sal_uInt32 nWidth = aSize.getWidth(); + sal_uInt32 nHeight = aSize.getHeight(); + const Graphic* pGraphic = rOLENode.GetGraphic(); + const sal_uInt8* pPresentationData = nullptr; + sal_uInt64 nPresentationData = 0; + SvMemoryStream aGraphicStream; + if (pGraphic) + { + uno::Sequence<beans::PropertyValue> aFilterData + = { comphelper::makePropertyValue("EmbedEMF", false) }; + FilterConfigItem aConfigItem(&aFilterData); + if (ConvertGraphicToWMF(*pGraphic, aGraphicStream, &aConfigItem)) + { + pPresentationData = static_cast<const sal_uInt8*>(aGraphicStream.GetData()); + nPresentationData = aGraphicStream.TellEnd(); + msfilter::rtfutil::StripMetafileHeader(pPresentationData, nPresentationData); + } + } + OString aClassName = InsertOLE1Header(rOle2, aOLE1, nWidth, nHeight, rOLENode, + pPresentationData, nPresentationData); + + // Start object. + rRtf.WriteOString("{" OOO_STRING_SVTOOLS_RTF_OBJECT); + rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_OBJEMB); + + // Start objclass. + rRtf.WriteOString("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_OBJCLASS " "); + rRtf.WriteOString(aClassName); + // End objclass. + rRtf.WriteOString("}"); + + // Object size. + rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_OBJW); + rRtf.WriteOString(OString::number(nWidth)); + rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_OBJH); + rRtf.WriteOString(OString::number(nHeight)); + + // Start objdata. + rRtf.WriteOString( + "{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_OBJDATA SAL_NEWLINE_STRING); + msfilter::rtfutil::WriteHex(static_cast<const sal_uInt8*>(aOLE1.GetData()), aOLE1.GetSize(), + &rRtf); + // End objdata. + rRtf.WriteOString("}"); + + if (pPresentationData) + { + WrapOleGraphicInRtf(rRtf, nWidth, nHeight, pPresentationData, nPresentationData); + } + + // End object. + rRtf.WriteOString("}"); + + return true; +} + +bool WrapGraphicInRtf(const Graphic& rGraphic, const SwFrameFormat& rFormat, SvStream& rRtf) +{ + // Start object. + rRtf.WriteOString("{" OOO_STRING_SVTOOLS_RTF_OBJECT); + rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_OBJEMB); + + // Object size: as used in the document model (not pixel size) + Size aSize = rFormat.GetFrameSize().GetSize(); + sal_uInt32 nWidth = aSize.getWidth(); + sal_uInt32 nHeight = aSize.getHeight(); + rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_OBJW); + rRtf.WriteOString(OString::number(nWidth)); + rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_OBJH); + rRtf.WriteOString(OString::number(nHeight)); + rRtf.WriteOString(SAL_NEWLINE_STRING); + + // Start objclass. + rRtf.WriteOString("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_OBJCLASS " "); + OString aClassName("PBrush"_ostr); + rRtf.WriteOString(aClassName); + // End objclass. + rRtf.WriteOString("}"); + rRtf.WriteOString(SAL_NEWLINE_STRING); + + // Start objdata. + rRtf.WriteOString("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_OBJDATA " "); + + SvMemoryStream aOle1; + // Write ObjectHeader, see [MS-OLEDS] 2.2.4. + // OLEVersion. + aOle1.WriteUInt32(0x00000501); + + // FormatID is EmbeddedObject. + aOle1.WriteUInt32(0x00000002); + + // ClassName + aOle1.WriteUInt32(aClassName.getLength() + 1); + aOle1.WriteOString(aClassName); + // Null terminated pascal string. + aOle1.WriteChar(0); + + // TopicName. + aOle1.WriteUInt32(0); + + // ItemName. + aOle1.WriteUInt32(0); + + // NativeDataSize + SvMemoryStream aNativeData; + + // Set white background for the semi-transparent pixels. + BitmapEx aBitmapEx = rGraphic.GetBitmapEx(); + Bitmap aBitmap = aBitmapEx.GetBitmap(/*aTransparentReplaceColor=*/COL_WHITE); + + if (aBitmap.getPixelFormat() != vcl::PixelFormat::N24_BPP) + { + // More exotic pixel formats cause trouble for ms paint. + aBitmap.Convert(BmpConversion::N24Bit); + } + + if (GraphicConverter::Export(aNativeData, BitmapEx(aBitmap), ConvertDataFormat::BMP) + != ERRCODE_NONE) + { + SAL_WARN("sw.html", "WrapGraphicInRtf: bmp conversion failed"); + } + aOle1.WriteUInt32(aNativeData.TellEnd()); + + // Write the actual native data. + aNativeData.Seek(0); + aOle1.WriteStream(aNativeData); + + // Prepare presentation data. + const sal_uInt8* pPresentationData = nullptr; + sal_uInt64 nPresentationData = 0; + SvMemoryStream aGraphicStream; + uno::Sequence<beans::PropertyValue> aFilterData + = { comphelper::makePropertyValue("EmbedEMF", false) }; + FilterConfigItem aConfigItem(&aFilterData); + if (ConvertGraphicToWMF(rGraphic, aGraphicStream, &aConfigItem)) + { + pPresentationData = static_cast<const sal_uInt8*>(aGraphicStream.GetData()); + nPresentationData = aGraphicStream.TellEnd(); + msfilter::rtfutil::StripMetafileHeader(pPresentationData, nPresentationData); + } + + // Write Presentation. + // OLEVersion. + aOle1.WriteUInt32(0x00000501); + // FormatID: constant means the ClassName field is present. + aOle1.WriteUInt32(0x00000005); + // ClassName: null terminated pascal string. + OString aPresentationClassName("METAFILEPICT"_ostr); + aOle1.WriteUInt32(aPresentationClassName.getLength() + 1); + aOle1.WriteOString(aPresentationClassName); + aOle1.WriteChar(0); + const sal_uInt8* pBytes = nullptr; + sal_uInt64 nBytes = 0; + // Take presentation data for OLE1 from RTF. + pBytes = pPresentationData; + nBytes = nPresentationData; + // Width. + aOle1.WriteUInt32(nWidth); + // Height. + aOle1.WriteUInt32(nHeight * -1); + // PresentationDataSize: size of (reserved fields + pBytes). + aOle1.WriteUInt32(8 + nBytes); + // Reserved1-4. + aOle1.WriteUInt16(0x0008); + aOle1.WriteUInt16(0x31b1); + aOle1.WriteUInt16(0x1dd9); + aOle1.WriteUInt16(0x0000); + aOle1.WriteBytes(pBytes, nBytes); + + // End objdata. + msfilter::rtfutil::WriteHex(static_cast<const sal_uInt8*>(aOle1.GetData()), aOle1.GetSize(), + &rRtf); + rRtf.WriteOString("}"); + rRtf.WriteOString(SAL_NEWLINE_STRING); + + rRtf.WriteOString("{" OOO_STRING_SVTOOLS_RTF_RESULT); + rRtf.WriteOString("{" OOO_STRING_SVTOOLS_RTF_PICT); + + Size aMapped(rGraphic.GetPrefSize()); + rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_PICW); + rRtf.WriteOString(OString::number(aMapped.Width())); + rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_PICH); + rRtf.WriteOString(OString::number(aMapped.Height())); + + rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_PICWGOAL); + rRtf.WriteOString(OString::number(nWidth)); + rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_PICHGOAL); + rRtf.WriteOString(OString::number(nHeight)); + rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_WMETAFILE "8"); + rRtf.WriteOString(SAL_NEWLINE_STRING); + + if (pPresentationData) + { + msfilter::rtfutil::WriteHex(pPresentationData, nPresentationData, &rRtf); + rRtf.WriteOString(SAL_NEWLINE_STRING); + } + + // End pict. + rRtf.WriteOString("}"); + + // End result. + rRtf.WriteOString("}"); + + // End object. + rRtf.WriteOString("}"); + return true; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlreqifreader.hxx b/sw/source/filter/html/htmlreqifreader.hxx new file mode 100644 index 0000000000..84169bb7c0 --- /dev/null +++ b/sw/source/filter/html/htmlreqifreader.hxx @@ -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/. + */ +#ifndef INCLUDED_SW_SOURCE_FILTER_HTML_HTMLREQIFREADER_HXX +#define INCLUDED_SW_SOURCE_FILTER_HTML_HTMLREQIFREADER_HXX + +class Graphic; +class Size; +class SvStream; +class SwOLENode; +class SwFrameFormat; + +namespace SwReqIfReader +{ +/** + * Extracts an OLE2 container binary from an RTF fragment. + * + * @param bOwnFormat if the extracted data has an ODF class ID or not. + */ +bool ExtractOleFromRtf(SvStream& rRtf, SvStream& rOle, bool& bOwnFormat); + +/// Wraps an OLE2 container binary in an RTF fragment. +bool WrapOleInRtf(SvStream& rOle2, SvStream& rRtf, SwOLENode& rOLENode, + const SwFrameFormat& rFormat); + +/** + * Wraps an image in an RTF fragment. + */ +bool WrapGraphicInRtf(const Graphic& rGraphic, const SwFrameFormat& rFormat, SvStream& rRtf); +} + +#endif // INCLUDED_SW_SOURCE_FILTER_HTML_HTMLREQIFREADER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlsect.cxx b/sw/source/filter/html/htmlsect.cxx new file mode 100644 index 0000000000..26a1ec8d0e --- /dev/null +++ b/sw/source/filter/html/htmlsect.cxx @@ -0,0 +1,825 @@ +/* -*- 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/HoriOrientation.hpp> +#include <com/sun/star/text/VertOrientation.hpp> +#include <rtl/uri.hxx> + +#include <svl/urihelper.hxx> +#include <vcl/svapp.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/formatbreakitem.hxx> +#include <svtools/htmltokn.h> +#include <svtools/htmlkywd.hxx> +#include <sfx2/linkmgr.hxx> +#include <osl/diagnose.h> + +#include <hintids.hxx> +#include <fmthdft.hxx> +#include <fmtcntnt.hxx> +#include <fmtclds.hxx> +#include <fmtanchr.hxx> +#include <fmtpdsc.hxx> +#include <frmatr.hxx> +#include <doc.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <shellio.hxx> +#include <section.hxx> +#include <poolfmt.hxx> +#include <pagedesc.hxx> +#include <swtable.hxx> +#include "swcss1.hxx" +#include "swhtml.hxx" + + +using namespace ::com::sun::star; + +void SwHTMLParser::NewDivision( HtmlTokenId nToken ) +{ + OUString aId, aHRef; + OUString aStyle, aLang, aDir; + OUString aClass; + SvxAdjust eAdjust = HtmlTokenId::CENTER_ON==nToken ? SvxAdjust::Center + : SvxAdjust::End; + + bool bHeader=false, bFooter=false; + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::ALIGN: + if( HtmlTokenId::DIVISION_ON==nToken ) + eAdjust = rOption.GetEnum( aHTMLPAlignTable, eAdjust ); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::LANG: + aLang = rOption.GetString(); + break; + case HtmlOptionId::DIR: + aDir = rOption.GetString(); + break; + case HtmlOptionId::HREF: + aHRef = rOption.GetString(); + break; + case HtmlOptionId::TITLE: + { + const OUString& rType = rOption.GetString(); + if( rType.equalsIgnoreAsciiCase("header") ) + bHeader = true; + else if( rType.equalsIgnoreAsciiCase("footer") ) + bFooter = true; + } + break; + default: break; + } + } + + bool bAppended = false; + if( m_pPam->GetPoint()->GetContentIndex() ) + { + AppendTextNode( bHeader||bFooter||!aId.isEmpty()|| !aHRef.isEmpty() ? AM_NORMAL + : AM_NOSPACE ); + bAppended = true; + } + + std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(nToken)); + + bool bStyleParsed = false, bPositioned = false; + SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + if( HasStyleOptions( aStyle, aId, aClass, &aLang, &aDir ) ) + { + bStyleParsed = ParseStyleOptions( aStyle, aId, aClass, + aItemSet, aPropInfo, &aLang, &aDir ); + if( bStyleParsed ) + { + if ( aPropInfo.m_nColumnCount >= 2 ) + { + xCntxt.reset(); + NewMultiCol( aPropInfo.m_nColumnCount ); + return; + } + bPositioned = HtmlTokenId::DIVISION_ON == nToken && !aClass.isEmpty() && + CreateContainer(aClass, aItemSet, aPropInfo, + xCntxt.get()); + if( !bPositioned ) + { + if (aPropInfo.m_bVisible && m_aContexts.size()) + { + const std::unique_ptr<HTMLAttrContext>& pParent + = m_aContexts[m_aContexts.size() - 1]; + if (!pParent->IsVisible()) + { + // If the parent context is hidden, we are not visible, either. + aPropInfo.m_bVisible = false; + } + } + bPositioned = DoPositioning(aItemSet, aPropInfo, xCntxt.get()); + } + } + } + + if (!bPositioned && (bHeader || bFooter) && IsNewDoc() && !m_bReadingHeaderOrFooter) + { + m_bReadingHeaderOrFooter = true; + xCntxt->SetHeaderOrFooter(true); + + SwPageDesc *pPageDesc = m_pCSS1Parser->GetMasterPageDesc(); + SwFrameFormat& rPageFormat = pPageDesc->GetMaster(); + + SwFrameFormat *pHdFtFormat; + bool bNew = false; + HtmlContextFlags nFlags = HtmlContextFlags::MultiColMask; + if( bHeader ) + { + pHdFtFormat = const_cast<SwFrameFormat*>(rPageFormat.GetHeader().GetHeaderFormat()); + if( !pHdFtFormat ) + { + // still no header, then create one + rPageFormat.SetFormatAttr( SwFormatHeader( true )); + pHdFtFormat = const_cast<SwFrameFormat*>(rPageFormat.GetHeader().GetHeaderFormat()); + bNew = true; + } + nFlags |= HtmlContextFlags::HeaderDist; + } + else + { + pHdFtFormat = const_cast<SwFrameFormat*>(rPageFormat.GetFooter().GetFooterFormat()); + if( !pHdFtFormat ) + { + // still no footer, then create one + rPageFormat.SetFormatAttr( SwFormatFooter( true )); + pHdFtFormat = const_cast<SwFrameFormat*>(rPageFormat.GetFooter().GetFooterFormat()); + bNew = true; + } + nFlags |= HtmlContextFlags::FooterDist; + } + + const SwFormatContent& rFlyContent = pHdFtFormat->GetContent(); + const SwNodeIndex& rContentStIdx = *rFlyContent.GetContentIdx(); + + if( !bNew ) + { + // Our own html export only exports one "header" at most (and one "footer") + + // Create a new node at the beginning of the section if a duplicate arises + // and hide the original header/footers content by putting it into a hidden + // document-level section + SwNodeIndex aSttIdx( rContentStIdx, 1 ); + m_xDoc->GetNodes().MakeTextNode( aSttIdx.GetNode(), + m_pCSS1Parser->GetTextCollFromPool(RES_POOLCOLL_TEXT)); + + // delete the current content of the section + SwPaM aDelPam( aSttIdx ); + aDelPam.SetMark(); + + const SwStartNode *pStNd = + static_cast<const SwStartNode *>( &rContentStIdx.GetNode() ); + aDelPam.GetPoint()->Assign( pStNd->EndOfSectionIndex() ); + + SwSectionData aSection(SectionType::Content, m_xDoc->GetUniqueSectionName()); + if (SwSection* pOldContent = m_xDoc->InsertSwSection(aDelPam, aSection, nullptr, nullptr, false)) + pOldContent->SetHidden(true); + + // update page style + for( size_t i=0; i < m_xDoc->GetPageDescCnt(); i++ ) + { + if( RES_POOLPAGE_HTML == m_xDoc->GetPageDesc(i).GetPoolFormatId() ) + { + m_xDoc->ChgPageDesc( i, *pPageDesc ); + break; + } + } + } + + SwPosition aNewPos( rContentStIdx, SwNodeOffset(1) ); + SaveDocContext(xCntxt.get(), nFlags, &aNewPos); + } + else if( !bPositioned && aId.getLength() > 9 && + (aId[0] == 's' || aId[0] == 'S' ) && + (aId[1] == 'd' || aId[1] == 'D' ) ) + { + bool bEndNote = false, bFootNote = false; + if( aId.startsWithIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_sdendnote ) ) + bEndNote = true; + else if( aId.startsWithIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_sdfootnote ) ) + bFootNote = true; + if( bFootNote || bEndNote ) + { + SwNodeIndex *pStartNdIdx = GetFootEndNoteSection( aId ); + if( pStartNdIdx ) + { + SwContentNode *pCNd = + m_xDoc->GetNodes()[pStartNdIdx->GetIndex()+1]->GetContentNode(); + SwNodeIndex aTmpSwNodeIndex(*pCNd); + SwPosition aNewPos( aTmpSwNodeIndex, pCNd, 0 ); + SaveDocContext(xCntxt.get(), HtmlContextFlags::MultiColMask, &aNewPos); + aId.clear(); + aPropInfo.m_aId.clear(); + } + } + } + + // We only insert sections into frames if the section is linked. + if( (!aId.isEmpty() && !bPositioned) || !aHRef.isEmpty() ) + { + // Insert section (has to be done before setting of attributes, + // because the section is inserted before the PaM position. + + // If we are in the first node of a section, we insert the section + // before the current section and not in the current section. + // Therefore we have to add a node and delete it again! + if( !bAppended ) + { + SwNodeIndex aPrvNdIdx( m_pPam->GetPoint()->GetNode(), -1 ); + if (aPrvNdIdx.GetNode().IsSectionNode()) + { + AppendTextNode(); + bAppended = true; + } + } + std::unique_ptr<std::deque<std::unique_ptr<HTMLAttr>>> pPostIts(bAppended ? nullptr : new std::deque<std::unique_ptr<HTMLAttr>>); + SetAttr( true, true, pPostIts.get() ); + + // make name of section unique + const OUString aName( m_xDoc->GetUniqueSectionName( !aId.isEmpty() ? &aId : nullptr ) ); + + if( !aHRef.isEmpty() ) + { + sal_Unicode cDelim = 255U; + sal_Int32 nPos = aHRef.lastIndexOf( cDelim ); + sal_Int32 nPos2 = -1; + if( nPos != -1 ) + { + nPos2 = aHRef.lastIndexOf( cDelim, nPos ); + if( nPos2 != -1 ) + std::swap( nPos, nPos2 ); + } + OUString aURL; + if( nPos == -1 ) + { + aURL = URIHelper::SmartRel2Abs(INetURLObject( m_sBaseURL ), aHRef, Link<OUString *, bool>(), false); + } + else + { + aURL = URIHelper::SmartRel2Abs(INetURLObject( m_sBaseURL ), aHRef.copy( 0, nPos ), Link<OUString *, bool>(), false ) + + OUStringChar(sfx2::cTokenSeparator); + if( nPos2 == -1 ) + { + aURL += aHRef.subView( nPos+1 ); + } + else + { + aURL += aHRef.subView( nPos+1, nPos2 - (nPos+1) ) + + OUStringChar(sfx2::cTokenSeparator) + + rtl::Uri::decode( aHRef.copy( nPos2+1 ), + rtl_UriDecodeWithCharset, + RTL_TEXTENCODING_ISO_8859_1 ); + } + } + aHRef = aURL; + } + + SwSectionData aSection( (!aHRef.isEmpty()) ? SectionType::FileLink + : SectionType::Content, aName ); + if( !aHRef.isEmpty() ) + { + aSection.SetLinkFileName( aHRef ); + aSection.SetProtectFlag(true); + } + + SfxItemSetFixed<RES_FRMATR_BEGIN, RES_FRMATR_END-1> aFrameItemSet( m_xDoc->GetAttrPool() ); + if( !IsNewDoc() ) + Reader::ResetFrameFormatAttrs(aFrameItemSet ); + + if( const SvxBrushItem* pItem = aItemSet.GetItemIfSet( RES_BACKGROUND, false ) ) + { + aFrameItemSet.Put( *pItem ); + aItemSet.ClearItem( RES_BACKGROUND ); + } + if( const SvxFrameDirectionItem* pItem = aItemSet.GetItemIfSet( RES_FRAMEDIR, false ) ) + { + aFrameItemSet.Put( *pItem ); + aItemSet.ClearItem( RES_FRAMEDIR ); + } + + m_xDoc->InsertSwSection( *m_pPam, aSection, nullptr, &aFrameItemSet, false ); + + // maybe jump to section + if( JumpToMarks::Region == m_eJumpTo && aName == m_sJmpMark ) + { + m_bChkJumpMark = true; + m_eJumpTo = JumpToMarks::NONE; + } + + SwTextNode* pOldTextNd = + bAppended ? nullptr : m_pPam->GetPoint()->GetNode().GetTextNode(); + + m_pPam->Move( fnMoveBackward ); + + // move PageDesc and SwFormatBreak attribute from current node into + // (first) node of the section + if( pOldTextNd ) + MovePageDescAttrs( pOldTextNd, m_pPam->GetPoint()->GetNodeIndex(), + true ); + + if( pPostIts ) + { + // move still existing PostIts in the first paragraph of the table + InsertAttrs( std::move(*pPostIts) ); + pPostIts.reset(); + } + + xCntxt->SetSpansSection( true ); + + // don't insert Bookmarks with same name as sections + if( !aPropInfo.m_aId.isEmpty() && aPropInfo.m_aId==aName ) + aPropInfo.m_aId.clear(); + } + else + { + xCntxt->SetAppendMode( AM_NOSPACE ); + } + + if( SvxAdjust::End != eAdjust ) + { + InsertAttr(&m_xAttrTab->pAdjust, SvxAdjustItem(eAdjust, RES_PARATR_ADJUST), xCntxt.get()); + } + + // parse style + if( bStyleParsed ) + InsertAttrs( aItemSet, aPropInfo, xCntxt.get(), true ); + + xCntxt->SetVisible(aPropInfo.m_bVisible); + PushContext(xCntxt); +} + +void SwHTMLParser::EndDivision() +{ + // search for the stack entry of the token (because we still have the div stack + // we don't make a difference between DIV and CENTER) + std::unique_ptr<HTMLAttrContext> xCntxt; + auto nPos = m_aContexts.size(); + while (!xCntxt && nPos>m_nContextStMin) + { + switch( m_aContexts[--nPos]->GetToken() ) + { + case HtmlTokenId::CENTER_ON: + case HtmlTokenId::DIVISION_ON: + xCntxt = std::move(m_aContexts[nPos]); + m_aContexts.erase( m_aContexts.begin() + nPos ); + break; + default: break; + } + } + + if (xCntxt) + { + // close attribute + EndContext(xCntxt.get()); + SetAttr(); // set paragraph attributes really fast because of JavaScript + if (xCntxt->IsHeaderOrFooter()) + m_bReadingHeaderOrFooter = false; + } +} + +void SwHTMLParser::FixHeaderFooterDistance( bool bHeader, + const SwPosition *pOldPos ) +{ + SwPageDesc *pPageDesc = m_pCSS1Parser->GetMasterPageDesc(); + SwFrameFormat& rPageFormat = pPageDesc->GetMaster(); + + SwFrameFormat *pHdFtFormat = + bHeader ? const_cast<SwFrameFormat*>(rPageFormat.GetHeader().GetHeaderFormat()) + : const_cast<SwFrameFormat*>(rPageFormat.GetFooter().GetFooterFormat()); + OSL_ENSURE( pHdFtFormat, "No header or footer" ); + + const SwFormatContent& rFlyContent = pHdFtFormat->GetContent(); + const SwNodeIndex& rContentStIdx = *rFlyContent.GetContentIdx(); + + SwNodeOffset nPrvNxtIdx; + if( bHeader ) + { + nPrvNxtIdx = rContentStIdx.GetNode().EndOfSectionIndex()-1; + } + else + { + nPrvNxtIdx = pOldPos->GetNodeIndex() - 1; + } + + sal_uInt16 nSpace = 0; + SwTextNode *pTextNode = m_xDoc->GetNodes()[nPrvNxtIdx]->GetTextNode(); + if( pTextNode ) + { + const SvxULSpaceItem& rULSpace = + pTextNode->SwContentNode::GetAttr( RES_UL_SPACE ); + + // The bottom paragraph padding becomes the padding + // to header or footer + nSpace = rULSpace.GetLower(); + + // and afterwards set to a valid value + const SvxULSpaceItem& rCollULSpace = + pTextNode->GetAnyFormatColl().GetULSpace(); + if( rCollULSpace.GetUpper() == rULSpace.GetUpper() ) + pTextNode->ResetAttr( RES_UL_SPACE ); + else + pTextNode->SetAttr( + SvxULSpaceItem( rULSpace.GetUpper(), + rCollULSpace.GetLower(), RES_UL_SPACE ) ); + } + + if( bHeader ) + { + nPrvNxtIdx = pOldPos->GetNodeIndex(); + } + else + { + nPrvNxtIdx = rContentStIdx.GetIndex() + 1; + } + + pTextNode = m_xDoc->GetNodes()[nPrvNxtIdx] + ->GetTextNode(); + if( pTextNode ) + { + const SvxULSpaceItem& rULSpace = + pTextNode->SwContentNode::GetAttr( RES_UL_SPACE ); + + // The top paragraph padding becomes the padding + // to headline or footer if it is greater than the + // bottom padding of the paragraph beforehand + if( rULSpace.GetUpper() > nSpace ) + nSpace = rULSpace.GetUpper(); + + // and afterwards set to a valid value + const SvxULSpaceItem& rCollULSpace = + pTextNode->GetAnyFormatColl().GetULSpace(); + if( rCollULSpace.GetLower() == rULSpace.GetLower() ) + pTextNode->ResetAttr( RES_UL_SPACE ); + else + pTextNode->SetAttr( + SvxULSpaceItem( rCollULSpace.GetUpper(), + rULSpace.GetLower(), RES_UL_SPACE ) ); + } + + SvxULSpaceItem aULSpace( RES_UL_SPACE ); + if( bHeader ) + aULSpace.SetLower( nSpace ); + else + aULSpace.SetUpper( nSpace ); + + pHdFtFormat->SetFormatAttr( aULSpace ); +} + +bool SwHTMLParser::EndSection( bool bLFStripped ) +{ + SwEndNode *pEndNd = m_xDoc->GetNodes()[m_pPam->GetPoint()->GetNodeIndex()+1] + ->GetEndNode(); + if( pEndNd && pEndNd->StartOfSectionNode()->IsSectionNode() ) + { + // close the section + if( !bLFStripped ) + StripTrailingPara(); + m_pPam->Move( fnMoveForward ); + return true; + } + + OSL_ENSURE( false, "Wrong PaM position at end of section" ); + + return false; +} + +bool SwHTMLParser::EndSections( bool bLFStripped ) +{ + bool bSectionClosed = false; + auto nPos = m_aContexts.size(); + while( nPos>m_nContextStMin ) + { + HTMLAttrContext *pCntxt = m_aContexts[--nPos].get(); + if( pCntxt->GetSpansSection() && EndSection( bLFStripped ) ) + { + bSectionClosed = true; + pCntxt->SetSpansSection( false ); + bLFStripped = false; + } + } + + return bSectionClosed; +} + +void SwHTMLParser::NewMultiCol( sal_uInt16 columnsFromCss ) +{ + OUString aId; + OUString aStyle, aClass, aLang, aDir; + tools::Long nWidth = 100; + sal_uInt16 nCols = columnsFromCss, nGutter = 10; + bool bPercentWidth = true; + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::LANG: + aLang = rOption.GetString(); + break; + case HtmlOptionId::DIR: + aDir = rOption.GetString(); + break; + case HtmlOptionId::COLS: + nCols = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + break; + case HtmlOptionId::WIDTH: + nWidth = rOption.GetNumber(); + bPercentWidth = (rOption.GetString().indexOf('%') != -1); + if( bPercentWidth && nWidth>100 ) + nWidth = 100; + break; + case HtmlOptionId::GUTTER: + nGutter = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + break; + default: break; + } + } + + std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(HtmlTokenId::MULTICOL_ON)); + + //.is the multicol element contained in a container? That may be the + // case for 5.0 documents. + bool bInCntnr = false; + auto i = m_aContexts.size(); + while( !bInCntnr && i > m_nContextStMin ) + bInCntnr = nullptr != m_aContexts[--i]->GetFrameItemSet(); + + // Parse style sheets, but don't position anything by now. + bool bStyleParsed = false; + SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + if( HasStyleOptions( aStyle, aId, aClass, &aLang, &aDir ) ) + bStyleParsed = ParseStyleOptions( aStyle, aId, aClass, + aItemSet, aPropInfo, &aLang, &aDir ); + + // Calculate width. + sal_uInt8 nPercentWidth = bPercentWidth ? static_cast<sal_uInt8>(nWidth) : 0; + SwTwips nTwipWidth = 0; + if( !bPercentWidth && nWidth ) + { + nTwipWidth = o3tl::convert(nWidth, o3tl::Length::px, o3tl::Length::twip); + } + + if( !nPercentWidth && nTwipWidth < MINFLY ) + nTwipWidth = MINFLY; + + // Do positioning. + bool bPositioned = false; + if( bInCntnr || SwCSS1Parser::MayBePositioned( aPropInfo, true ) ) + { + SfxItemSetFixed<RES_FRMATR_BEGIN, RES_FRMATR_END-1> aFrameItemSet( m_xDoc->GetAttrPool() ); + if( !IsNewDoc() ) + Reader::ResetFrameFormatAttrs(aFrameItemSet ); + + SetAnchorAndAdjustment( text::VertOrientation::NONE, text::HoriOrientation::NONE, aPropInfo, + aFrameItemSet ); + + // The width is either the WIDTH attribute's value or contained + // in some style option. + SetVarSize( aPropInfo, aFrameItemSet, nTwipWidth, nPercentWidth ); + + SetSpace( Size(0,0), aItemSet, aPropInfo, aFrameItemSet ); + + // Set some other frame attributes. If the background is set, its + // it will be cleared here. That for, it won't be set at the section, + // too. + SetFrameFormatAttrs( aItemSet, + HtmlFrameFormatFlags::Box|HtmlFrameFormatFlags::Background|HtmlFrameFormatFlags::Padding|HtmlFrameFormatFlags::Direction, + aFrameItemSet ); + + // Insert fly frame. If the are columns, the fly frame's name is not + // the sections name but a generated one. + OUString aFlyName; + if( nCols < 2 ) + { + aFlyName = aId; + aPropInfo.m_aId.clear(); + } + + InsertFlyFrame(aFrameItemSet, xCntxt.get(), aFlyName); + + xCntxt->SetPopStack( true ); + bPositioned = true; + } + + bool bAppended = false; + if( !bPositioned ) + { + if( m_pPam->GetPoint()->GetContentIndex() ) + { + AppendTextNode( AM_SPACE ); + bAppended = true; + } + else + { + AddParSpace(); + } + } + + // If there are less than 2 columns, no section is inserted. + if( nCols >= 2 ) + { + if( !bAppended ) + { + // If the pam is at the start of a section, an additional text + // node must be inserted. Otherwise, the new section will be + // inserted in front of the old one. + SwNodeIndex aPrvNdIdx( m_pPam->GetPoint()->GetNode(), -1 ); + if (aPrvNdIdx.GetNode().IsSectionNode()) + { + AppendTextNode(); + bAppended = true; + } + } + std::unique_ptr<std::deque<std::unique_ptr<HTMLAttr>>> pPostIts(bAppended ? nullptr : new std::deque<std::unique_ptr<HTMLAttr>>); + SetAttr( true, true, pPostIts.get() ); + + // Make section name unique. + OUString aName( m_xDoc->GetUniqueSectionName( !aId.isEmpty() ? &aId : nullptr ) ); + SwSectionData aSection( SectionType::Content, aName ); + + SfxItemSetFixed<RES_FRMATR_BEGIN, RES_FRMATR_END-1> aFrameItemSet( m_xDoc->GetAttrPool() ); + if( !IsNewDoc() ) + Reader::ResetFrameFormatAttrs(aFrameItemSet ); + + if( nGutter ) + { + nGutter = o3tl::convert(nGutter, o3tl::Length::px, o3tl::Length::twip); + } + + SwFormatCol aFormatCol; + + aFormatCol.Init( nCols, nGutter, USHRT_MAX ); + aFrameItemSet.Put( aFormatCol ); + + if( const SvxBrushItem* pItem = aItemSet.GetItemIfSet( RES_BACKGROUND, false) ) + { + aFrameItemSet.Put( *pItem ); + aItemSet.ClearItem( RES_BACKGROUND ); + } + if( const SvxFrameDirectionItem* pItem = aItemSet.GetItemIfSet( RES_FRAMEDIR, false ) ) + { + aFrameItemSet.Put( *pItem ); + aItemSet.ClearItem( RES_FRAMEDIR ); + } + m_xDoc->InsertSwSection( *m_pPam, aSection, nullptr, &aFrameItemSet, false ); + + // Jump to section, if this is requested. + if( JumpToMarks::Region == m_eJumpTo && aName == m_sJmpMark ) + { + m_bChkJumpMark = true; + m_eJumpTo = JumpToMarks::NONE; + } + + SwTextNode* pOldTextNd = + bAppended ? nullptr : m_pPam->GetPoint()->GetNode().GetTextNode(); + + m_pPam->Move( fnMoveBackward ); + + // Move PageDesc and SwFormatBreak attributes of the current node + // to the section's first node. + if( pOldTextNd ) + MovePageDescAttrs( pOldTextNd, m_pPam->GetPoint()->GetNodeIndex(), + true ); + + if( pPostIts ) + { + // Move pending PostIts into the section. + InsertAttrs( std::move(*pPostIts) ); + pPostIts.reset(); + } + + xCntxt->SetSpansSection( true ); + + // Insert a bookmark if its name differs from the section's name only. + if( !aPropInfo.m_aId.isEmpty() && aPropInfo.m_aId==aName ) + aPropInfo.m_aId.clear(); + } + + // Additional attributes must be set as hard ones. + if( bStyleParsed ) + InsertAttrs( aItemSet, aPropInfo, xCntxt.get(), true ); + + PushContext(xCntxt); +} + +void SwHTMLParser::InsertFlyFrame( const SfxItemSet& rItemSet, + HTMLAttrContext *pCntxt, + const OUString& rName ) +{ + RndStdIds eAnchorId = + rItemSet.Get( RES_ANCHOR ).GetAnchorId(); + + // create frame + SwFlyFrameFormat* pFlyFormat = m_xDoc->MakeFlySection( eAnchorId, m_pPam->GetPoint(), + &rItemSet ); + if( !rName.isEmpty() ) + pFlyFormat->SetFormatName( rName ); + + RegisterFlyFrame( pFlyFormat ); + + const SwFormatContent& rFlyContent = pFlyFormat->GetContent(); + const SwNodeIndex& rFlyCntIdx = *rFlyContent.GetContentIdx(); + + SwPosition aNewPos( rFlyCntIdx, SwNodeOffset(1) ); + const HtmlContextFlags nFlags = HtmlContextFlags::ProtectStack|HtmlContextFlags::StripPara; + SaveDocContext( pCntxt, nFlags, &aNewPos ); +} + +void SwHTMLParser::MovePageDescAttrs( SwNode *pSrcNd, + SwNodeOffset nDestIdx, + bool bFormatBreak ) +{ + SwContentNode* pDestContentNd = + m_xDoc->GetNodes()[nDestIdx]->GetContentNode(); + + OSL_ENSURE( pDestContentNd, "Why is the target not a Content-Node?" ); + + if( pSrcNd->IsContentNode() ) + { + SwContentNode* pSrcContentNd = pSrcNd->GetContentNode(); + + const SwFormatPageDesc* pFormatPageDesc = + pSrcContentNd->GetSwAttrSet().GetItemIfSet( RES_PAGEDESC, false ); + if( pFormatPageDesc && pFormatPageDesc->GetPageDesc() ) + { + pDestContentNd->SetAttr( *pFormatPageDesc ); + pSrcContentNd->ResetAttr( RES_PAGEDESC ); + } + if( const SvxFormatBreakItem* pItem = pSrcContentNd->GetSwAttrSet() + .GetItemIfSet( RES_BREAK, false ) ) + { + switch( pItem->GetBreak() ) + { + case SvxBreak::PageBefore: + case SvxBreak::PageAfter: + case SvxBreak::PageBoth: + if( bFormatBreak ) + pDestContentNd->SetAttr( *pItem ); + pSrcContentNd->ResetAttr( RES_BREAK ); + break; + default: + break; + } + } + } + else if( pSrcNd->IsTableNode() ) + { + SwFrameFormat *pFrameFormat = pSrcNd->GetTableNode()->GetTable().GetFrameFormat(); + + if( const SwFormatPageDesc* pItem = pFrameFormat->GetAttrSet(). + GetItemIfSet( RES_PAGEDESC, false ) ) + { + if (pDestContentNd) + pDestContentNd->SetAttr(*pItem); + pFrameFormat->ResetFormatAttr( RES_PAGEDESC ); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmltab.cxx b/sw/source/filter/html/htmltab.cxx new file mode 100644 index 0000000000..424644378b --- /dev/null +++ b/sw/source/filter/html/htmltab.cxx @@ -0,0 +1,5220 @@ +/* -*- 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 <memory> +#include <hintids.hxx> +#include <comphelper/flagguard.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/formatbreakitem.hxx> +#include <editeng/spltitem.hxx> +#include <unotools/configmgr.hxx> +#include <svtools/htmltokn.h> +#include <svtools/htmlkywd.hxx> +#include <svl/numformat.hxx> +#include <svl/urihelper.hxx> +#include <svx/sdrobjectuser.hxx> +#include <svx/svdotext.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +#include <dcontact.hxx> +#include <fmtornt.hxx> +#include <frmfmt.hxx> +#include <fmtfsize.hxx> +#include <fmtsrnd.hxx> +#include <fmtpdsc.hxx> +#include <fmtcntnt.hxx> +#include <fmtanchr.hxx> +#include <fmtlsplt.hxx> +#include <frmatr.hxx> +#include <pam.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentMarkAccess.hxx> +#include <ndtxt.hxx> +#include <shellio.hxx> +#include <poolfmt.hxx> +#include <swtable.hxx> +#include <cellatr.hxx> +#include <htmltbl.hxx> +#include <swtblfmt.hxx> +#include "htmlnum.hxx" +#include "swhtml.hxx" +#include "swcss1.hxx" +#include <txtftn.hxx> +#include <itabenum.hxx> +#include <tblafmt.hxx> +#include <SwStyleNameMapper.hxx> +#include <frameformats.hxx> + +#define NETSCAPE_DFLT_BORDER 1 +#define NETSCAPE_DFLT_CELLSPACING 2 + +using ::editeng::SvxBorderLine; +using namespace ::com::sun::star; + +HTMLOptionEnum<sal_Int16> const aHTMLTableVAlignTable[] = +{ + { OOO_STRING_SVTOOLS_HTML_VA_top, text::VertOrientation::NONE }, + { OOO_STRING_SVTOOLS_HTML_VA_middle, text::VertOrientation::CENTER }, + { OOO_STRING_SVTOOLS_HTML_VA_bottom, text::VertOrientation::BOTTOM }, + { nullptr, 0 } +}; + +// table tags options + +namespace { + +struct HTMLTableOptions +{ + sal_uInt16 nCols; + sal_uInt16 nWidth; + sal_uInt16 nHeight; + sal_uInt16 nCellPadding; + sal_uInt16 nCellSpacing; + sal_uInt16 nBorder; + sal_uInt16 nHSpace; + sal_uInt16 nVSpace; + + SvxAdjust eAdjust; + sal_Int16 eVertOri; + HTMLTableFrame eFrame; + HTMLTableRules eRules; + + bool bPercentWidth : 1; + bool bTableAdjust : 1; + bool bBGColor : 1; + + Color aBorderColor; + Color aBGColor; + + OUString aBGImage, aStyle, aId, aClass, aDir; + + HTMLTableOptions( const HTMLOptions& rOptions, SvxAdjust eParentAdjust ); +}; + +class HTMLTableContext +{ + SwHTMLNumRuleInfo m_aNumRuleInfo; // Numbering valid before the table + + SwTableNode *m_pTableNd; // table node + SwFrameFormat *m_pFrameFormat; // the Fly frame::Frame, containing the table + std::unique_ptr<SwPosition> m_pPos; // position behind the table + + size_t m_nContextStAttrMin; + size_t m_nContextStMin; + + bool m_bRestartPRE : 1; + bool m_bRestartXMP : 1; + bool m_bRestartListing : 1; + + HTMLTableContext(const HTMLTableContext&) = delete; + HTMLTableContext& operator=(const HTMLTableContext&) = delete; + +public: + + std::shared_ptr<HTMLAttrTable> m_xAttrTab; // attributes + + HTMLTableContext( SwPosition *pPs, size_t nCntxtStMin, + size_t nCntxtStAttrMin ) : + m_pTableNd( nullptr ), + m_pFrameFormat( nullptr ), + m_pPos( pPs ), + m_nContextStAttrMin( nCntxtStAttrMin ), + m_nContextStMin( nCntxtStMin ), + m_bRestartPRE( false ), + m_bRestartXMP( false ), + m_bRestartListing( false ), + m_xAttrTab(std::make_shared<HTMLAttrTable>()) + { + memset(m_xAttrTab.get(), 0, sizeof(HTMLAttrTable)); + } + + void SetNumInfo( const SwHTMLNumRuleInfo& rInf ) { m_aNumRuleInfo.Set(rInf); } + const SwHTMLNumRuleInfo& GetNumInfo() const { return m_aNumRuleInfo; }; + + void SavePREListingXMP( SwHTMLParser& rParser ); + void RestorePREListingXMP( SwHTMLParser& rParser ); + + SwPosition *GetPos() const { return m_pPos.get(); } + + void SetTableNode( SwTableNode *pNd ) { m_pTableNd = pNd; } + SwTableNode *GetTableNode() const { return m_pTableNd; } + + void SetFrameFormat( SwFrameFormat *pFormat ) { m_pFrameFormat = pFormat; } + SwFrameFormat *GetFrameFormat() const { return m_pFrameFormat; } + + size_t GetContextStMin() const { return m_nContextStMin; } + size_t GetContextStAttrMin() const { return m_nContextStAttrMin; } +}; + +} + +// Cell content is a linked list with SwStartNodes and +// HTMLTables. + +class HTMLTableCnts +{ + std::unique_ptr<HTMLTableCnts> m_pNext; // next content + + // Only one of the next two pointers must be set! + const SwStartNode *m_pStartNode; // a paragraph + std::shared_ptr<HTMLTable> m_xTable; // a table + + std::shared_ptr<SwHTMLTableLayoutCnts> m_xLayoutInfo; + + bool m_bNoBreak; + + void InitCtor(); + +public: + + explicit HTMLTableCnts(const SwStartNode* pStNd); + explicit HTMLTableCnts(std::shared_ptr<HTMLTable> xTab); + + ~HTMLTableCnts(); // only allowed in ~HTMLTableCell + + // Determine SwStartNode and HTMLTable respectively + const SwStartNode *GetStartNode() const { return m_pStartNode; } + const std::shared_ptr<HTMLTable>& GetTable() const { return m_xTable; } + std::shared_ptr<HTMLTable>& GetTable() { return m_xTable; } + + // Add a new node at the end of the list + void Add( std::unique_ptr<HTMLTableCnts> pNewCnts ); + + // Determine next node + const HTMLTableCnts *Next() const { return m_pNext.get(); } + HTMLTableCnts *Next() { return m_pNext.get(); } + + inline void SetTableBox( SwTableBox *pBox ); + + void SetNoBreak() { m_bNoBreak = true; } + + const std::shared_ptr<SwHTMLTableLayoutCnts>& CreateLayoutInfo(); +}; + +namespace { + +// Cell of a HTML table +class HTMLTableCell +{ + std::shared_ptr<HTMLTableCnts> m_xContents; // cell content + std::shared_ptr<SvxBrushItem> m_xBGBrush; // cell background + std::shared_ptr<SvxBoxItem> m_xBoxItem; + + double m_nValue; + sal_uInt32 m_nNumFormat; + sal_uInt16 m_nRowSpan; // cell ROWSPAN + sal_uInt16 m_nColSpan; // cell COLSPAN + sal_uInt16 m_nWidth; // cell WIDTH + sal_Int16 m_eVertOrient; // vertical alignment of the cell + bool m_bProtected : 1; // cell must not filled + bool m_bRelWidth : 1; // nWidth is given in % + bool m_bHasNumFormat : 1; + bool m_bHasValue : 1; + bool m_bNoWrap : 1; + bool mbCovered : 1; + +public: + + HTMLTableCell(); // new cells always empty + + // Fill a not empty cell + void Set( std::shared_ptr<HTMLTableCnts> const& rCnts, sal_uInt16 nRSpan, sal_uInt16 nCSpan, + sal_Int16 eVertOri, std::shared_ptr<SvxBrushItem> const& rBGBrush, + std::shared_ptr<SvxBoxItem> const& rBoxItem, + bool bHasNumFormat, sal_uInt32 nNumFormat, + bool bHasValue, double nValue, bool bNoWrap, bool bCovered ); + + // Protect an empty 1x1 cell + void SetProtected(); + + // Set/Get cell content + void SetContents(std::shared_ptr<HTMLTableCnts> const& rCnts) { m_xContents = rCnts; } + const std::shared_ptr<HTMLTableCnts>& GetContents() const { return m_xContents; } + + // Set/Get cell ROWSPAN/COLSPAN + void SetRowSpan( sal_uInt16 nRSpan ) { m_nRowSpan = nRSpan; } + sal_uInt16 GetRowSpan() const { return m_nRowSpan; } + + void SetColSpan( sal_uInt16 nCSpan ) { m_nColSpan = nCSpan; } + sal_uInt16 GetColSpan() const { return m_nColSpan; } + + inline void SetWidth( sal_uInt16 nWidth, bool bRelWidth ); + + const std::shared_ptr<SvxBrushItem>& GetBGBrush() const { return m_xBGBrush; } + const std::shared_ptr<SvxBoxItem>& GetBoxItem() const { return m_xBoxItem; } + + inline bool GetNumFormat( sal_uInt32& rNumFormat ) const; + inline bool GetValue( double& rValue ) const; + + sal_Int16 GetVertOri() const { return m_eVertOrient; } + + // Is the cell filled or protected ? + bool IsUsed() const { return m_xContents || m_bProtected; } + + std::unique_ptr<SwHTMLTableLayoutCell> CreateLayoutInfo(); + + bool IsCovered() const { return mbCovered; } +}; + +} + + +namespace { + +// Row of a HTML table +class HTMLTableRow +{ + std::vector<HTMLTableCell> m_aCells; ///< cells of the row + std::unique_ptr<SvxBrushItem> m_xBGBrush; // background of cell from STYLE + + SvxAdjust m_eAdjust; + sal_uInt16 m_nHeight; // options of <TR>/<TD> + sal_uInt16 m_nEmptyRows; // number of empty rows are following + sal_Int16 m_eVertOri; + bool m_bIsEndOfGroup : 1; + bool m_bBottomBorder : 1; // Is there a line after the row? + +public: + + explicit HTMLTableRow( sal_uInt16 nCells ); // cells of the row are empty + + void SetBottomBorder(bool bIn) { m_bBottomBorder = bIn; } + bool GetBottomBorder() const { return m_bBottomBorder; } + + inline void SetHeight( sal_uInt16 nHeight ); + sal_uInt16 GetHeight() const { return m_nHeight; } + + const HTMLTableCell& GetCell(sal_uInt16 nCell) const; + HTMLTableCell& GetCell(sal_uInt16 nCell) + { + return const_cast<HTMLTableCell&>(const_cast<const HTMLTableRow&>(*this).GetCell(nCell)); + } + + void SetAdjust( SvxAdjust eAdj ) { m_eAdjust = eAdj; } + SvxAdjust GetAdjust() const { return m_eAdjust; } + + void SetVertOri( sal_Int16 eV) { m_eVertOri = eV; } + sal_Int16 GetVertOri() const { return m_eVertOri; } + + void SetBGBrush(std::unique_ptr<SvxBrushItem>& rBrush ) { m_xBGBrush = std::move(rBrush); } + const std::unique_ptr<SvxBrushItem>& GetBGBrush() const { return m_xBGBrush; } + + void SetEndOfGroup() { m_bIsEndOfGroup = true; } + bool IsEndOfGroup() const { return m_bIsEndOfGroup; } + + void IncEmptyRows() { m_nEmptyRows++; } + sal_uInt16 GetEmptyRows() const { return m_nEmptyRows; } + + // Expand row by adding empty cells + void Expand( sal_uInt16 nCells, bool bOneCell=false ); + + // Shrink row by deleting empty cells + void Shrink( sal_uInt16 nCells ); +}; + +// Column of a HTML table +class HTMLTableColumn +{ + bool m_bIsEndOfGroup; + + sal_uInt16 m_nWidth; // options of <COL> + bool m_bRelWidth; + + SvxAdjust m_eAdjust; + sal_Int16 m_eVertOri; + + SwFrameFormat *m_aFrameFormats[6]; + + static inline sal_uInt16 GetFrameFormatIdx( bool bBorderLine, + sal_Int16 eVertOri ); + +public: + + bool m_bLeftBorder; // is there a line before the column + + HTMLTableColumn(); + + inline void SetWidth( sal_uInt16 nWidth, bool bRelWidth); + + void SetAdjust( SvxAdjust eAdj ) { m_eAdjust = eAdj; } + SvxAdjust GetAdjust() const { return m_eAdjust; } + + void SetVertOri( sal_Int16 eV) { m_eVertOri = eV; } + sal_Int16 GetVertOri() const { return m_eVertOri; } + + void SetEndOfGroup() { m_bIsEndOfGroup = true; } + bool IsEndOfGroup() const { return m_bIsEndOfGroup; } + + inline void SetFrameFormat( SwFrameFormat *pFormat, bool bBorderLine, + sal_Int16 eVertOri ); + inline SwFrameFormat *GetFrameFormat( bool bBorderLine, + sal_Int16 eVertOri ) const; + + std::unique_ptr<SwHTMLTableLayoutColumn> CreateLayoutInfo(); +}; + +} + +// HTML table +typedef std::vector<SdrObject *> SdrObjects; + +class HTMLTable : public sdr::ObjectUser +{ + OUString m_aId; + OUString m_aStyle; + OUString m_aClass; + OUString m_aDir; + + std::optional<SdrObjects> m_xResizeDrawObjects;// SDR objects + std::optional<std::vector<sal_uInt16>> m_xDrawObjectPercentWidths; // column of draw object and its rel. width + + std::vector<HTMLTableRow> m_aRows; ///< table rows + std::vector<HTMLTableColumn> m_aColumns; ///< table columns + + sal_uInt16 m_nRows; // number of rows + sal_uInt16 m_nCols; // number of columns + sal_uInt16 m_nFilledColumns; // number of filled columns + + sal_uInt16 m_nCurrentRow; // current Row + sal_uInt16 m_nCurrentColumn; // current Column + + sal_uInt16 m_nLeftMargin; // Space to the left margin (from paragraph edge) + sal_uInt16 m_nRightMargin; // Space to the right margin (from paragraph edge) + + sal_uInt16 m_nCellPadding; // Space from border to Text + sal_uInt16 m_nCellSpacing; // Space between two cells + sal_uInt16 m_nHSpace; + sal_uInt16 m_nVSpace; + + sal_uInt16 m_nBoxes; // number of boxes in the table + + const SwStartNode *m_pPrevStartNode; // the Table-Node or the Start-Node of the section before + const SwTable *m_pSwTable; // SW-Table (only on Top-Level) +public: + std::unique_ptr<SwTableBox> m_xBox1; // TableBox, generated when the Top-Level-Table was build +private: + SwTableBoxFormat *m_pBoxFormat; // frame::Frame-Format from SwTableBox + SwTableLineFormat *m_pLineFormat; // frame::Frame-Format from SwTableLine + SwTableLineFormat *m_pLineFrameFormatNoHeight; + std::unique_ptr<SvxBrushItem> m_xBackgroundBrush; // background of the table + std::unique_ptr<SvxBrushItem> m_xInheritedBackgroundBrush; // "inherited" background of the table + const SwStartNode *m_pCaptionStartNode; // Start-Node of the table-caption + //lines for the border + SvxBorderLine m_aTopBorderLine; + SvxBorderLine m_aBottomBorderLine; + SvxBorderLine m_aLeftBorderLine; + SvxBorderLine m_aRightBorderLine; + SvxBorderLine m_aBorderLine; + SvxBorderLine m_aInheritedLeftBorderLine; + SvxBorderLine m_aInheritedRightBorderLine; + bool m_bTopBorder; // is there a line on the top of the table + bool m_bRightBorder; // is there a line on the top right of the table + bool m_bTopAllowed; // is it allowed to set the border? + bool m_bRightAllowed; + bool m_bFillerTopBorder; // gets the left/right filler-cell a border on the + bool m_bFillerBottomBorder; // top or in the bottom + bool m_bInheritedLeftBorder; + bool m_bInheritedRightBorder; + bool m_bBordersSet; // the border is set already + bool m_bForceFrame; + bool m_bTableAdjustOfTag; // comes nTableAdjust from <TABLE>? + sal_uInt32 m_nHeadlineRepeat; // repeating rows + bool m_bIsParentHead; + bool m_bHasParentSection; + bool m_bHasToFly; + bool m_bFixedCols; + bool m_bColSpec; // where there COL(GROUP)-elements? + bool m_bPercentWidth; // width is declared in % + + SwHTMLParser *m_pParser; // the current parser + std::unique_ptr<HTMLTableCnts> m_xParentContents; + + std::unique_ptr<HTMLTableContext> m_pContext; // the context of the table + + std::shared_ptr<SwHTMLTableLayout> m_xLayoutInfo; + + // the following parameters are from the <TABLE>-Tag + sal_uInt16 m_nWidth; // width of the table + sal_uInt16 m_nHeight; // absolute height of the table + SvxAdjust m_eTableAdjust; // drawing::Alignment of the table + sal_Int16 m_eVertOrientation; // Default vertical direction of the cells + sal_uInt16 m_nBorder; // width of the external border + HTMLTableFrame m_eFrame; // frame around the table + HTMLTableRules m_eRules; // frame in the table + bool m_bTopCaption; // Caption of the table + + void InitCtor(const HTMLTableOptions& rOptions); + + // Correction of the Row-Spans for all cells above the chosen cell and the cell itself for the indicated content. The chosen cell gets the Row-Span 1 + void FixRowSpan( sal_uInt16 nRow, sal_uInt16 nCol, const HTMLTableCnts *pCnts ); + + // Protects the chosen cell and the cells among + void ProtectRowSpan( sal_uInt16 nRow, sal_uInt16 nCol, sal_uInt16 nRowSpan ); + + // Looking for the SwStartNodes of the box ahead + // If nRow==nCell==USHRT_MAX, return the last Start-Node of the table. + const SwStartNode* GetPrevBoxStartNode( sal_uInt16 nRow, sal_uInt16 nCell ) const; + + sal_uInt16 GetTopCellSpace( sal_uInt16 nRow ) const; + sal_uInt16 GetBottomCellSpace( sal_uInt16 nRow, sal_uInt16 nRowSpan ) const; + + // Conforming of the frame::Frame-Format of the box + void FixFrameFormat( SwTableBox *pBox, sal_uInt16 nRow, sal_uInt16 nCol, + sal_uInt16 nRowSpan, sal_uInt16 nColSpan, + bool bFirstPara=true, bool bLastPara=true ) const; + + // Create a table with the content (lines/boxes) + void MakeTable_( SwTableBox *pUpper ); + + // Generate a new SwTableBox, which contains a SwStartNode + SwTableBox *NewTableBox( const SwStartNode *pStNd, + SwTableLine *pUpper ) const; + + // Generate a SwTableLine from the cells of the rectangle + // (nTopRow/nLeftCol) inclusive to (nBottomRow/nRightRow) exclusive + SwTableLine *MakeTableLine( SwTableBox *pUpper, + sal_uInt16 nTopRow, sal_uInt16 nLeftCol, + sal_uInt16 nBottomRow, sal_uInt16 nRightCol ); + + // Generate a SwTableBox from the content of the cell + SwTableBox *MakeTableBox( SwTableLine *pUpper, + HTMLTableCnts *pCnts, + sal_uInt16 nTopRow, sal_uInt16 nLeftCol, + sal_uInt16 nBootomRow, sal_uInt16 nRightCol ); + + // Autolayout-Algorithm + + // Setting the border with the help of guidelines of the Parent-Table + void InheritBorders( const HTMLTable *pParent, + sal_uInt16 nRow, sal_uInt16 nCol, + sal_uInt16 nRowSpan, + bool bFirstPara, bool bLastPara ); + + // Inherit the left and the right border of the surrounding table + void InheritVertBorders( const HTMLTable *pParent, + sal_uInt16 nCol, sal_uInt16 nColSpan ); + + // Set the border with the help of the information from the user + void SetBorders(); + + // is the border already set? + bool BordersSet() const { return m_bBordersSet; } + + const std::unique_ptr<SvxBrushItem>& GetBGBrush() const { return m_xBackgroundBrush; } + const std::unique_ptr<SvxBrushItem>& GetInhBGBrush() const { return m_xInheritedBackgroundBrush; } + + sal_uInt16 GetBorderWidth( const SvxBorderLine& rBLine, + bool bWithDistance=false ) const; + + virtual void ObjectInDestruction(const SdrObject& rObject) override; + +public: + + bool m_bFirstCell; // is there a cell created already? + + HTMLTable(SwHTMLParser* pPars, + bool bParHead, bool bHasParentSec, + bool bHasToFly, + const HTMLTableOptions& rOptions); + + virtual ~HTMLTable(); + + // Identifying of a cell + const HTMLTableCell& GetCell(sal_uInt16 nRow, sal_uInt16 nCell) const; + HTMLTableCell& GetCell(sal_uInt16 nRow, sal_uInt16 nCell) + { + return const_cast<HTMLTableCell&>(const_cast<const HTMLTable&>(*this).GetCell(nRow, nCell)); + } + + // set/determine caption + inline void SetCaption( const SwStartNode *pStNd, bool bTop ); + const SwStartNode *GetCaptionStartNode() const { return m_pCaptionStartNode; } + bool IsTopCaption() const { return m_bTopCaption; } + + SvxAdjust GetTableAdjust( bool bAny ) const + { + return (m_bTableAdjustOfTag || bAny) ? m_eTableAdjust : SvxAdjust::End; + } + + sal_uInt16 GetHSpace() const { return m_nHSpace; } + sal_uInt16 GetVSpace() const { return m_nVSpace; } + + // get inherited drawing::Alignment of rows and column + SvxAdjust GetInheritedAdjust() const; + sal_Int16 GetInheritedVertOri() const; + + // Insert a cell on the current position + void InsertCell( std::shared_ptr<HTMLTableCnts> const& rCnts, sal_uInt16 nRowSpan, sal_uInt16 nColSpan, + sal_uInt16 nWidth, bool bRelWidth, sal_uInt16 nHeight, + sal_Int16 eVertOri, std::shared_ptr<SvxBrushItem> const& rBGBrushItem, + std::shared_ptr<SvxBoxItem> const& rBoxItem, + bool bHasNumFormat, sal_uInt32 nNumFormat, + bool bHasValue, double nValue, bool bNoWrap ); + + // announce the start/end of a new row + void OpenRow(SvxAdjust eAdjust, sal_Int16 eVertOri, std::unique_ptr<SvxBrushItem>& rBGBrush); + void CloseRow( bool bEmpty ); + + // announce the end of a new section + inline void CloseSection( bool bHead ); + + // announce the end of a column-group + inline void CloseColGroup( sal_uInt16 nSpan, sal_uInt16 nWidth, bool bRelWidth, + SvxAdjust eAdjust, sal_Int16 eVertOri ); + + // insert a new column + void InsertCol( sal_uInt16 nSpan, sal_uInt16 nWidth, bool bRelWidth, + SvxAdjust eAdjust, sal_Int16 eVertOri ); + + // End a table definition (needs to be called for every table) + void CloseTable(); + + // Construct a SwTable (including child tables) + void MakeTable( SwTableBox *pUpper, sal_uInt16 nAbsAvail, + sal_uInt16 nRelAvail=0, sal_uInt16 nAbsLeftSpace=0, + sal_uInt16 nAbsRightSpace=0, sal_uInt16 nInhAbsSpace=0 ); + + bool IsNewDoc() const { return m_pParser->IsNewDoc(); } + + void SetHasParentSection( bool bSet ) { m_bHasParentSection = bSet; } + bool HasParentSection() const { return m_bHasParentSection; } + + void SetParentContents(std::unique_ptr<HTMLTableCnts> pCnts) { m_xParentContents = std::move(pCnts); } + std::unique_ptr<HTMLTableCnts>& GetParentContents() { return m_xParentContents; } + + void MakeParentContents(); + + bool HasToFly() const { return m_bHasToFly; } + + void SetTable( const SwStartNode *pStNd, std::unique_ptr<HTMLTableContext> pCntxt, + sal_uInt16 nLeft, sal_uInt16 nRight, + const SwTable *pSwTab=nullptr, bool bFrcFrame=false ); + + HTMLTableContext *GetContext() const { return m_pContext.get(); } + + const std::shared_ptr<SwHTMLTableLayout>& CreateLayoutInfo(); + + bool HasColTags() const { return m_bColSpec; } + + sal_uInt16 IncGrfsThatResize() { return m_pSwTable ? const_cast<SwTable *>(m_pSwTable)->IncGrfsThatResize() : 0; } + + void RegisterDrawObject( SdrObject *pObj, sal_uInt8 nPercentWidth ); + + const SwTable *GetSwTable() const { return m_pSwTable; } + + void SetBGBrush(const SvxBrushItem& rBrush) { m_xBackgroundBrush.reset(new SvxBrushItem(rBrush)); } + + const OUString& GetId() const { return m_aId; } + const OUString& GetClass() const { return m_aClass; } + const OUString& GetStyle() const { return m_aStyle; } + const OUString& GetDirection() const { return m_aDir; } + + void IncBoxCount() { m_nBoxes++; } + bool IsOverflowing() const { return m_nBoxes > 64000; } +}; + +void HTMLTableCnts::InitCtor() +{ + m_pNext = nullptr; + m_xLayoutInfo.reset(); + m_bNoBreak = false; +} + +HTMLTableCnts::HTMLTableCnts(const SwStartNode* pStNd) + : m_pStartNode(pStNd) +{ + InitCtor(); +} + +HTMLTableCnts::HTMLTableCnts(std::shared_ptr<HTMLTable> xTab) + : m_pStartNode(nullptr) + , m_xTable(std::move(xTab)) +{ + InitCtor(); +} + +HTMLTableCnts::~HTMLTableCnts() +{ + m_xTable.reset(); // we don't need the tables anymore + m_pNext.reset(); +} + +void HTMLTableCnts::Add( std::unique_ptr<HTMLTableCnts> pNewCnts ) +{ + HTMLTableCnts *pCnts = this; + + while( pCnts->m_pNext ) + pCnts = pCnts->m_pNext.get(); + + pCnts->m_pNext = std::move(pNewCnts); +} + +inline void HTMLTableCnts::SetTableBox( SwTableBox *pBox ) +{ + OSL_ENSURE(m_xLayoutInfo, "There is no layout info"); + if (m_xLayoutInfo) + m_xLayoutInfo->SetTableBox(pBox); +} + +const std::shared_ptr<SwHTMLTableLayoutCnts>& HTMLTableCnts::CreateLayoutInfo() +{ + if (!m_xLayoutInfo) + { + std::shared_ptr<SwHTMLTableLayoutCnts> xNextInfo; + if (m_pNext) + xNextInfo = m_pNext->CreateLayoutInfo(); + std::shared_ptr<SwHTMLTableLayout> xTableInfo; + if (m_xTable) + xTableInfo = m_xTable->CreateLayoutInfo(); + m_xLayoutInfo = std::make_shared<SwHTMLTableLayoutCnts>(m_pStartNode, xTableInfo, m_bNoBreak, xNextInfo); + } + + return m_xLayoutInfo; +} + +HTMLTableCell::HTMLTableCell(): + m_nValue(0), + m_nNumFormat(0), + m_nRowSpan(1), + m_nColSpan(1), + m_nWidth( 0 ), + m_eVertOrient( text::VertOrientation::NONE ), + m_bProtected(false), + m_bRelWidth( false ), + m_bHasNumFormat(false), + m_bHasValue(false), + m_bNoWrap(false), + mbCovered(false) +{} + +void HTMLTableCell::Set( std::shared_ptr<HTMLTableCnts> const& rCnts, sal_uInt16 nRSpan, sal_uInt16 nCSpan, + sal_Int16 eVert, std::shared_ptr<SvxBrushItem> const& rBrush, + std::shared_ptr<SvxBoxItem> const& rBoxItem, + bool bHasNF, sal_uInt32 nNF, bool bHasV, double nVal, + bool bNWrap, bool bCovered ) +{ + m_xContents = rCnts; + m_nRowSpan = nRSpan; + m_nColSpan = nCSpan; + m_bProtected = false; + m_eVertOrient = eVert; + m_xBGBrush = rBrush; + m_xBoxItem = rBoxItem; + + m_bHasNumFormat = bHasNF; + m_bHasValue = bHasV; + m_nNumFormat = nNF; + m_nValue = nVal; + + m_bNoWrap = bNWrap; + mbCovered = bCovered; +} + +inline void HTMLTableCell::SetWidth( sal_uInt16 nWdth, bool bRelWdth ) +{ + m_nWidth = nWdth; + m_bRelWidth = bRelWdth; +} + +void HTMLTableCell::SetProtected() +{ + // The content of this cell doesn't have to be anchored anywhere else, + // since they're not gonna be deleted + + m_xContents.reset(); + + // Copy background color + if (m_xBGBrush) + m_xBGBrush = std::make_shared<SvxBrushItem>(*m_xBGBrush); + + m_nRowSpan = 1; + m_nColSpan = 1; + m_bProtected = true; +} + +inline bool HTMLTableCell::GetNumFormat( sal_uInt32& rNumFormat ) const +{ + rNumFormat = m_nNumFormat; + return m_bHasNumFormat; +} + +inline bool HTMLTableCell::GetValue( double& rValue ) const +{ + rValue = m_nValue; + return m_bHasValue; +} + +std::unique_ptr<SwHTMLTableLayoutCell> HTMLTableCell::CreateLayoutInfo() +{ + std::shared_ptr<SwHTMLTableLayoutCnts> xCntInfo; + if (m_xContents) + xCntInfo = m_xContents->CreateLayoutInfo(); + return std::unique_ptr<SwHTMLTableLayoutCell>(new SwHTMLTableLayoutCell(xCntInfo, m_nRowSpan, m_nColSpan, m_nWidth, + m_bRelWidth, m_bNoWrap)); +} + +HTMLTableRow::HTMLTableRow(sal_uInt16 const nCells) + : m_aCells(nCells) + , m_eAdjust(SvxAdjust::End) + , m_nHeight(0) + , m_nEmptyRows(0) + , m_eVertOri(text::VertOrientation::TOP) + , m_bIsEndOfGroup(false) + , m_bBottomBorder(false) +{ + assert(nCells == m_aCells.size() && + "wrong Cell count in new HTML table row"); +} + +inline void HTMLTableRow::SetHeight( sal_uInt16 nHght ) +{ + if( nHght > m_nHeight ) + m_nHeight = nHght; +} + +const HTMLTableCell& HTMLTableRow::GetCell(sal_uInt16 nCell) const +{ + OSL_ENSURE( nCell < m_aCells.size(), + "invalid cell index in HTML table row" ); + return m_aCells.at(nCell); +} + +void HTMLTableRow::Expand( sal_uInt16 nCells, bool bOneCell ) +{ + // This row will be filled with a single cell if bOneCell is set. + // This will only work for rows that don't allow adding cells! + + sal_uInt16 nColSpan = nCells - m_aCells.size(); + for (sal_uInt16 i = m_aCells.size(); i < nCells; ++i) + { + m_aCells.emplace_back(); + if (bOneCell) + m_aCells.back().SetColSpan(nColSpan); + --nColSpan; + } + + OSL_ENSURE(nCells == m_aCells.size(), + "wrong Cell count in expanded HTML table row"); +} + +void HTMLTableRow::Shrink( sal_uInt16 nCells ) +{ + OSL_ENSURE(nCells < m_aCells.size(), "number of cells too large"); + +#if OSL_DEBUG_LEVEL > 0 + sal_uInt16 const nEnd = m_aCells.size(); +#endif + // The colspan of empty cells at the end has to be fixed to the new + // number of cells. + sal_uInt16 i=nCells; + while( i ) + { + HTMLTableCell& rCell = m_aCells[--i]; + if (!rCell.GetContents()) + { +#if OSL_DEBUG_LEVEL > 0 + OSL_ENSURE( rCell.GetColSpan() == nEnd - i, + "invalid col span for empty cell at row end" ); +#endif + rCell.SetColSpan( nCells-i); + } + else + break; + } +#if OSL_DEBUG_LEVEL > 0 + for( i=nCells; i<nEnd; i++ ) + { + HTMLTableCell& rCell = m_aCells[i]; + OSL_ENSURE( rCell.GetRowSpan() == 1, + "RowSpan of to be deleted cell is wrong" ); + OSL_ENSURE( rCell.GetColSpan() == nEnd - i, + "ColSpan of to be deleted cell is wrong" ); + OSL_ENSURE( !rCell.GetContents(), "To be deleted cell has content" ); + } +#endif + + m_aCells.erase(m_aCells.begin() + nCells, m_aCells.end()); +} + +HTMLTableColumn::HTMLTableColumn(): + m_bIsEndOfGroup(false), + m_nWidth(0), m_bRelWidth(false), + m_eAdjust(SvxAdjust::End), m_eVertOri(text::VertOrientation::TOP), + m_bLeftBorder(false) +{ + for(SwFrameFormat* & rp : m_aFrameFormats) + rp = nullptr; +} + +inline void HTMLTableColumn::SetWidth( sal_uInt16 nWdth, bool bRelWdth ) +{ + if( m_bRelWidth==bRelWdth ) + { + if( nWdth > m_nWidth ) + m_nWidth = nWdth; + } + else + m_nWidth = nWdth; + m_bRelWidth = bRelWdth; +} + +inline std::unique_ptr<SwHTMLTableLayoutColumn> HTMLTableColumn::CreateLayoutInfo() +{ + return std::unique_ptr<SwHTMLTableLayoutColumn>(new SwHTMLTableLayoutColumn( m_nWidth, m_bRelWidth, m_bLeftBorder )); +} + +inline sal_uInt16 HTMLTableColumn::GetFrameFormatIdx( bool bBorderLine, + sal_Int16 eVertOrient ) +{ + OSL_ENSURE( text::VertOrientation::TOP != eVertOrient, "Top is not allowed" ); + sal_uInt16 n = bBorderLine ? 3 : 0; + switch( eVertOrient ) + { + case text::VertOrientation::CENTER: n+=1; break; + case text::VertOrientation::BOTTOM: n+=2; break; + default: + ; + } + return n; +} + +inline void HTMLTableColumn::SetFrameFormat( SwFrameFormat *pFormat, bool bBorderLine, + sal_Int16 eVertOrient ) +{ + m_aFrameFormats[GetFrameFormatIdx(bBorderLine,eVertOrient)] = pFormat; +} + +inline SwFrameFormat *HTMLTableColumn::GetFrameFormat( bool bBorderLine, + sal_Int16 eVertOrient ) const +{ + return m_aFrameFormats[GetFrameFormatIdx(bBorderLine,eVertOrient)]; +} + +void HTMLTable::InitCtor(const HTMLTableOptions& rOptions) +{ + m_nRows = 0; + m_nCurrentRow = 0; m_nCurrentColumn = 0; + + m_pBoxFormat = nullptr; m_pLineFormat = nullptr; + m_pLineFrameFormatNoHeight = nullptr; + m_xInheritedBackgroundBrush.reset(); + + m_pPrevStartNode = nullptr; + m_pSwTable = nullptr; + + m_bTopBorder = false; m_bRightBorder = false; + m_bTopAllowed = true; m_bRightAllowed = true; + m_bFillerTopBorder = false; m_bFillerBottomBorder = false; + m_bInheritedLeftBorder = false; m_bInheritedRightBorder = false; + m_bBordersSet = false; + m_bForceFrame = false; + m_nHeadlineRepeat = 0; + + m_nLeftMargin = 0; + m_nRightMargin = 0; + + const Color& rBorderColor = rOptions.aBorderColor; + + tools::Long nBorderOpt = static_cast<tools::Long>(rOptions.nBorder); + tools::Long nPWidth = nBorderOpt==USHRT_MAX ? NETSCAPE_DFLT_BORDER + : nBorderOpt; + tools::Long nPHeight = nBorderOpt==USHRT_MAX ? 0 : nBorderOpt; + SvxCSS1Parser::PixelToTwip( nPWidth, nPHeight ); + + // nBorder tells the width of the border as it's used in the width calculation of NetScape + // If pOption->nBorder == USHRT_MAX, there wasn't a BORDER option given + // Nonetheless, a 1 pixel wide border will be used for width calculation + m_nBorder = o3tl::narrowing<sal_uInt16>(nPWidth); + if( nBorderOpt==USHRT_MAX ) + nPWidth = 0; + + if ( rOptions.nCellSpacing != 0 ) + { + m_aTopBorderLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE); + } + m_aTopBorderLine.SetWidth( nPHeight ); + m_aTopBorderLine.SetColor( rBorderColor ); + m_aBottomBorderLine = m_aTopBorderLine; + + if( nPWidth == nPHeight ) + { + m_aLeftBorderLine = m_aTopBorderLine; + } + else + { + if ( rOptions.nCellSpacing != 0 ) + { + m_aLeftBorderLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE); + } + m_aLeftBorderLine.SetWidth( nPWidth ); + m_aLeftBorderLine.SetColor( rBorderColor ); + } + m_aRightBorderLine = m_aLeftBorderLine; + + if( rOptions.nCellSpacing != 0 ) + m_aBorderLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE); + m_aBorderLine.SetWidth(SvxBorderLineWidth::Hairline); + m_aBorderLine.SetColor( rBorderColor ); + + if( m_nCellPadding ) + { + if( m_nCellPadding==USHRT_MAX ) + m_nCellPadding = MIN_BORDER_DIST; // default + else + { + m_nCellPadding = SwHTMLParser::ToTwips( m_nCellPadding ); + if( m_nCellPadding<MIN_BORDER_DIST ) + m_nCellPadding = MIN_BORDER_DIST; + } + } + if( m_nCellSpacing ) + { + if( m_nCellSpacing==USHRT_MAX ) + m_nCellSpacing = NETSCAPE_DFLT_CELLSPACING; + m_nCellSpacing = SwHTMLParser::ToTwips( m_nCellSpacing ); + } + + nPWidth = rOptions.nHSpace; + nPHeight = rOptions.nVSpace; + SvxCSS1Parser::PixelToTwip( nPWidth, nPHeight ); + m_nHSpace = o3tl::narrowing<sal_uInt16>(nPWidth); + m_nVSpace = o3tl::narrowing<sal_uInt16>(nPHeight); + + m_bColSpec = false; + + m_xBackgroundBrush.reset(m_pParser->CreateBrushItem( + rOptions.bBGColor ? &(rOptions.aBGColor) : nullptr, + rOptions.aBGImage, OUString(), OUString(), OUString())); + + m_pContext = nullptr; + m_xParentContents.reset(); + + m_aId = rOptions.aId; + m_aClass = rOptions.aClass; + m_aStyle = rOptions.aStyle; + m_aDir = rOptions.aDir; +} + +HTMLTable::HTMLTable(SwHTMLParser* pPars, + bool bParHead, + bool bHasParentSec, bool bHasToFlw, + const HTMLTableOptions& rOptions) : + m_aColumns(rOptions.nCols), + m_nCols(rOptions.nCols), + m_nFilledColumns( 0 ), + m_nCellPadding(rOptions.nCellPadding), + m_nCellSpacing(rOptions.nCellSpacing), + m_nBoxes( 1 ), + m_pCaptionStartNode( nullptr ), + m_bTableAdjustOfTag( rOptions.bTableAdjust ), + m_bIsParentHead( bParHead ), + m_bHasParentSection( bHasParentSec ), + m_bHasToFly( bHasToFlw ), + m_bFixedCols( rOptions.nCols>0 ), + m_bPercentWidth( rOptions.bPercentWidth ), + m_pParser( pPars ), + m_nWidth( rOptions.nWidth ), + m_nHeight( rOptions.nHeight ), + m_eTableAdjust( rOptions.eAdjust ), + m_eVertOrientation( rOptions.eVertOri ), + m_eFrame( rOptions.eFrame ), + m_eRules( rOptions.eRules ), + m_bTopCaption( false ), + m_bFirstCell(true) +{ + InitCtor(rOptions); + m_pParser->RegisterHTMLTable(this); +} + +void SwHTMLParser::DeregisterHTMLTable(HTMLTable* pOld) +{ + if (pOld->m_xBox1) + m_aOrphanedTableBoxes.emplace_back(std::move(pOld->m_xBox1)); + std::erase(m_aTables, pOld); +} + +SwDoc* SwHTMLParser::GetDoc() const +{ + return m_xDoc.get(); +} + +bool SwHTMLParser::IsReqIF() const +{ + return m_bReqIF; +} + +// if any m_xResizeDrawObjects members are deleted during parse, remove them +// from m_xResizeDrawObjects and m_xDrawObjectPercentWidths +void HTMLTable::ObjectInDestruction(const SdrObject& rObject) +{ + auto it = std::find(m_xResizeDrawObjects->begin(), m_xResizeDrawObjects->end(), &rObject); + assert(it != m_xResizeDrawObjects->end()); + auto nIndex = std::distance(m_xResizeDrawObjects->begin(), it); + m_xResizeDrawObjects->erase(it); + auto otherit = m_xDrawObjectPercentWidths->begin() + nIndex * 3; + m_xDrawObjectPercentWidths->erase(otherit, otherit + 3); +} + +HTMLTable::~HTMLTable() +{ + m_pParser->DeregisterHTMLTable(this); + + if (m_xResizeDrawObjects) + { + size_t nCount = m_xResizeDrawObjects->size(); + for (size_t i = 0; i < nCount; ++i) + { + SdrObject *pObj = (*m_xResizeDrawObjects)[i]; + pObj->RemoveObjectUser(*this); + } + m_xResizeDrawObjects.reset(); + } + + m_xDrawObjectPercentWidths.reset(); + + m_pContext.reset(); + + // pLayoutInfo has either already been deleted or is now owned by SwTable +} + +const std::shared_ptr<SwHTMLTableLayout>& HTMLTable::CreateLayoutInfo() +{ + sal_uInt16 nW = m_bPercentWidth ? m_nWidth : SwHTMLParser::ToTwips( m_nWidth ); + + sal_uInt16 nBorderWidth = GetBorderWidth( m_aBorderLine, true ); + sal_uInt16 nLeftBorderWidth = + m_aColumns[0].m_bLeftBorder ? GetBorderWidth(m_aLeftBorderLine, true) : 0; + sal_uInt16 nRightBorderWidth = + m_bRightBorder ? GetBorderWidth( m_aRightBorderLine, true ) : 0; + + m_xLayoutInfo = std::make_shared<SwHTMLTableLayout>( + m_pSwTable, + m_nRows, m_nCols, m_bFixedCols, m_bColSpec, + nW, m_bPercentWidth, m_nBorder, m_nCellPadding, + m_nCellSpacing, m_eTableAdjust, + m_nLeftMargin, m_nRightMargin, + nBorderWidth, nLeftBorderWidth, nRightBorderWidth); + + bool bExportable = true; + sal_uInt16 i; + for( i=0; i<m_nRows; i++ ) + { + HTMLTableRow& rRow = m_aRows[i]; + for( sal_uInt16 j=0; j<m_nCols; j++ ) + { + m_xLayoutInfo->SetCell(rRow.GetCell(j).CreateLayoutInfo(), i, j); + SwHTMLTableLayoutCell* pLayoutCell = m_xLayoutInfo->GetCell(i, j ); + + if( bExportable ) + { + const std::shared_ptr<SwHTMLTableLayoutCnts>& rLayoutCnts = + pLayoutCell->GetContents(); + bExportable = !rLayoutCnts || + (rLayoutCnts->GetStartNode() && !rLayoutCnts->GetNext()); + } + } + } + + m_xLayoutInfo->SetExportable( bExportable ); + + for( i=0; i<m_nCols; i++ ) + m_xLayoutInfo->SetColumn(m_aColumns[i].CreateLayoutInfo(), i); + + return m_xLayoutInfo; +} + +inline void HTMLTable::SetCaption( const SwStartNode *pStNd, bool bTop ) +{ + m_pCaptionStartNode = pStNd; + m_bTopCaption = bTop; +} + +void HTMLTable::FixRowSpan( sal_uInt16 nRow, sal_uInt16 nCol, + const HTMLTableCnts *pCnts ) +{ + sal_uInt16 nRowSpan=1; + while (true) + { + HTMLTableCell& rCell = GetCell(nRow, nCol); + if (rCell.GetContents().get() != pCnts) + break; + rCell.SetRowSpan(nRowSpan); + if (m_xLayoutInfo) + m_xLayoutInfo->GetCell(nRow,nCol)->SetRowSpan(nRowSpan); + + if( !nRow ) break; + nRowSpan++; nRow--; + } +} + +void HTMLTable::ProtectRowSpan( sal_uInt16 nRow, sal_uInt16 nCol, sal_uInt16 nRowSpan ) +{ + for( sal_uInt16 i=0; i<nRowSpan; i++ ) + { + GetCell(nRow+i,nCol).SetProtected(); + if (m_xLayoutInfo) + m_xLayoutInfo->GetCell(nRow+i,nCol)->SetProtected(); + } +} + +// Search the SwStartNode of the last used predecessor box +const SwStartNode* HTMLTable::GetPrevBoxStartNode( sal_uInt16 nRow, sal_uInt16 nCol ) const +{ + const HTMLTableCnts *pPrevCnts = nullptr; + + if( 0==nRow ) + { + // always the predecessor cell + if( nCol>0 ) + pPrevCnts = GetCell(0, nCol - 1).GetContents().get(); + else + return m_pPrevStartNode; + } + else if( USHRT_MAX==nRow && USHRT_MAX==nCol ) + // contents of preceding cell + pPrevCnts = GetCell(m_nRows - 1, m_nCols - 1).GetContents().get(); + else + { + sal_uInt16 i; + const HTMLTableRow& rPrevRow = m_aRows[nRow-1]; + + // maybe a cell in the current row + i = nCol; + while( i ) + { + i--; + if( 1 == rPrevRow.GetCell(i).GetRowSpan() ) + { + pPrevCnts = GetCell(nRow, i).GetContents().get(); + break; + } + } + + // otherwise the last filled cell of the row before + if( !pPrevCnts ) + { + i = m_nCols; + while( !pPrevCnts && i ) + { + i--; + pPrevCnts = rPrevRow.GetCell(i).GetContents().get(); + } + } + } + OSL_ENSURE( pPrevCnts, "No previous filled cell found" ); + if( !pPrevCnts ) + { + pPrevCnts = GetCell(0, 0).GetContents().get(); + if( !pPrevCnts ) + return m_pPrevStartNode; + } + + while( pPrevCnts->Next() ) + pPrevCnts = pPrevCnts->Next(); + + const SwStartNode* pRet = pPrevCnts->GetStartNode(); + if (pRet) + return pRet; + HTMLTable* pTable = pPrevCnts->GetTable().get(); + if (!pTable) + return nullptr; + return pTable->GetPrevBoxStartNode(USHRT_MAX, USHRT_MAX); +} + +sal_uInt16 HTMLTable::GetTopCellSpace( sal_uInt16 nRow ) const +{ + sal_uInt16 nSpace = m_nCellPadding; + + if( nRow == 0 ) + { + nSpace += m_nBorder + m_nCellSpacing; + } + + return nSpace; +} + +sal_uInt16 HTMLTable::GetBottomCellSpace( sal_uInt16 nRow, sal_uInt16 nRowSpan ) const +{ + sal_uInt16 nSpace = m_nCellSpacing + m_nCellPadding; + + if( nRow+nRowSpan == m_nRows ) + { + nSpace = nSpace + m_nBorder; + } + + return nSpace; +} + +void HTMLTable::FixFrameFormat( SwTableBox *pBox, + sal_uInt16 nRow, sal_uInt16 nCol, + sal_uInt16 nRowSpan, sal_uInt16 nColSpan, + bool bFirstPara, bool bLastPara ) const +{ + SwFrameFormat *pFrameFormat = nullptr; // frame::Frame format + sal_Int16 eVOri = text::VertOrientation::NONE; + const SvxBrushItem *pBGBrushItem = nullptr; // background + std::shared_ptr<SvxBoxItem> pBoxItem; + bool bTopLine = false, bBottomLine = false, bLastBottomLine = false; + bool bReUsable = false; // Format reusable? + sal_uInt16 nEmptyRows = 0; + bool bHasNumFormat = false; + bool bHasValue = false; + sal_uInt32 nNumFormat = 0; + double nValue = 0.0; + + const HTMLTableColumn& rColumn = m_aColumns[nCol]; + + if( pBox->GetSttNd() ) + { + // Determine background color/graphic + const HTMLTableCell& rCell = GetCell(nRow, nCol); + pBoxItem = rCell.GetBoxItem(); + pBGBrushItem = rCell.GetBGBrush().get(); + if( !pBGBrushItem ) + { + // If a cell spans multiple rows, a background to that row should be copied to the cell. + if (nRowSpan > 1) + { + pBGBrushItem = m_aRows[nRow].GetBGBrush().get(); + } + } + + bTopLine = 0==nRow && m_bTopBorder && bFirstPara; + if (m_aRows[nRow+nRowSpan-1].GetBottomBorder() && bLastPara) + { + nEmptyRows = m_aRows[nRow+nRowSpan-1].GetEmptyRows(); + if( nRow+nRowSpan == m_nRows ) + bLastBottomLine = true; + else + bBottomLine = true; + } + + eVOri = rCell.GetVertOri(); + bHasNumFormat = rCell.GetNumFormat( nNumFormat ); + if( bHasNumFormat ) + bHasValue = rCell.GetValue( nValue ); + + if( nColSpan==1 && !bTopLine && !bLastBottomLine && !nEmptyRows && + !pBGBrushItem && !bHasNumFormat && !pBoxItem) + { + pFrameFormat = rColumn.GetFrameFormat( bBottomLine, eVOri ); + bReUsable = !pFrameFormat; + } + } + + if( !pFrameFormat ) + { + pFrameFormat = pBox->ClaimFrameFormat(); + + // calculate width of the box + SwTwips nFrameWidth = static_cast<SwTwips>(m_xLayoutInfo->GetColumn(nCol) + ->GetRelColWidth()); + for( sal_uInt16 i=1; i<nColSpan; i++ ) + nFrameWidth += static_cast<SwTwips>(m_xLayoutInfo->GetColumn(nCol+i) + ->GetRelColWidth()); + + // Only set the border on edit boxes. + // On setting the upper and lower border, keep in mind if + // it's the first or the last paragraph of the cell + if( pBox->GetSttNd() ) + { + bool bSet = (m_nCellPadding > 0); + + SvxBoxItem aBoxItem( RES_BOX ); + tools::Long nInnerFrameWidth = nFrameWidth; + + if( bTopLine ) + { + aBoxItem.SetLine( &m_aTopBorderLine, SvxBoxItemLine::TOP ); + bSet = true; + } + if( bLastBottomLine ) + { + aBoxItem.SetLine( &m_aBottomBorderLine, SvxBoxItemLine::BOTTOM ); + bSet = true; + } + else if( bBottomLine ) + { + if( nEmptyRows && !m_aBorderLine.GetInWidth() ) + { + // For now, empty rows can only be emulated by thick lines, if it's a single line + SvxBorderLine aThickBorderLine( m_aBorderLine ); + + sal_uInt16 nBorderWidth = m_aBorderLine.GetOutWidth(); + nBorderWidth *= (nEmptyRows + 1); + aThickBorderLine.SetBorderLineStyle(SvxBorderLineStyle::SOLID); + aThickBorderLine.SetWidth( nBorderWidth ); + aBoxItem.SetLine( &aThickBorderLine, SvxBoxItemLine::BOTTOM ); + } + else + { + aBoxItem.SetLine( &m_aBorderLine, SvxBoxItemLine::BOTTOM ); + } + bSet = true; + } + if (m_aColumns[nCol].m_bLeftBorder) + { + const SvxBorderLine& rBorderLine = + 0==nCol ? m_aLeftBorderLine : m_aBorderLine; + aBoxItem.SetLine( &rBorderLine, SvxBoxItemLine::LEFT ); + nInnerFrameWidth -= GetBorderWidth( rBorderLine ); + bSet = true; + } + if( m_bRightBorder ) + { + aBoxItem.SetLine( &m_aRightBorderLine, SvxBoxItemLine::RIGHT ); + nInnerFrameWidth -= GetBorderWidth( m_aRightBorderLine ); + bSet = true; + } + + if (pBoxItem) + { + pFrameFormat->SetFormatAttr( *pBoxItem ); + } + else if (bSet) + { + // BorderDist is not part of a cell with fixed width + sal_uInt16 nBDist = static_cast< sal_uInt16 >( + (2*m_nCellPadding <= nInnerFrameWidth) ? m_nCellPadding + : (nInnerFrameWidth / 2) ); + // We only set the item if there's a border or a border distance + // If the latter is missing, there's gonna be a border and we'll have to set the distance + aBoxItem.SetAllDistances(nBDist ? nBDist : MIN_BORDER_DIST); + pFrameFormat->SetFormatAttr( aBoxItem ); + } + else + pFrameFormat->ResetFormatAttr( RES_BOX ); + + if( pBGBrushItem ) + { + pFrameFormat->SetFormatAttr( *pBGBrushItem ); + } + else + pFrameFormat->ResetFormatAttr( RES_BACKGROUND ); + + // Only set format if there's a value or the box is empty + if( bHasNumFormat && (bHasValue || pBox->IsEmpty()) ) + { + bool bLock = pFrameFormat->GetDoc()->GetNumberFormatter() + ->IsTextFormat( nNumFormat ); + SfxItemSetFixed<RES_BOXATR_FORMAT, RES_BOXATR_VALUE> + aItemSet( *pFrameFormat->GetAttrSet().GetPool() ); + SvxAdjust eAdjust = SvxAdjust::End; + SwContentNode *pCNd = nullptr; + if( !bLock ) + { + const SwStartNode *pSttNd = pBox->GetSttNd(); + pCNd = pSttNd->GetNodes()[pSttNd->GetIndex()+1] + ->GetContentNode(); + const SvxAdjustItem *pItem; + if( pCNd && pCNd->HasSwAttrSet() && + (pItem = pCNd->GetpSwAttrSet()->GetItemIfSet( + RES_PARATR_ADJUST, false )) ) + { + eAdjust = pItem->GetAdjust(); + } + } + aItemSet.Put( SwTableBoxNumFormat(nNumFormat) ); + if( bHasValue ) + aItemSet.Put( SwTableBoxValue(nValue) ); + + if( bLock ) + pFrameFormat->LockModify(); + pFrameFormat->SetFormatAttr( aItemSet ); + if( bLock ) + pFrameFormat->UnlockModify(); + else if( pCNd && SvxAdjust::End != eAdjust ) + { + SvxAdjustItem aAdjItem( eAdjust, RES_PARATR_ADJUST ); + pCNd->SetAttr( aAdjItem ); + } + } + else + pFrameFormat->ResetFormatAttr( RES_BOXATR_FORMAT ); + + OSL_ENSURE( eVOri != text::VertOrientation::TOP, "text::VertOrientation::TOP is not allowed!" ); + if( text::VertOrientation::NONE != eVOri ) + { + pFrameFormat->SetFormatAttr( SwFormatVertOrient( 0, eVOri ) ); + } + else + pFrameFormat->ResetFormatAttr( RES_VERT_ORIENT ); + + if( bReUsable ) + const_cast<HTMLTableColumn&>(rColumn).SetFrameFormat(pFrameFormat, bBottomLine, eVOri); + } + else + { + pFrameFormat->ResetFormatAttr( RES_BOX ); + pFrameFormat->ResetFormatAttr( RES_BACKGROUND ); + pFrameFormat->ResetFormatAttr( RES_VERT_ORIENT ); + pFrameFormat->ResetFormatAttr( RES_BOXATR_FORMAT ); + } + + if (m_pParser->IsReqIF()) + { + // ReqIF case, cells would have no formatting. Apply the default + // table autoformat on them, so imported and UI-created tables look + // the same. + SwTableAutoFormatTable& rTable = m_pParser->GetDoc()->GetTableStyles(); + SwTableAutoFormat* pTableFormat = rTable.FindAutoFormat( + SwStyleNameMapper::GetUIName(RES_POOLTABLESTYLE_DEFAULT, OUString())); + if (pTableFormat) + { + sal_uInt8 nPos = SwTableAutoFormat::CountPos(nCol, m_nCols, nRow, m_nRows); + const SfxItemSet& rAttrSet = pFrameFormat->GetAttrSet(); + std::unique_ptr<SvxBoxItem> pOldBoxItem; + if (const SvxBoxItem* pBoxItem2 = rAttrSet.GetItemIfSet(RES_BOX)) + pOldBoxItem.reset(pBoxItem2->Clone()); + pTableFormat->UpdateToSet(nPos, m_nRows==1, m_nCols==1, + const_cast<SfxItemSet&>(rAttrSet), + SwTableAutoFormatUpdateFlags::Box, + pFrameFormat->GetDoc()->GetNumberFormatter()); + if (pOldBoxItem) + { + // There was an old item, so it's guaranteed that there's a new item + const SvxBoxItem* pBoxItem2(rAttrSet.GetItem(RES_BOX)); + if (*pBoxItem2 != *pOldBoxItem) + { + std::unique_ptr<SvxBoxItem> pNewBoxItem(pBoxItem2->Clone()); + // Restore the box elements that could have been already set + for (auto eLine : { SvxBoxItemLine::TOP, SvxBoxItemLine::BOTTOM, + SvxBoxItemLine::LEFT, SvxBoxItemLine::RIGHT }) + { + if (auto pLine = pOldBoxItem->GetLine(eLine)) + pNewBoxItem->SetLine(pLine, eLine); + if (auto nDistance = pOldBoxItem->GetDistance(eLine, true)) + pNewBoxItem->SetDistance(nDistance, eLine); + } + + pFrameFormat->SetFormatAttr(*pNewBoxItem); + } + } + } + } + } + else + { + OSL_ENSURE( pBox->GetSttNd() || + SfxItemState::SET!=pFrameFormat->GetAttrSet().GetItemState( + RES_VERT_ORIENT, false ), + "Box without content has vertical orientation" ); + pBox->ChgFrameFormat( static_cast<SwTableBoxFormat*>(pFrameFormat) ); + } + +} + +SwTableBox *HTMLTable::NewTableBox( const SwStartNode *pStNd, + SwTableLine *pUpper ) const +{ + SwTableBox *pBox; + + if (m_xBox1 && m_xBox1->GetSttNd() == pStNd) + { + // If the StartNode is the StartNode of the initially created box, we take that box + pBox = const_cast<HTMLTable*>(this)->m_xBox1.release(); + pBox->SetUpper(pUpper); + } + else + pBox = new SwTableBox( m_pBoxFormat, *pStNd, pUpper ); + + return pBox; +} + +static void ResetLineFrameFormatAttrs( SwFrameFormat *pFrameFormat ) +{ + pFrameFormat->ResetFormatAttr( RES_FRM_SIZE ); + pFrameFormat->ResetFormatAttr( RES_BACKGROUND ); + OSL_ENSURE( SfxItemState::SET!=pFrameFormat->GetAttrSet().GetItemState( + RES_VERT_ORIENT, false ), + "Cell has vertical orientation" ); +} + +// !!! could be simplified +SwTableLine *HTMLTable::MakeTableLine( SwTableBox *pUpper, + sal_uInt16 nTopRow, sal_uInt16 nLeftCol, + sal_uInt16 nBottomRow, sal_uInt16 nRightCol ) +{ + SwTableLine *pLine; + if (!pUpper && 0 == nTopRow) + pLine = (m_pSwTable->GetTabLines())[0]; + else + pLine = new SwTableLine( m_pLineFrameFormatNoHeight ? m_pLineFrameFormatNoHeight + : m_pLineFormat, + 0, pUpper ); + + const HTMLTableRow& rTopRow = m_aRows[nTopRow]; + sal_uInt16 nRowHeight = rTopRow.GetHeight(); + const SvxBrushItem *pBGBrushItem = nullptr; + if (nTopRow > 0 || nBottomRow < m_nRows) + { + // It doesn't make sense to set a color on a line, + // if it's the outermost and simultaneously sole line of a table in a table + pBGBrushItem = rTopRow.GetBGBrush().get(); + } + if( nTopRow==nBottomRow-1 && (nRowHeight || pBGBrushItem) ) + { + SwTableLineFormat *pFrameFormat = static_cast<SwTableLineFormat*>(pLine->ClaimFrameFormat()); + ResetLineFrameFormatAttrs( pFrameFormat ); + + if( nRowHeight ) + { + // set table height. Since it's a minimum height it can be calculated like in Netscape, + // so without considering the actual border width + nRowHeight += GetTopCellSpace( nTopRow ) + + GetBottomCellSpace( nTopRow, 1 ); + + pFrameFormat->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Minimum, 0, nRowHeight ) ); + } + + if( pBGBrushItem ) + { + pFrameFormat->SetFormatAttr( *pBGBrushItem ); + } + + } + else if( !m_pLineFrameFormatNoHeight ) + { + // else, we'll have to remove the height from the attribute and remember the format + m_pLineFrameFormatNoHeight = static_cast<SwTableLineFormat*>(pLine->ClaimFrameFormat()); + + ResetLineFrameFormatAttrs( m_pLineFrameFormatNoHeight ); + } + + SwTableBoxes& rBoxes = pLine->GetTabBoxes(); + + sal_uInt16 nStartCol = nLeftCol; + while( nStartCol<nRightCol ) + { + sal_uInt16 nCol = nStartCol; + sal_uInt16 nSplitCol = nRightCol; + bool bSplitted = false; + while( !bSplitted ) + { + OSL_ENSURE( nCol < nRightCol, "Gone too far" ); + + HTMLTableCell& rCell = GetCell(nTopRow,nCol); + const bool bSplit = 1 == rCell.GetColSpan(); + + OSL_ENSURE((nCol != nRightCol-1) || bSplit, "Split-Flag wrong"); + if( bSplit ) + { + SwTableBox* pBox = nullptr; + HTMLTableCell& rCell2 = GetCell(nTopRow, nStartCol); + if (rCell2.GetColSpan() == (nCol+1-nStartCol)) + { + // The HTML tables represent a box. So we need to split behind that box + nSplitCol = nCol + 1; + + sal_Int32 nBoxRowSpan = rCell2.GetRowSpan(); + if (!rCell2.GetContents() || rCell2.IsCovered()) + { + if (rCell2.IsCovered()) + nBoxRowSpan = -1 * nBoxRowSpan; + + const SwStartNode* pPrevStartNd = + GetPrevBoxStartNode( nTopRow, nStartCol ); + auto xCnts = std::make_shared<HTMLTableCnts>( + m_pParser->InsertTableSection(pPrevStartNd)); + const std::shared_ptr<SwHTMLTableLayoutCnts> xCntsLayoutInfo = + xCnts->CreateLayoutInfo(); + + rCell2.SetContents(xCnts); + SwHTMLTableLayoutCell *pCurrCell = m_xLayoutInfo->GetCell(nTopRow, nStartCol); + pCurrCell->SetContents(xCntsLayoutInfo); + if( nBoxRowSpan < 0 ) + pCurrCell->SetRowSpan( 0 ); + + // check COLSPAN if needed + for( sal_uInt16 j=nStartCol+1; j<nSplitCol; j++ ) + { + GetCell(nTopRow, j).SetContents(xCnts); + m_xLayoutInfo->GetCell(nTopRow, j) + ->SetContents(xCntsLayoutInfo); + } + } + + pBox = MakeTableBox(pLine, rCell2.GetContents().get(), + nTopRow, nStartCol, + nBottomRow, nSplitCol); + + if (1 != nBoxRowSpan && pBox) + pBox->setRowSpan( nBoxRowSpan ); + + bSplitted = true; + } + + OSL_ENSURE( pBox, "Colspan trouble" ); + + if( pBox ) + rBoxes.push_back( pBox ); + } + nCol++; + } + nStartCol = nSplitCol; + } + + return pLine; +} + +SwTableBox *HTMLTable::MakeTableBox( SwTableLine *pUpper, + HTMLTableCnts *pCnts, + sal_uInt16 nTopRow, sal_uInt16 nLeftCol, + sal_uInt16 nBottomRow, sal_uInt16 nRightCol ) +{ + SwTableBox *pBox; + sal_uInt16 nColSpan = nRightCol - nLeftCol; + sal_uInt16 nRowSpan = nBottomRow - nTopRow; + + if( !pCnts->Next() ) + { + // just one content section + if( pCnts->GetStartNode() ) + { + // ... that's not a table + pBox = NewTableBox( pCnts->GetStartNode(), pUpper ); + pCnts->SetTableBox( pBox ); + } + else if (HTMLTable* pTable = pCnts->GetTable().get()) + { + pTable->InheritVertBorders( this, nLeftCol, + nRightCol-nLeftCol ); + // ... that's a table. We'll build a new box and put the rows of the table + // in the rows of the box + pBox = new SwTableBox( m_pBoxFormat, 0, pUpper ); + sal_uInt16 nAbs, nRel; + m_xLayoutInfo->GetAvail( nLeftCol, nColSpan, nAbs, nRel ); + sal_uInt16 nLSpace = m_xLayoutInfo->GetLeftCellSpace( nLeftCol, nColSpan ); + sal_uInt16 nRSpace = m_xLayoutInfo->GetRightCellSpace( nLeftCol, nColSpan ); + sal_uInt16 nInhSpace = m_xLayoutInfo->GetInhCellSpace( nLeftCol, nColSpan ); + pCnts->GetTable()->MakeTable( pBox, nAbs, nRel, nLSpace, nRSpace, + nInhSpace ); + } + else + { + return nullptr; + } + } + else + { + // multiple content sections: we'll build a box with rows + pBox = new SwTableBox( m_pBoxFormat, 0, pUpper ); + SwTableLines& rLines = pBox->GetTabLines(); + bool bFirstPara = true; + + while( pCnts ) + { + if( pCnts->GetStartNode() ) + { + // normal paragraphs are gonna be boxes in a row + SwTableLine *pLine = + new SwTableLine( m_pLineFrameFormatNoHeight ? m_pLineFrameFormatNoHeight + : m_pLineFormat, 0, pBox ); + if( !m_pLineFrameFormatNoHeight ) + { + // If there's no line format without height yet, we can use that one + m_pLineFrameFormatNoHeight = static_cast<SwTableLineFormat*>(pLine->ClaimFrameFormat()); + + ResetLineFrameFormatAttrs( m_pLineFrameFormatNoHeight ); + } + + SwTableBox* pCntBox = NewTableBox( pCnts->GetStartNode(), + pLine ); + pCnts->SetTableBox( pCntBox ); + FixFrameFormat( pCntBox, nTopRow, nLeftCol, nRowSpan, nColSpan, + bFirstPara, nullptr==pCnts->Next() ); + pLine->GetTabBoxes().push_back( pCntBox ); + + rLines.push_back( pLine ); + } + else + { + pCnts->GetTable()->InheritVertBorders( this, nLeftCol, + nRightCol-nLeftCol ); + // Tables are entered directly + sal_uInt16 nAbs, nRel; + m_xLayoutInfo->GetAvail( nLeftCol, nColSpan, nAbs, nRel ); + sal_uInt16 nLSpace = m_xLayoutInfo->GetLeftCellSpace( nLeftCol, + nColSpan ); + sal_uInt16 nRSpace = m_xLayoutInfo->GetRightCellSpace( nLeftCol, + nColSpan ); + sal_uInt16 nInhSpace = m_xLayoutInfo->GetInhCellSpace( nLeftCol, nColSpan ); + pCnts->GetTable()->MakeTable( pBox, nAbs, nRel, nLSpace, + nRSpace, nInhSpace ); + } + + pCnts = pCnts->Next(); + bFirstPara = false; + } + } + + FixFrameFormat( pBox, nTopRow, nLeftCol, nRowSpan, nColSpan ); + + return pBox; +} + +void HTMLTable::InheritBorders( const HTMLTable *pParent, + sal_uInt16 nRow, sal_uInt16 nCol, + sal_uInt16 nRowSpan, + bool bFirstPara, bool bLastPara ) +{ + OSL_ENSURE( m_nRows>0 && m_nCols>0 && m_nCurrentRow==m_nRows, + "Was CloseTable not called?" ); + + // The child table needs a border, if the surrounding cell has a margin on that side. + // The upper/lower border is only set if the table is the first/last paragraph in that cell + // It can't be determined if a border for that table is needed or possible for the left or right side, + // since that's depending on if filler cells are gonna be added. We'll only collect info for now + + if( 0==nRow && pParent->m_bTopBorder && bFirstPara ) + { + m_bTopBorder = true; + m_bFillerTopBorder = true; // fillers get a border too + m_aTopBorderLine = pParent->m_aTopBorderLine; + } + if (pParent->m_aRows[nRow+nRowSpan-1].GetBottomBorder() && bLastPara) + { + m_aRows[m_nRows-1].SetBottomBorder(true); + m_bFillerBottomBorder = true; // fillers get a border too + m_aBottomBorderLine = + nRow+nRowSpan==pParent->m_nRows ? pParent->m_aBottomBorderLine + : pParent->m_aBorderLine; + } + + // The child table mustn't get an upper or lower border, if that's already done by the surrounding table + // It can get an upper border if the table is not the first paragraph in that cell + m_bTopAllowed = ( !bFirstPara || (pParent->m_bTopAllowed && + (0==nRow || !pParent->m_aRows[nRow-1].GetBottomBorder())) ); + + // The child table has to inherit the color of the cell it's contained in, if it doesn't have one + const SvxBrushItem *pInhBG = pParent->GetCell(nRow, nCol).GetBGBrush().get(); + if( !pInhBG && pParent != this && + pParent->GetCell(nRow,nCol).GetRowSpan() == pParent->m_nRows ) + { + // the whole surrounding table is a table in a table and consists only of a single line + // that's gonna be GC-ed (correctly). That's why the background of that line is copied. + pInhBG = pParent->m_aRows[nRow].GetBGBrush().get(); + if( !pInhBG ) + pInhBG = pParent->GetBGBrush().get(); + if( !pInhBG ) + pInhBG = pParent->GetInhBGBrush().get(); + } + if( pInhBG ) + m_xInheritedBackgroundBrush.reset(new SvxBrushItem(*pInhBG)); +} + +void HTMLTable::InheritVertBorders( const HTMLTable *pParent, + sal_uInt16 nCol, sal_uInt16 nColSpan ) +{ + sal_uInt16 nInhLeftBorderWidth = 0; + sal_uInt16 nInhRightBorderWidth = 0; + + if( nCol+nColSpan==pParent->m_nCols && pParent->m_bRightBorder ) + { + m_bInheritedRightBorder = true; // just remember for now + m_aInheritedRightBorderLine = pParent->m_aRightBorderLine; + nInhRightBorderWidth = + GetBorderWidth( m_aInheritedRightBorderLine, true ) + MIN_BORDER_DIST; + } + + if (pParent->m_aColumns[nCol].m_bLeftBorder) + { + m_bInheritedLeftBorder = true; // just remember for now + m_aInheritedLeftBorderLine = 0==nCol ? pParent->m_aLeftBorderLine + : pParent->m_aBorderLine; + nInhLeftBorderWidth = + GetBorderWidth( m_aInheritedLeftBorderLine, true ) + MIN_BORDER_DIST; + } + + if( !m_bInheritedLeftBorder && (m_bFillerTopBorder || m_bFillerBottomBorder) ) + nInhLeftBorderWidth = 2 * MIN_BORDER_DIST; + if( !m_bInheritedRightBorder && (m_bFillerTopBorder || m_bFillerBottomBorder) ) + nInhRightBorderWidth = 2 * MIN_BORDER_DIST; + m_xLayoutInfo->SetInhBorderWidths( nInhLeftBorderWidth, + nInhRightBorderWidth ); + + m_bRightAllowed = ( pParent->m_bRightAllowed && + (nCol+nColSpan==pParent->m_nCols || + !pParent->m_aColumns[nCol+nColSpan].m_bLeftBorder) ); +} + +void HTMLTable::SetBorders() +{ + sal_uInt16 i; + for( i=1; i<m_nCols; i++ ) + if( HTMLTableRules::All==m_eRules || HTMLTableRules::Cols==m_eRules || + ((HTMLTableRules::Rows==m_eRules || HTMLTableRules::Groups==m_eRules) && + m_aColumns[i-1].IsEndOfGroup())) + { + m_aColumns[i].m_bLeftBorder = true; + } + + for( i=0; i<m_nRows-1; i++ ) + if( HTMLTableRules::All==m_eRules || HTMLTableRules::Rows==m_eRules || + ((HTMLTableRules::Cols==m_eRules || HTMLTableRules::Groups==m_eRules) && + m_aRows[i].IsEndOfGroup())) + { + m_aRows[i].SetBottomBorder(true); + } + + if( m_bTopAllowed && (HTMLTableFrame::Above==m_eFrame || HTMLTableFrame::HSides==m_eFrame || + HTMLTableFrame::Box==m_eFrame) ) + m_bTopBorder = true; + if( HTMLTableFrame::Below==m_eFrame || HTMLTableFrame::HSides==m_eFrame || + HTMLTableFrame::Box==m_eFrame ) + { + m_aRows[m_nRows-1].SetBottomBorder(true); + } + if( HTMLTableFrame::RHS==m_eFrame || HTMLTableFrame::VSides==m_eFrame || + HTMLTableFrame::Box==m_eFrame ) + m_bRightBorder = true; + if( HTMLTableFrame::LHS==m_eFrame || HTMLTableFrame::VSides==m_eFrame || HTMLTableFrame::Box==m_eFrame ) + { + m_aColumns[0].m_bLeftBorder = true; + } + + for( i=0; i<m_nRows; i++ ) + { + HTMLTableRow& rRow = m_aRows[i]; + for (sal_uInt16 j=0; j<m_nCols; ++j) + { + HTMLTableCell& rCell = rRow.GetCell(j); + if (rCell.GetContents()) + { + HTMLTableCnts *pCnts = rCell.GetContents().get(); + bool bFirstPara = true; + while( pCnts ) + { + HTMLTable *pTable = pCnts->GetTable().get(); + if( pTable && !pTable->BordersSet() ) + { + pTable->InheritBorders(this, i, j, + rCell.GetRowSpan(), + bFirstPara, + nullptr==pCnts->Next()); + pTable->SetBorders(); + } + bFirstPara = false; + pCnts = pCnts->Next(); + } + } + } + } + + m_bBordersSet = true; +} + +sal_uInt16 HTMLTable::GetBorderWidth( const SvxBorderLine& rBLine, + bool bWithDistance ) const +{ + sal_uInt16 nBorderWidth = rBLine.GetWidth(); + if( bWithDistance ) + { + if( m_nCellPadding ) + nBorderWidth = nBorderWidth + m_nCellPadding; + else if( nBorderWidth ) + nBorderWidth = nBorderWidth + MIN_BORDER_DIST; + } + + return nBorderWidth; +} + +const HTMLTableCell& HTMLTable::GetCell(sal_uInt16 nRow, sal_uInt16 nCell) const +{ + OSL_ENSURE(nRow < m_aRows.size(), "invalid row index in HTML table"); + return m_aRows[nRow].GetCell(nCell); +} + +SvxAdjust HTMLTable::GetInheritedAdjust() const +{ + SvxAdjust eAdjust = (m_nCurrentColumn<m_nCols ? m_aColumns[m_nCurrentColumn].GetAdjust() + : SvxAdjust::End ); + if( SvxAdjust::End==eAdjust ) + eAdjust = m_aRows[m_nCurrentRow].GetAdjust(); + + return eAdjust; +} + +sal_Int16 HTMLTable::GetInheritedVertOri() const +{ + // text::VertOrientation::TOP is default! + sal_Int16 eVOri = m_aRows[m_nCurrentRow].GetVertOri(); + if( text::VertOrientation::TOP==eVOri && m_nCurrentColumn<m_nCols ) + eVOri = m_aColumns[m_nCurrentColumn].GetVertOri(); + if( text::VertOrientation::TOP==eVOri ) + eVOri = m_eVertOrientation; + + OSL_ENSURE( m_eVertOrientation != text::VertOrientation::TOP, "text::VertOrientation::TOP is not allowed!" ); + return eVOri; +} + +void HTMLTable::InsertCell( std::shared_ptr<HTMLTableCnts> const& rCnts, + sal_uInt16 nRowSpan, sal_uInt16 nColSpan, + sal_uInt16 nCellWidth, bool bRelWidth, sal_uInt16 nCellHeight, + sal_Int16 eVertOrient, std::shared_ptr<SvxBrushItem> const& rBGBrushItem, + std::shared_ptr<SvxBoxItem> const& rBoxItem, + bool bHasNumFormat, sal_uInt32 nNumFormat, + bool bHasValue, double nValue, bool bNoWrap ) +{ + if( !nRowSpan || static_cast<sal_uInt32>(m_nCurrentRow) + nRowSpan > USHRT_MAX ) + nRowSpan = 1; + + if( !nColSpan || static_cast<sal_uInt32>(m_nCurrentColumn) + nColSpan > USHRT_MAX ) + nColSpan = 1; + + sal_uInt16 nColsReq = m_nCurrentColumn + nColSpan; + sal_uInt16 nRowsReq = m_nCurrentRow + nRowSpan; + sal_uInt16 i, j; + + // if we need more columns than we currently have, we need to add cells for all rows + if( m_nCols < nColsReq ) + { + m_aColumns.resize(nColsReq); + for( i=0; i<m_nRows; i++ ) + m_aRows[i].Expand( nColsReq, i<m_nCurrentRow ); + m_nCols = nColsReq; + OSL_ENSURE(m_aColumns.size() == m_nCols, + "wrong number of columns after expanding"); + } + if( nColsReq > m_nFilledColumns ) + m_nFilledColumns = nColsReq; + + // if we need more rows than we currently have, we need to add cells + if( m_nRows < nRowsReq ) + { + for( i=m_nRows; i<nRowsReq; i++ ) + m_aRows.emplace_back(m_nCols); + m_nRows = nRowsReq; + OSL_ENSURE(m_nRows == m_aRows.size(), "wrong number of rows in Insert"); + } + + // Check if we have an overlap and could remove that + sal_uInt16 nSpanedCols = 0; + if( m_nCurrentRow>0 ) + { + HTMLTableRow& rCurRow = m_aRows[m_nCurrentRow]; + for( i=m_nCurrentColumn; i<nColsReq; i++ ) + { + HTMLTableCell& rCell = rCurRow.GetCell(i); + if (rCell.GetContents()) + { + // A cell from a row further above overlaps this one. + // Content and colors are coming from that cell and can be overwritten + // or deleted (content) or copied (color) by ProtectRowSpan + nSpanedCols = i + rCell.GetColSpan(); + FixRowSpan( m_nCurrentRow-1, i, rCell.GetContents().get() ); + if (rCell.GetRowSpan() > nRowSpan) + ProtectRowSpan( nRowsReq, i, + rCell.GetRowSpan()-nRowSpan ); + } + } + for( i=nColsReq; i<nSpanedCols; i++ ) + { + // These contents are anchored in the row above in any case + HTMLTableCell& rCell = rCurRow.GetCell(i); + FixRowSpan( m_nCurrentRow-1, i, rCell.GetContents().get() ); + ProtectRowSpan( m_nCurrentRow, i, rCell.GetRowSpan() ); + } + } + + // Fill the cells + for( i=nColSpan; i>0; i-- ) + { + for( j=nRowSpan; j>0; j-- ) + { + const bool bCovered = i != nColSpan || j != nRowSpan; + GetCell( nRowsReq-j, nColsReq-i ) + .Set( rCnts, j, i, eVertOrient, rBGBrushItem, rBoxItem, + bHasNumFormat, nNumFormat, bHasValue, nValue, bNoWrap, bCovered ); + } + } + + Size aTwipSz( bRelWidth ? 0 : nCellWidth, nCellHeight ); + if( aTwipSz.Width() || aTwipSz.Height() ) + { + aTwipSz = o3tl::convert(aTwipSz, o3tl::Length::px, o3tl::Length::twip); + } + + // Only set width on the first cell! + if( nCellWidth ) + { + sal_uInt16 nTmp = bRelWidth ? nCellWidth : o3tl::narrowing<sal_uInt16>(aTwipSz.Width()); + GetCell( m_nCurrentRow, m_nCurrentColumn ).SetWidth( nTmp, bRelWidth ); + } + + // Remember height + if( nCellHeight && 1==nRowSpan ) + { + m_aRows[m_nCurrentRow].SetHeight(o3tl::narrowing<sal_uInt16>(aTwipSz.Height())); + } + + // Set the column counter behind the new cells + m_nCurrentColumn = nColsReq; + if( nSpanedCols > m_nCurrentColumn ) + m_nCurrentColumn = nSpanedCols; + + // and search for the next free cell + while( m_nCurrentColumn<m_nCols && GetCell(m_nCurrentRow,m_nCurrentColumn).IsUsed() ) + m_nCurrentColumn++; +} + +inline void HTMLTable::CloseSection( bool bHead ) +{ + // Close the preceding sections if there's already a row + OSL_ENSURE( m_nCurrentRow<=m_nRows, "invalid current row" ); + if( m_nCurrentRow>0 && m_nCurrentRow<=m_nRows ) + m_aRows[m_nCurrentRow-1].SetEndOfGroup(); + if( bHead ) + m_nHeadlineRepeat = m_nCurrentRow; +} + +void HTMLTable::OpenRow(SvxAdjust eAdjust, sal_Int16 eVertOrient, + std::unique_ptr<SvxBrushItem>& rBGBrushItem) +{ + sal_uInt16 nRowsReq = m_nCurrentRow+1; + + // create the next row if it's not there already + if( m_nRows<nRowsReq ) + { + for( sal_uInt16 i=m_nRows; i<nRowsReq; i++ ) + m_aRows.emplace_back(m_nCols); + m_nRows = nRowsReq; + OSL_ENSURE( m_nRows == m_aRows.size(), + "Row number in OpenRow is wrong" ); + } + + HTMLTableRow& rCurRow = m_aRows[m_nCurrentRow]; + rCurRow.SetAdjust(eAdjust); + rCurRow.SetVertOri(eVertOrient); + if (rBGBrushItem) + m_aRows[m_nCurrentRow].SetBGBrush(rBGBrushItem); + + // reset the column counter + m_nCurrentColumn=0; + + // and search for the next free cell + while( m_nCurrentColumn<m_nCols && GetCell(m_nCurrentRow,m_nCurrentColumn).IsUsed() ) + m_nCurrentColumn++; +} + +void HTMLTable::CloseRow( bool bEmpty ) +{ + OSL_ENSURE( m_nCurrentRow<m_nRows, "current row after table end" ); + + // empty cells just get a slightly thicker lower border! + if( bEmpty ) + { + if( m_nCurrentRow > 0 ) + m_aRows[m_nCurrentRow-1].IncEmptyRows(); + return; + } + + HTMLTableRow& rRow = m_aRows[m_nCurrentRow]; + + // modify the COLSPAN of all empty cells at the row end in a way, that they're forming a single cell + // that can be done here (and not earlier) since there's no more cells in that row + sal_uInt16 i=m_nCols; + while( i ) + { + HTMLTableCell& rCell = rRow.GetCell(--i); + if (!rCell.GetContents()) + { + sal_uInt16 nColSpan = m_nCols-i; + if( nColSpan > 1 ) + rCell.SetColSpan(nColSpan); + } + else + break; + } + + m_nCurrentRow++; +} + +inline void HTMLTable::CloseColGroup( sal_uInt16 nSpan, sal_uInt16 _nWidth, + bool bRelWidth, SvxAdjust eAdjust, + sal_Int16 eVertOrient ) +{ + if( nSpan ) + InsertCol( nSpan, _nWidth, bRelWidth, eAdjust, eVertOrient ); + + OSL_ENSURE( m_nCurrentColumn<=m_nCols, "invalid column" ); + if( m_nCurrentColumn>0 && m_nCurrentColumn<=m_nCols ) + m_aColumns[m_nCurrentColumn-1].SetEndOfGroup(); +} + +void HTMLTable::InsertCol( sal_uInt16 nSpan, sal_uInt16 nColWidth, bool bRelWidth, + SvxAdjust eAdjust, sal_Int16 eVertOrient ) +{ + // #i35143# - no columns, if rows already exist. + if ( m_nRows > 0 ) + return; + + sal_uInt16 i; + + if( !nSpan ) + nSpan = 1; + + sal_uInt16 nColsReq = m_nCurrentColumn + nSpan; + + if( m_nCols < nColsReq ) + { + m_aColumns.resize(nColsReq); + m_nCols = nColsReq; + } + + sal_uInt16 nTwipWidth(bRelWidth ? 0 : o3tl::convert(nColWidth, o3tl::Length::px, o3tl::Length::twip)); + + for( i=m_nCurrentColumn; i<nColsReq; i++ ) + { + HTMLTableColumn& rCol = m_aColumns[i]; + sal_uInt16 nTmp = bRelWidth ? nColWidth : o3tl::narrowing<sal_uInt16>(nTwipWidth); + rCol.SetWidth( nTmp, bRelWidth ); + rCol.SetAdjust( eAdjust ); + rCol.SetVertOri( eVertOrient ); + } + + m_bColSpec = true; + + m_nCurrentColumn = nColsReq; +} + +void HTMLTable::CloseTable() +{ + sal_uInt16 i; + + // The number of table rows is only dependent on the <TR> elements (i.e. nCurRow). + // Rows that are spanned via ROWSPAN behind nCurRow need to be deleted + // and we need to adjust the ROWSPAN in the rows above + if( m_nRows>m_nCurrentRow ) + { + HTMLTableRow& rPrevRow = m_aRows[m_nCurrentRow-1]; + for( i=0; i<m_nCols; i++ ) + { + HTMLTableCell& rCell = rPrevRow.GetCell(i); + if (rCell.GetRowSpan() > 1) + { + FixRowSpan(m_nCurrentRow-1, i, rCell.GetContents().get()); + ProtectRowSpan(m_nCurrentRow, i, m_aRows[m_nCurrentRow].GetCell(i).GetRowSpan()); + } + } + for( i=m_nRows-1; i>=m_nCurrentRow; i-- ) + m_aRows.erase(m_aRows.begin() + i); + m_nRows = m_nCurrentRow; + } + + // if the table has no column, we need to add one + if( 0==m_nCols ) + { + m_aColumns.resize(1); + for( i=0; i<m_nRows; i++ ) + m_aRows[i].Expand(1); + m_nCols = 1; + m_nFilledColumns = 1; + } + + // if the table has no row, we need to add one + if( 0==m_nRows ) + { + m_aRows.emplace_back(m_nCols); + m_nRows = 1; + m_nCurrentRow = 1; + } + + if( m_nFilledColumns < m_nCols ) + { + m_aColumns.erase(m_aColumns.begin() + m_nFilledColumns, m_aColumns.begin() + m_nCols); + for( i=0; i<m_nRows; i++ ) + m_aRows[i].Shrink( m_nFilledColumns ); + m_nCols = m_nFilledColumns; + } +} + +void HTMLTable::MakeTable_( SwTableBox *pBox ) +{ + SwTableLines& rLines = (pBox ? pBox->GetTabLines() + : const_cast<SwTable *>(m_pSwTable)->GetTabLines() ); + + for( sal_uInt16 i=0; i<m_nRows; i++ ) + { + SwTableLine *pLine = MakeTableLine( pBox, i, 0, i+1, m_nCols ); + if( pBox || i > 0 ) + rLines.push_back( pLine ); + } +} + +/* How are tables aligned? + +first row: without paragraph indents +second row: with paragraph indents + +ALIGN= LEFT RIGHT CENTER - +------------------------------------------------------------------------- +xxx for tables with WIDTH=nn% the percentage value is important: +xxx nn = 100 text::HoriOrientation::FULL text::HoriOrientation::FULL text::HoriOrientation::FULL text::HoriOrientation::FULL % +xxx text::HoriOrientation::NONE text::HoriOrientation::NONE text::HoriOrientation::NONE % text::HoriOrientation::NONE % +xxx nn < 100 frame F frame F text::HoriOrientation::CENTER % text::HoriOrientation::LEFT % +xxx frame F frame F text::HoriOrientation::CENTER % text::HoriOrientation::NONE % + +for tables with WIDTH=nn% the percentage value is important: +nn = 100 text::HoriOrientation::LEFT text::HoriOrientation::RIGHT text::HoriOrientation::CENTER % text::HoriOrientation::LEFT % + text::HoriOrientation::LEFT_AND text::HoriOrientation::RIGHT text::HoriOrientation::CENTER % text::HoriOrientation::LEFT_AND % +nn < 100 frame F frame F text::HoriOrientation::CENTER % text::HoriOrientation::LEFT % + frame F frame F text::HoriOrientation::CENTER % text::HoriOrientation::NONE % + +otherwise the calculated width w +w = avail* text::HoriOrientation::LEFT text::HoriOrientation::RIGHT text::HoriOrientation::CENTER text::HoriOrientation::LEFT + HORI_LEDT_AND text::HoriOrientation::RIGHT text::HoriOrientation::CENTER text::HoriOrientation::LEFT_AND +w < avail frame L frame L text::HoriOrientation::CENTER text::HoriOrientation::LEFT + frame L frame L text::HoriOrientation::CENTER text::HoriOrientation::NONE + +xxx *) if for the table no size was specified, always +xxx text::HoriOrientation::FULL is taken + +*/ + +void HTMLTable::MakeTable( SwTableBox *pBox, sal_uInt16 nAbsAvail, + sal_uInt16 nRelAvail, sal_uInt16 nAbsLeftSpace, + sal_uInt16 nAbsRightSpace, sal_uInt16 nInhAbsSpace ) +{ + OSL_ENSURE( m_nRows>0 && m_nCols>0 && m_nCurrentRow==m_nRows, + "Was CloseTable not called?" ); + + OSL_ENSURE(m_xLayoutInfo == nullptr, "Table already has layout info"); + + // Calculate borders of the table and all contained tables + SetBorders(); + + // Step 1: needed layout structures are created (including tables in tables) + CreateLayoutInfo(); + + if (!utl::ConfigManager::IsFuzzing()) // skip slow path for fuzzing + { + // Step 2: the minimal and maximal column width is calculated + // (including tables in tables). Since we don't have boxes yet, + // we'll work on the start nodes + m_xLayoutInfo->AutoLayoutPass1(); + + // Step 3: the actual column widths of this table are calculated (not tables in tables) + // We need this now to decide if we need filler cells + // (Pass1 was needed because of this as well) + m_xLayoutInfo->AutoLayoutPass2( nAbsAvail, nRelAvail, nAbsLeftSpace, + nAbsRightSpace, nInhAbsSpace ); + } + + // Set adjustment for the top table + sal_Int16 eHoriOri; + if (m_bForceFrame) + { + // The table should go in a text frame and it's narrower than the + // available space and not 100% wide. So it gets a border + eHoriOri = m_bPercentWidth ? text::HoriOrientation::FULL : text::HoriOrientation::LEFT; + } + else switch (m_eTableAdjust) + { + // The table either fits the page but shouldn't get a text frame, + // or it's wider than the page so it doesn't need a text frame + + case SvxAdjust::Right: + // Don't be considerate of the right margin in right-adjusted tables + eHoriOri = text::HoriOrientation::RIGHT; + break; + case SvxAdjust::Center: + // Centred tables are not considerate of margins + eHoriOri = text::HoriOrientation::CENTER; + break; + case SvxAdjust::Left: + default: + // left-adjusted tables are only considerate of the left margin + eHoriOri = m_nLeftMargin ? text::HoriOrientation::LEFT_AND_WIDTH : text::HoriOrientation::LEFT; + break; + } + + if (!m_pSwTable) + { + SAL_WARN("sw.html", "no table"); + return; + } + + // get the table format and adapt it + SwFrameFormat *pFrameFormat = m_pSwTable->GetFrameFormat(); + pFrameFormat->SetFormatAttr( SwFormatHoriOrient(0, eHoriOri) ); + if (text::HoriOrientation::LEFT_AND_WIDTH == eHoriOri) + { + OSL_ENSURE( m_nLeftMargin || m_nRightMargin, + "There are still leftovers from relative margins" ); + + // The right margin will be ignored anyway. + SvxLRSpaceItem aLRItem( m_pSwTable->GetFrameFormat()->GetLRSpace() ); + aLRItem.SetLeft( m_nLeftMargin ); + aLRItem.SetRight( m_nRightMargin ); + pFrameFormat->SetFormatAttr( aLRItem ); + } + + if (m_bPercentWidth && text::HoriOrientation::FULL != eHoriOri) + { + pFrameFormat->LockModify(); + SwFormatFrameSize aFrameSize( pFrameFormat->GetFrameSize() ); + aFrameSize.SetWidthPercent( static_cast<sal_uInt8>(m_nWidth) ); + pFrameFormat->SetFormatAttr( aFrameSize ); + pFrameFormat->UnlockModify(); + } + + // get the default line and box format + // remember the first box and unlist it from the first row + SwTableLine *pLine1 = (m_pSwTable->GetTabLines())[0]; + m_xBox1.reset((pLine1->GetTabBoxes())[0]); + pLine1->GetTabBoxes().erase(pLine1->GetTabBoxes().begin()); + + m_pLineFormat = static_cast<SwTableLineFormat*>(pLine1->GetFrameFormat()); + m_pBoxFormat = static_cast<SwTableBoxFormat*>(m_xBox1->GetFrameFormat()); + + MakeTable_( pBox ); + + // Finally, we'll do a garbage collection for the top level table + + if( 1==m_nRows && m_nHeight && 1==m_pSwTable->GetTabLines().size() ) + { + // Set height of a one-row table as the minimum width of the row + // Was originally a fixed height, but that made problems + // and is not Netscape 4.0 compliant + m_nHeight = SwHTMLParser::ToTwips( m_nHeight ); + if( m_nHeight < MINLAY ) + m_nHeight = MINLAY; + + (m_pSwTable->GetTabLines())[0]->ClaimFrameFormat(); + (m_pSwTable->GetTabLines())[0]->GetFrameFormat() + ->SetFormatAttr( SwFormatFrameSize( SwFrameSize::Minimum, 0, m_nHeight ) ); + } + + if( GetBGBrush() ) + m_pSwTable->GetFrameFormat()->SetFormatAttr( *GetBGBrush() ); + + const_cast<SwTable *>(m_pSwTable)->SetRowsToRepeat( static_cast< sal_uInt16 >(m_nHeadlineRepeat) ); + const_cast<SwTable *>(m_pSwTable)->GCLines(); + + bool bIsInFlyFrame = m_pContext && m_pContext->GetFrameFormat(); + if( bIsInFlyFrame && !m_nWidth ) + { + SvxAdjust eAdjust = GetTableAdjust(false); + if (eAdjust != SvxAdjust::Left && + eAdjust != SvxAdjust::Right) + { + // If a table with a width attribute isn't flowed around left or right + // we'll stack it with a border of 100% width, so its size will + // be adapted. That text frame mustn't be modified + OSL_ENSURE( HasToFly(), "Why is the table in a frame?" ); + sal_uInt32 nMin = m_xLayoutInfo->GetMin(); + if( nMin > USHRT_MAX ) + nMin = USHRT_MAX; + SwFormatFrameSize aFlyFrameSize( SwFrameSize::Variable, static_cast<SwTwips>(nMin), MINLAY ); + aFlyFrameSize.SetWidthPercent( 100 ); + m_pContext->GetFrameFormat()->SetFormatAttr( aFlyFrameSize ); + bIsInFlyFrame = false; + } + else + { + // left or right adjusted table without width mustn't be adjusted in width + // as they would only shrink but never grow + m_xLayoutInfo->SetMustNotRecalc( true ); + if( m_pContext->GetFrameFormat()->GetAnchor().GetAnchorNode() + ->FindTableNode() ) + { + sal_uInt32 nMax = m_xLayoutInfo->GetMax(); + if( nMax > USHRT_MAX ) + nMax = USHRT_MAX; + SwFormatFrameSize aFlyFrameSize( SwFrameSize::Variable, static_cast<SwTwips>(nMax), MINLAY ); + m_pContext->GetFrameFormat()->SetFormatAttr( aFlyFrameSize ); + bIsInFlyFrame = false; + } + else + { + m_xLayoutInfo->SetMustNotResize( true ); + } + } + } + m_xLayoutInfo->SetMayBeInFlyFrame( bIsInFlyFrame ); + + // Only tables with relative width or without width should be modified + m_xLayoutInfo->SetMustResize( m_bPercentWidth || !m_nWidth ); + + if (!pLine1->GetTabBoxes().empty()) + m_xLayoutInfo->SetWidths(); + else + SAL_WARN("sw.html", "no table box"); + + const_cast<SwTable *>(m_pSwTable)->SetHTMLTableLayout(m_xLayoutInfo); + + if( !m_xResizeDrawObjects ) + return; + + sal_uInt16 nCount = m_xResizeDrawObjects->size(); + for( sal_uInt16 i=0; i<nCount; i++ ) + { + SdrObject *pObj = (*m_xResizeDrawObjects)[i]; + sal_uInt16 nRow = (*m_xDrawObjectPercentWidths)[3*i]; + sal_uInt16 nCol = (*m_xDrawObjectPercentWidths)[3*i+1]; + sal_uInt8 nPercentWidth = static_cast<sal_uInt8>((*m_xDrawObjectPercentWidths)[3*i+2]); + + SwHTMLTableLayoutCell *pLayoutCell = + m_xLayoutInfo->GetCell( nRow, nCol ); + sal_uInt16 nColSpan = pLayoutCell->GetColSpan(); + + sal_uInt16 nWidth2, nDummy; + m_xLayoutInfo->GetAvail( nCol, nColSpan, nWidth2, nDummy ); + nWidth2 = static_cast< sal_uInt16 >((static_cast<tools::Long>(m_nWidth) * nPercentWidth) / 100); + + SwHTMLParser::ResizeDrawObject( pObj, nWidth2 ); + } + +} + +void HTMLTable::SetTable( const SwStartNode *pStNd, std::unique_ptr<HTMLTableContext> pCntxt, + sal_uInt16 nLeft, sal_uInt16 nRight, + const SwTable *pSwTab, bool bFrcFrame ) +{ + m_pPrevStartNode = pStNd; + m_pSwTable = pSwTab; + m_pContext = std::move(pCntxt); + + m_nLeftMargin = nLeft; + m_nRightMargin = nRight; + + m_bForceFrame = bFrcFrame; +} + +void HTMLTable::RegisterDrawObject( SdrObject *pObj, sal_uInt8 nPercentWidth ) +{ + if( !m_xResizeDrawObjects ) + m_xResizeDrawObjects.emplace(); + m_xResizeDrawObjects->push_back( pObj ); + pObj->AddObjectUser(*this); + + if( !m_xDrawObjectPercentWidths ) + m_xDrawObjectPercentWidths.emplace(); + m_xDrawObjectPercentWidths->push_back( m_nCurrentRow ); + m_xDrawObjectPercentWidths->push_back( m_nCurrentColumn ); + m_xDrawObjectPercentWidths->push_back( o3tl::narrowing<sal_uInt16>(nPercentWidth) ); +} + +void HTMLTable::MakeParentContents() +{ + if( !GetContext() && !HasParentSection() ) + { + SetParentContents( + m_pParser->InsertTableContents( m_bIsParentHead ) ); + + SetHasParentSection( true ); + } +} + +void HTMLTableContext::SavePREListingXMP( SwHTMLParser& rParser ) +{ + m_bRestartPRE = rParser.IsReadPRE(); + m_bRestartXMP = rParser.IsReadXMP(); + m_bRestartListing = rParser.IsReadListing(); + rParser.FinishPREListingXMP(); +} + +void HTMLTableContext::RestorePREListingXMP( SwHTMLParser& rParser ) +{ + rParser.FinishPREListingXMP(); + + if( m_bRestartPRE ) + rParser.StartPRE(); + + if( m_bRestartXMP ) + rParser.StartXMP(); + + if( m_bRestartListing ) + rParser.StartListing(); +} + +const SwStartNode *SwHTMLParser::InsertTableSection + ( const SwStartNode *pPrevStNd ) +{ + OSL_ENSURE( pPrevStNd, "Start-Node is NULL" ); + + m_pCSS1Parser->SetTDTagStyles(); + SwTextFormatColl *pColl = m_pCSS1Parser->GetTextCollFromPool( RES_POOLCOLL_TABLE ); + + const SwStartNode *pStNd; + if (m_xTable->m_bFirstCell ) + { + SwNode *const pNd = & m_pPam->GetPoint()->GetNode(); + pNd->GetTextNode()->ChgFormatColl( pColl ); + pStNd = pNd->FindTableBoxStartNode(); + m_xTable->m_bFirstCell = false; + } + else if (pPrevStNd) + { + const SwNode* pNd; + if( pPrevStNd->IsTableNode() ) + pNd = pPrevStNd; + else + pNd = pPrevStNd->EndOfSectionNode(); + SwNodeIndex nIdx( *pNd, 1 ); + pStNd = m_xDoc->GetNodes().MakeTextSection( nIdx.GetNode(), SwTableBoxStartNode, + pColl ); + m_xTable->IncBoxCount(); + } + else + { + eState = SvParserState::Error; + return nullptr; + } + + //Added defaults to CJK and CTL + SwContentNode *pCNd = m_xDoc->GetNodes()[pStNd->GetIndex()+1] ->GetContentNode(); + SvxFontHeightItem aFontHeight( 40, 100, RES_CHRATR_FONTSIZE ); + pCNd->SetAttr( aFontHeight ); + SvxFontHeightItem aFontHeightCJK( 40, 100, RES_CHRATR_CJK_FONTSIZE ); + pCNd->SetAttr( aFontHeightCJK ); + SvxFontHeightItem aFontHeightCTL( 40, 100, RES_CHRATR_CTL_FONTSIZE ); + pCNd->SetAttr( aFontHeightCTL ); + + return pStNd; +} + +const SwStartNode *SwHTMLParser::InsertTableSection( sal_uInt16 nPoolId ) +{ + switch( nPoolId ) + { + case RES_POOLCOLL_TABLE_HDLN: + m_pCSS1Parser->SetTHTagStyles(); + break; + case RES_POOLCOLL_TABLE: + m_pCSS1Parser->SetTDTagStyles(); + break; + } + + SwTextFormatColl *pColl = m_pCSS1Parser->GetTextCollFromPool( nPoolId ); + + SwNode *const pNd = & m_pPam->GetPoint()->GetNode(); + const SwStartNode *pStNd; + if (m_xTable->m_bFirstCell) + { + SwTextNode* pTextNd = pNd->GetTextNode(); + if (!pTextNd) + { + eState = SvParserState::Error; + return nullptr; + } + pTextNd->ChgFormatColl(pColl); + m_xTable->m_bFirstCell = false; + pStNd = pNd->FindTableBoxStartNode(); + } + else + { + SwTableNode *pTableNd = pNd->FindTableNode(); + if (!pTableNd) + { + eState = SvParserState::Error; + return nullptr; + } + if( pTableNd->GetTable().GetHTMLTableLayout() ) + { // if there is already a HTMTableLayout, this table is already finished + // and we have to look for the right table in the environment + SwTableNode *pOutTable = pTableNd; + do { + pTableNd = pOutTable; + pOutTable = pOutTable->StartOfSectionNode()->FindTableNode(); + } while( pOutTable && pTableNd->GetTable().GetHTMLTableLayout() ); + } + pStNd = m_xDoc->GetNodes().MakeTextSection( *pTableNd->EndOfSectionNode(), SwTableBoxStartNode, + pColl ); + + m_pPam->GetPoint()->Assign( pStNd->GetIndex() + 1 ); + m_xTable->IncBoxCount(); + } + + if (!pStNd) + { + eState = SvParserState::Error; + } + + return pStNd; +} + +SwStartNode *SwHTMLParser::InsertTempTableCaptionSection() +{ + SwTextFormatColl *pColl = m_pCSS1Parser->GetTextCollFromPool( RES_POOLCOLL_TEXT ); + SwStartNode *pStNd = m_xDoc->GetNodes().MakeTextSection( m_xDoc->GetNodes().GetEndOfExtras(), + SwNormalStartNode, pColl ); + + m_pPam->GetPoint()->Assign( pStNd->GetIndex() + 1); + + return pStNd; +} + +sal_Int32 SwHTMLParser::StripTrailingLF() +{ + sal_Int32 nStripped = 0; + + if (IsReqIF()) + { + // One <br> is exactly one line-break in the ReqIF case. + return nStripped; + } + + const sal_Int32 nLen = m_pPam->GetPoint()->GetContentIndex(); + if( nLen ) + { + SwTextNode* pTextNd = m_pPam->GetPoint()->GetNode().GetTextNode(); + // careful, when comments aren't ignored!!! + if( pTextNd ) + { + sal_Int32 nPos = nLen; + sal_Int32 nLFCount = 0; + while (nPos && ('\x0a' == pTextNd->GetText()[--nPos])) + nLFCount++; + + if( nLFCount ) + { + if( nLFCount > 2 ) + { + // On Netscape, a paragraph end matches 2 LFs + // (1 is just a newline, 2 creates a blank line) + // We already have this space with the lower paragraph gap + // If there's a paragraph after the <BR>, we take the maximum + // of the gap that results from the <BR> and <P> + // That's why we need to delete 2 respectively all if less than 2 + nLFCount = 2; + } + + nPos = nLen - nLFCount; + SwContentIndex nIdx( pTextNd, nPos ); + pTextNd->EraseText( nIdx, nLFCount ); + nStripped = nLFCount; + } + } + } + + return nStripped; +} + +SvxBrushItem* SwHTMLParser::CreateBrushItem( const Color *pColor, + const OUString& rImageURL, + const OUString& rStyle, + const OUString& rId, + const OUString& rClass ) +{ + SvxBrushItem *pBrushItem = nullptr; + + if( !rStyle.isEmpty() || !rId.isEmpty() || !rClass.isEmpty() ) + { + SfxItemSetFixed<RES_BACKGROUND, RES_BACKGROUND> aItemSet( m_xDoc->GetAttrPool() ); + SvxCSS1PropertyInfo aPropInfo; + + if( !rClass.isEmpty() ) + { + OUString aClass( rClass ); + SwCSS1Parser::GetScriptFromClass( aClass ); + const SvxCSS1MapEntry *pClass = m_pCSS1Parser->GetClass( aClass ); + if( pClass ) + aItemSet.Put( pClass->GetItemSet() ); + } + + if( !rId.isEmpty() ) + { + const SvxCSS1MapEntry *pId = m_pCSS1Parser->GetId( rId ); + if( pId ) + aItemSet.Put( pId->GetItemSet() ); + } + + m_pCSS1Parser->ParseStyleOption( rStyle, aItemSet, aPropInfo ); + if( const SvxBrushItem *pItem = aItemSet.GetItemIfSet( RES_BACKGROUND, false ) ) + { + pBrushItem = new SvxBrushItem( *pItem ); + } + } + + if( !pBrushItem && (pColor || !rImageURL.isEmpty()) ) + { + pBrushItem = new SvxBrushItem(RES_BACKGROUND); + + if( pColor ) + pBrushItem->SetColor(*pColor); + + if( !rImageURL.isEmpty() ) + { + pBrushItem->SetGraphicLink( URIHelper::SmartRel2Abs( INetURLObject(m_sBaseURL), rImageURL, Link<OUString *, bool>(), false) ); + pBrushItem->SetGraphicPos( GPOS_TILED ); + } + } + + return pBrushItem; +} + +class SectionSaveStruct : public SwPendingData +{ + sal_uInt16 m_nBaseFontStMinSave, m_nFontStMinSave, m_nFontStHeadStartSave; + sal_uInt16 m_nDefListDeepSave; + size_t m_nContextStMinSave; + size_t m_nContextStAttrMinSave; + +public: + + std::shared_ptr<HTMLTable> m_xTable; + + explicit SectionSaveStruct( SwHTMLParser& rParser ); + +#if OSL_DEBUG_LEVEL > 0 + size_t GetContextStAttrMin() const { return m_nContextStAttrMinSave; } +#endif + void Restore( SwHTMLParser& rParser ); +}; + +SectionSaveStruct::SectionSaveStruct( SwHTMLParser& rParser ) : + m_nBaseFontStMinSave(rParser.m_nBaseFontStMin), + m_nFontStMinSave(rParser.m_nFontStMin), + m_nFontStHeadStartSave(rParser.m_nFontStHeadStart), + m_nDefListDeepSave(rParser.m_nDefListDeep), + m_nContextStMinSave(rParser.m_nContextStMin), + m_nContextStAttrMinSave(rParser.m_nContextStAttrMin) +{ + // Freeze font stacks + rParser.m_nBaseFontStMin = rParser.m_aBaseFontStack.size(); + + rParser.m_nFontStMin = rParser.m_aFontStack.size(); + + // Freeze context stack + rParser.m_nContextStMin = rParser.m_aContexts.size(); + rParser.m_nContextStAttrMin = rParser.m_nContextStMin; + + // And remember a few counters + rParser.m_nDefListDeep = 0; +} + +void SectionSaveStruct::Restore( SwHTMLParser& rParser ) +{ + // Unfreeze font stacks + sal_uInt16 nMin = rParser.m_nBaseFontStMin; + if( rParser.m_aBaseFontStack.size() > nMin ) + rParser.m_aBaseFontStack.erase( rParser.m_aBaseFontStack.begin() + nMin, + rParser.m_aBaseFontStack.end() ); + rParser.m_nBaseFontStMin = m_nBaseFontStMinSave; + + nMin = rParser.m_nFontStMin; + if( rParser.m_aFontStack.size() > nMin ) + rParser.m_aFontStack.erase( rParser.m_aFontStack.begin() + nMin, + rParser.m_aFontStack.end() ); + rParser.m_nFontStMin = m_nFontStMinSave; + rParser.m_nFontStHeadStart = m_nFontStHeadStartSave; + + OSL_ENSURE( rParser.m_aContexts.size() == rParser.m_nContextStMin && + rParser.m_aContexts.size() == rParser.m_nContextStAttrMin, + "The Context Stack was not cleaned up" ); + rParser.m_nContextStMin = m_nContextStMinSave; + rParser.m_nContextStAttrMin = m_nContextStAttrMinSave; + + // Reconstruct a few counters + rParser.m_nDefListDeep = m_nDefListDeepSave; + + // Reset a few flags + rParser.m_bNoParSpace = false; + rParser.m_nOpenParaToken = HtmlTokenId::NONE; + + rParser.m_aParaAttrs.clear(); +} + +class CellSaveStruct : public SectionSaveStruct +{ + OUString m_aStyle, m_aId, m_aClass; + OUString m_aBGImage; + Color m_aBGColor; + std::shared_ptr<SvxBoxItem> m_xBoxItem; + + std::shared_ptr<HTMLTableCnts> m_xCnts; // List of all contents + HTMLTableCnts* m_pCurrCnts; // current content or 0 + std::optional<SwNodeIndex> m_oNoBreakEndNodeIndex; // Paragraph index of a <NOBR> + + double m_nValue; + + sal_uInt32 m_nNumFormat; + + sal_uInt16 m_nRowSpan, m_nColSpan, m_nWidth, m_nHeight; + sal_Int32 m_nNoBreakEndContentPos; // Character index of a <NOBR> + + sal_Int16 m_eVertOri; + + bool m_bHead : 1; + bool m_bPercentWidth : 1; + bool m_bHasNumFormat : 1; + bool m_bHasValue : 1; + bool m_bBGColor : 1; + bool m_bNoWrap : 1; // NOWRAP option + bool m_bNoBreak : 1; // NOBREAK tag + +public: + + CellSaveStruct( SwHTMLParser& rParser, HTMLTable const *pCurTable, bool bHd, + bool bReadOpt ); + + void AddContents( std::unique_ptr<HTMLTableCnts> pNewCnts ); + bool HasFirstContents() const { return bool(m_xCnts); } + + void ClearIsInSection() { m_pCurrCnts = nullptr; } + bool IsInSection() const { return m_pCurrCnts!=nullptr; } + + void InsertCell( SwHTMLParser& rParser, HTMLTable *pCurTable ); + + bool IsHeaderCell() const { return m_bHead; } + + void StartNoBreak( const SwPosition& rPos ); + void EndNoBreak( const SwPosition& rPos ); + void CheckNoBreak( const SwPosition& rPos ); +}; + +CellSaveStruct::CellSaveStruct( SwHTMLParser& rParser, HTMLTable const *pCurTable, + bool bHd, bool bReadOpt ) : + SectionSaveStruct( rParser ), + m_pCurrCnts( nullptr ), + m_nValue( 0.0 ), + m_nNumFormat( 0 ), + m_nRowSpan( 1 ), + m_nColSpan( 1 ), + m_nWidth( 0 ), + m_nHeight( 0 ), + m_nNoBreakEndContentPos( 0 ), + m_eVertOri( pCurTable->GetInheritedVertOri() ), + m_bHead( bHd ), + m_bPercentWidth( false ), + m_bHasNumFormat( false ), + m_bHasValue( false ), + m_bBGColor( false ), + m_bNoWrap( false ), + m_bNoBreak( false ) +{ + OUString aNumFormat, aValue, aDir, aLang; + SvxAdjust eAdjust( pCurTable->GetInheritedAdjust() ); + + if( bReadOpt ) + { + const HTMLOptions& rOptions = rParser.GetOptions(); + for (size_t i = rOptions.size(); i; ) + { + const HTMLOption& rOption = rOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + m_aId = rOption.GetString(); + break; + case HtmlOptionId::COLSPAN: + m_nColSpan = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + if (m_nColSpan > 256) + { + SAL_INFO("sw.html", "ignoring huge COLSPAN " << m_nColSpan); + m_nColSpan = 1; + } + break; + case HtmlOptionId::ROWSPAN: + m_nRowSpan = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + if (m_nRowSpan > 8192 || (m_nRowSpan > 256 && utl::ConfigManager::IsFuzzing())) + { + SAL_INFO("sw.html", "ignoring huge ROWSPAN " << m_nRowSpan); + m_nRowSpan = 1; + } + break; + case HtmlOptionId::ALIGN: + eAdjust = rOption.GetEnum( aHTMLPAlignTable, eAdjust ); + break; + case HtmlOptionId::VALIGN: + m_eVertOri = rOption.GetEnum( aHTMLTableVAlignTable, m_eVertOri ); + break; + case HtmlOptionId::WIDTH: + m_nWidth = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); // Just for Netscape + m_bPercentWidth = (rOption.GetString().indexOf('%') != -1); + if( m_bPercentWidth && m_nWidth>100 ) + m_nWidth = 100; + break; + case HtmlOptionId::HEIGHT: + m_nHeight = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); // Just for Netscape + if( rOption.GetString().indexOf('%') != -1) + m_nHeight = 0; // don't consider % attributes + break; + case HtmlOptionId::BGCOLOR: + // Ignore empty BGCOLOR on <TABLE>, <TR> and <TD>/<TH> like Netscape + // *really* not on other tags + if( !rOption.GetString().isEmpty() ) + { + rOption.GetColor( m_aBGColor ); + m_bBGColor = true; + } + break; + case HtmlOptionId::BACKGROUND: + m_aBGImage = rOption.GetString(); + break; + case HtmlOptionId::STYLE: + m_aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + m_aClass = rOption.GetString(); + break; + case HtmlOptionId::LANG: + aLang = rOption.GetString(); + break; + case HtmlOptionId::DIR: + aDir = rOption.GetString(); + break; + case HtmlOptionId::SDNUM: + aNumFormat = rOption.GetString(); + m_bHasNumFormat = true; + break; + case HtmlOptionId::SDVAL: + m_bHasValue = true; + aValue = rOption.GetString(); + break; + case HtmlOptionId::NOWRAP: + m_bNoWrap = true; + break; + default: break; + } + } + + if( !m_aId.isEmpty() ) + rParser.InsertBookmark( m_aId ); + } + + if( m_bHasNumFormat ) + { + LanguageType eLang; + m_nValue = SfxHTMLParser::GetTableDataOptionsValNum( + m_nNumFormat, eLang, aValue, aNumFormat, + *rParser.m_xDoc->GetNumberFormatter() ); + } + + // Create a new context but don't anchor the drawing::Alignment attribute there, + // since there's no section yet + HtmlTokenId nToken; + sal_uInt16 nColl; + if( m_bHead ) + { + nToken = HtmlTokenId::TABLEHEADER_ON; + nColl = RES_POOLCOLL_TABLE_HDLN; + } + else + { + nToken = HtmlTokenId::TABLEDATA_ON; + nColl = RES_POOLCOLL_TABLE; + } + std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(nToken, nColl, OUString(), true)); + if( SvxAdjust::End != eAdjust ) + rParser.InsertAttr(&rParser.m_xAttrTab->pAdjust, SvxAdjustItem(eAdjust, RES_PARATR_ADJUST), + xCntxt.get()); + + if( SwHTMLParser::HasStyleOptions( m_aStyle, m_aId, m_aClass, &aLang, &aDir ) ) + { + SfxItemSet aItemSet( rParser.m_xDoc->GetAttrPool(), + rParser.m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + + if( rParser.ParseStyleOptions( m_aStyle, m_aId, m_aClass, aItemSet, + aPropInfo, &aLang, &aDir ) ) + { + if (SvxBoxItem const* pItem = aItemSet.GetItemIfSet(RES_BOX, false)) + { // fdo#41796: steal box item to set it in FixFrameFormat later! + m_xBoxItem.reset(pItem->Clone()); + aItemSet.ClearItem(RES_BOX); + } + rParser.InsertAttrs(aItemSet, aPropInfo, xCntxt.get()); + } + } + + rParser.SplitPREListingXMP(xCntxt.get()); + + rParser.PushContext(xCntxt); +} + +void CellSaveStruct::AddContents( std::unique_ptr<HTMLTableCnts> pNewCnts ) +{ + m_pCurrCnts = pNewCnts.get(); + + if (m_xCnts) + m_xCnts->Add( std::move(pNewCnts) ); + else + m_xCnts = std::move(pNewCnts); +} + +void CellSaveStruct::InsertCell( SwHTMLParser& rParser, + HTMLTable *pCurTable ) +{ +#if OSL_DEBUG_LEVEL > 0 + // The attributes need to have been removed when tidying up the context stack, + // Otherwise something's wrong. Let's check that... + + // MIB 8.1.98: When attributes were opened outside of a cell, + // they're still in the attribute table and will only be deleted at the end + // through the CleanContext calls in BuildTable. We don't check that there + // so that we get no assert [violations, by translator] + // We can see this on nContextStAttrMin: the remembered value of nContextStAttrMinSave + // is the value that nContextStAttrMin had at the start of the table. And the + // current value of nContextStAttrMin corresponds to the number of contexts + // we found at the start of the cell. If the values differ, contexts + // were created and we don't check anything. + + if( rParser.m_nContextStAttrMin == GetContextStAttrMin() ) + { + HTMLAttr** pTable = reinterpret_cast<HTMLAttr**>(rParser.m_xAttrTab.get()); + + for( auto nCnt = sizeof( HTMLAttrTable ) / sizeof( HTMLAttr* ); + nCnt--; ++pTable ) + { + OSL_ENSURE( !*pTable, "The attribute table isn't empty" ); + } + } +#endif + + // we need to add the cell on the current position + std::shared_ptr<SvxBrushItem> xBrushItem( + rParser.CreateBrushItem(m_bBGColor ? &m_aBGColor : nullptr, m_aBGImage, + m_aStyle, m_aId, m_aClass)); + pCurTable->InsertCell( m_xCnts, m_nRowSpan, m_nColSpan, m_nWidth, + m_bPercentWidth, m_nHeight, m_eVertOri, xBrushItem, m_xBoxItem, + m_bHasNumFormat, m_nNumFormat, m_bHasValue, m_nValue, + m_bNoWrap ); + Restore( rParser ); +} + +void CellSaveStruct::StartNoBreak( const SwPosition& rPos ) +{ + if( !m_xCnts || + (!rPos.GetContentIndex() && m_pCurrCnts == m_xCnts.get() && + m_xCnts->GetStartNode() && + m_xCnts->GetStartNode()->GetIndex() + 1 == + rPos.GetNodeIndex()) ) + { + m_bNoBreak = true; + } +} + +void CellSaveStruct::EndNoBreak( const SwPosition& rPos ) +{ + if( m_bNoBreak ) + { + m_oNoBreakEndNodeIndex.emplace( rPos.GetNode() ); + m_nNoBreakEndContentPos = rPos.GetContentIndex(); + m_bNoBreak = false; + } +} + +void CellSaveStruct::CheckNoBreak( const SwPosition& rPos ) +{ + if (!(m_xCnts && m_pCurrCnts == m_xCnts.get())) + return; + + if( m_bNoBreak ) + { + // <NOBR> wasn't closed + m_xCnts->SetNoBreak(); + } + else if( m_oNoBreakEndNodeIndex && + m_oNoBreakEndNodeIndex->GetIndex() == rPos.GetNodeIndex() ) + { + if( m_nNoBreakEndContentPos == rPos.GetContentIndex() ) + { + // <NOBR> was closed immediately before the cell end + m_xCnts->SetNoBreak(); + } + else if( m_nNoBreakEndContentPos + 1 == rPos.GetContentIndex() ) + { + SwTextNode const*const pTextNd(rPos.GetNode().GetTextNode()); + if( pTextNd ) + { + sal_Unicode const cLast = + pTextNd->GetText()[m_nNoBreakEndContentPos]; + if( ' '==cLast || '\x0a'==cLast ) + { + // There's just a blank or a newline between the <NOBR> and the cell end + m_xCnts->SetNoBreak(); + } + } + } + } +} + +std::unique_ptr<HTMLTableCnts> SwHTMLParser::InsertTableContents( + bool bHead ) +{ + // create a new section, the PaM is gonna be there + const SwStartNode *pStNd = + InsertTableSection( static_cast< sal_uInt16 >(bHead ? RES_POOLCOLL_TABLE_HDLN + : RES_POOLCOLL_TABLE) ); + + if( GetNumInfo().GetNumRule() ) + { + // Set the first paragraph to non-enumerated + sal_uInt8 nLvl = GetNumInfo().GetLevel(); + + SetNodeNum( nLvl ); + } + + // Reset attributation start + const SwNode& rSttPara = m_pPam->GetPoint()->GetNode(); + sal_Int32 nSttCnt = m_pPam->GetPoint()->GetContentIndex(); + + HTMLAttr** pHTMLAttributes = reinterpret_cast<HTMLAttr**>(m_xAttrTab.get()); + for (sal_uInt16 nCnt = sizeof(HTMLAttrTable) / sizeof(HTMLAttr*); nCnt--; ++pHTMLAttributes) + { + HTMLAttr *pAttr = *pHTMLAttributes; + while( pAttr ) + { + OSL_ENSURE( !pAttr->GetPrev(), "Attribute has previous list" ); + pAttr->m_nStartPara = rSttPara; + pAttr->m_nEndPara = rSttPara; + pAttr->m_nStartContent = nSttCnt; + pAttr->m_nEndContent = nSttCnt; + + pAttr = pAttr->GetNext(); + } + } + + return std::make_unique<HTMLTableCnts>( pStNd ); +} + +sal_uInt16 SwHTMLParser::IncGrfsThatResizeTable() +{ + return m_xTable ? m_xTable->IncGrfsThatResize() : 0; +} + +void SwHTMLParser::RegisterDrawObjectToTable( HTMLTable *pCurTable, + SdrObject *pObj, sal_uInt8 nPercentWidth ) +{ + pCurTable->RegisterDrawObject( pObj, nPercentWidth ); +} + +void SwHTMLParser::BuildTableCell( HTMLTable *pCurTable, bool bReadOptions, + bool bHead ) +{ + if( !IsParserWorking() && m_vPendingStack.empty() ) + return; + + ::comphelper::FlagRestorationGuard g(m_isInTableStructure, false); + std::unique_ptr<CellSaveStruct> xSaveStruct; + + HtmlTokenId nToken = HtmlTokenId::NONE; + bool bPending = false; + if( !m_vPendingStack.empty() ) + { + xSaveStruct.reset(static_cast<CellSaveStruct*>(m_vPendingStack.back().pData.release())); + + m_vPendingStack.pop_back(); + nToken = !m_vPendingStack.empty() ? m_vPendingStack.back().nToken : GetSaveToken(); + bPending = SvParserState::Error == eState && !m_vPendingStack.empty(); + + SaveState( nToken ); + } + else + { + // <TH> resp. <TD> were already read + if (m_xTable->IsOverflowing()) + { + SaveState( HtmlTokenId::NONE ); + return; + } + + if( !pCurTable->GetContext() ) + { + bool bTopTable = m_xTable.get() == pCurTable; + + // the table has no content yet, this means the actual table needs + // to be created first + + SfxItemSetFixed< + RES_PARATR_SPLIT, RES_PARATR_SPLIT, + RES_PAGEDESC, RES_PAGEDESC, + RES_BREAK, RES_BREAK, + RES_BACKGROUND, RES_BACKGROUND, + RES_KEEP, RES_KEEP, + RES_LAYOUT_SPLIT, RES_LAYOUT_SPLIT, + RES_FRAMEDIR, RES_FRAMEDIR + > aItemSet( m_xDoc->GetAttrPool() ); + SvxCSS1PropertyInfo aPropInfo; + + bool bStyleParsed = ParseStyleOptions( pCurTable->GetStyle(), + pCurTable->GetId(), + pCurTable->GetClass(), + aItemSet, aPropInfo, + nullptr, &pCurTable->GetDirection() ); + if( bStyleParsed ) + { + if( const SvxBrushItem* pItem = aItemSet.GetItemIfSet( + RES_BACKGROUND, false ) ) + { + pCurTable->SetBGBrush( *pItem ); + aItemSet.ClearItem( RES_BACKGROUND ); + } + if( const SvxFormatSplitItem* pSplitItem = aItemSet.GetItemIfSet( + RES_PARATR_SPLIT, false ) ) + { + aItemSet.Put( + SwFormatLayoutSplit( pSplitItem->GetValue() ) ); + aItemSet.ClearItem( RES_PARATR_SPLIT ); + } + } + + sal_uInt16 nLeftSpace = 0; + sal_uInt16 nRightSpace = 0; + short nIndent; + GetMarginsFromContextWithNumberBullet( nLeftSpace, nRightSpace, nIndent ); + + // save the current position we'll get back to some time + SwPosition *pSavePos = nullptr; + bool bForceFrame = false; + bool bAppended = false; + bool bParentLFStripped = false; + if( bTopTable ) + { + SvxAdjust eTableAdjust = m_xTable->GetTableAdjust(false); + + // If the table is left or right adjusted or should be in a text frame, + // it'll get one + bForceFrame = eTableAdjust == SvxAdjust::Left || + eTableAdjust == SvxAdjust::Right || + pCurTable->HasToFly(); + + // The table either shouldn't get in a text frame and isn't in one + // (it gets simulated through cells), + // or there's already content at that position + OSL_ENSURE( !bForceFrame || pCurTable->HasParentSection(), + "table in frame has no parent!" ); + + bool bAppend = false; + if( bForceFrame ) + { + // If the table gets in a border, we only need to open a new + //paragraph if the paragraph has text frames that don't fly + bAppend = HasCurrentParaFlys(true); + } + else + { + // Otherwise, we need to open a new paragraph if the paragraph + // is empty or contains text frames or bookmarks + bAppend = + m_pPam->GetPoint()->GetContentIndex() || + HasCurrentParaFlys() || + HasCurrentParaBookmarks(); + } + if( bAppend ) + { + if( !m_pPam->GetPoint()->GetContentIndex() ) + { + //Set default to CJK and CTL + m_xDoc->SetTextFormatColl( *m_pPam, + m_pCSS1Parser->GetTextCollFromPool(RES_POOLCOLL_STANDARD) ); + SvxFontHeightItem aFontHeight( 40, 100, RES_CHRATR_FONTSIZE ); + + HTMLAttr* pTmp = + new HTMLAttr( *m_pPam->GetPoint(), aFontHeight, nullptr, std::shared_ptr<HTMLAttrTable>() ); + m_aSetAttrTab.push_back( pTmp ); + + SvxFontHeightItem aFontHeightCJK( 40, 100, RES_CHRATR_CJK_FONTSIZE ); + pTmp = + new HTMLAttr( *m_pPam->GetPoint(), aFontHeightCJK, nullptr, std::shared_ptr<HTMLAttrTable>() ); + m_aSetAttrTab.push_back( pTmp ); + + SvxFontHeightItem aFontHeightCTL( 40, 100, RES_CHRATR_CTL_FONTSIZE ); + pTmp = + new HTMLAttr( *m_pPam->GetPoint(), aFontHeightCTL, nullptr, std::shared_ptr<HTMLAttrTable>() ); + m_aSetAttrTab.push_back( pTmp ); + + pTmp = new HTMLAttr( *m_pPam->GetPoint(), + SvxULSpaceItem( 0, 0, RES_UL_SPACE ), nullptr, std::shared_ptr<HTMLAttrTable>() ); + m_aSetAttrTab.push_front( pTmp ); // Position 0, since + // something can be set by + // the table end before + } + AppendTextNode( AM_NOSPACE ); + bAppended = true; + } + else if( !m_aParaAttrs.empty() ) + { + if( !bForceFrame ) + { + // The paragraph will be moved right behind the table. + // That's why we remove all hard attributes of that paragraph + + for(HTMLAttr* i : m_aParaAttrs) + i->Invalidate(); + } + + m_aParaAttrs.clear(); + } + + pSavePos = new SwPosition( *m_pPam->GetPoint() ); + } + else if( pCurTable->HasParentSection() ) + { + bParentLFStripped = StripTrailingLF() > 0; + + // Close paragraph resp. headers + m_nOpenParaToken = HtmlTokenId::NONE; + m_nFontStHeadStart = m_nFontStMin; + + // The hard attributes on that paragraph are never gonna be invalid anymore + m_aParaAttrs.clear(); + } + + // create a table context + std::unique_ptr<HTMLTableContext> pTCntxt( + new HTMLTableContext( pSavePos, m_nContextStMin, + m_nContextStAttrMin ) ); + + // end all open attributes and open them again behind the table + std::optional<std::deque<std::unique_ptr<HTMLAttr>>> pPostIts; + if( !bForceFrame && (bTopTable || pCurTable->HasParentSection()) ) + { + SplitAttrTab(pTCntxt->m_xAttrTab, bTopTable); + // If we reuse an already existing paragraph, we can't add + // PostIts since the paragraph gets behind that table. + // They're gonna be moved into the first paragraph of the table + // If we have tables in tables, we also can't add PostIts to a + // still empty paragraph, since it's not gonna be deleted that way + if( (bTopTable && !bAppended) || + (!bTopTable && !bParentLFStripped && + !m_pPam->GetPoint()->GetContentIndex()) ) + pPostIts.emplace(); + SetAttr( bTopTable, bTopTable, pPostIts ? &*pPostIts : nullptr ); + } + else + { + SaveAttrTab(pTCntxt->m_xAttrTab); + if( bTopTable && !bAppended ) + { + pPostIts.emplace(); + SetAttr( true, true, &*pPostIts ); + } + } + m_bNoParSpace = false; + + // Save current numbering and turn it off + pTCntxt->SetNumInfo( GetNumInfo() ); + GetNumInfo().Clear(); + pTCntxt->SavePREListingXMP( *this ); + + if( bTopTable ) + { + if( bForceFrame ) + { + // the table should be put in a text frame + + SfxItemSetFixed<RES_FRMATR_BEGIN, RES_FRMATR_END-1> + aFrameSet( m_xDoc->GetAttrPool() ); + if( !pCurTable->IsNewDoc() ) + Reader::ResetFrameFormatAttrs( aFrameSet ); + + css::text::WrapTextMode eSurround = css::text::WrapTextMode_NONE; + sal_Int16 eHori; + + switch( pCurTable->GetTableAdjust(true) ) + { + case SvxAdjust::Right: + eHori = text::HoriOrientation::RIGHT; + eSurround = css::text::WrapTextMode_LEFT; + break; + case SvxAdjust::Center: + eHori = text::HoriOrientation::CENTER; + break; + case SvxAdjust::Left: + eSurround = css::text::WrapTextMode_RIGHT; + [[fallthrough]]; + default: + eHori = text::HoriOrientation::LEFT; + break; + } + SetAnchorAndAdjustment( text::VertOrientation::NONE, eHori, aFrameSet, + true ); + aFrameSet.Put( SwFormatSurround(eSurround) ); + + constexpr tools::Long constTwips_100mm = o3tl::convert(tools::Long(100), o3tl::Length::mm, o3tl::Length::twip); + + SwFormatFrameSize aFrameSize( SwFrameSize::Variable, constTwips_100mm, MINLAY ); + aFrameSize.SetWidthPercent( 100 ); + aFrameSet.Put( aFrameSize ); + + sal_uInt16 nSpace = pCurTable->GetHSpace(); + if( nSpace ) + aFrameSet.Put( SvxLRSpaceItem(nSpace, nSpace, 0, RES_LR_SPACE) ); + nSpace = pCurTable->GetVSpace(); + if( nSpace ) + aFrameSet.Put( SvxULSpaceItem(nSpace,nSpace, RES_UL_SPACE) ); + + RndStdIds eAnchorId = aFrameSet. + Get( RES_ANCHOR ). + GetAnchorId(); + SwFrameFormat *pFrameFormat = m_xDoc->MakeFlySection( + eAnchorId, m_pPam->GetPoint(), &aFrameSet ); + + pTCntxt->SetFrameFormat( pFrameFormat ); + const SwFormatContent& rFlyContent = pFrameFormat->GetContent(); + m_pPam->GetPoint()->Assign( *rFlyContent.GetContentIdx() ); + m_xDoc->GetNodes().GoNext( m_pPam->GetPoint() ); + } + + // create a SwTable with a box and set the PaM to the content of + // the box section (the adjustment parameter is a dummy for now + // and will be corrected later) + OSL_ENSURE( !m_pPam->GetPoint()->GetContentIndex(), + "The paragraph after the table is not empty!" ); + const SwTable* pSwTable = m_xDoc->InsertTable( + SwInsertTableOptions( SwInsertTableFlags::HeadlineNoBorder, 1 ), + *m_pPam->GetPoint(), 1, 1, text::HoriOrientation::LEFT ); + SwFrameFormat *pFrameFormat = pSwTable ? pSwTable->GetFrameFormat() : nullptr; + + if( bForceFrame ) + { + SwNodeIndex aDstIdx( m_pPam->GetPoint()->GetNode() ); + m_pPam->Move( fnMoveBackward ); + m_xDoc->GetNodes().Delete( aDstIdx ); + } + else + { + if (bStyleParsed && pFrameFormat) + { + m_pCSS1Parser->SetFormatBreak( aItemSet, aPropInfo ); + pFrameFormat->SetFormatAttr( aItemSet ); + } + m_pPam->Move( fnMoveBackward ); + } + + SwNode const*const pNd = & m_pPam->GetPoint()->GetNode(); + SwTextNode *const pOldTextNd = (!bAppended && !bForceFrame) ? + pSavePos->GetNode().GetTextNode() : nullptr; + + if (pFrameFormat && pOldTextNd) + { + const SwFormatPageDesc* pPageDescItem = pOldTextNd->GetSwAttrSet() + .GetItemIfSet( RES_PAGEDESC, false ); + if( pPageDescItem && pPageDescItem->GetPageDesc() ) + { + pFrameFormat->SetFormatAttr( *pPageDescItem ); + pOldTextNd->ResetAttr( RES_PAGEDESC ); + } + + if( const SvxFormatBreakItem* pBreakItem = pOldTextNd->GetSwAttrSet() + .GetItemIfSet( RES_BREAK ) ) + { + switch( pBreakItem->GetBreak() ) + { + case SvxBreak::PageBefore: + case SvxBreak::PageAfter: + case SvxBreak::PageBoth: + pFrameFormat->SetFormatAttr( *pBreakItem ); + pOldTextNd->ResetAttr( RES_BREAK ); + break; + default: + break; + } + } + } + + if( !bAppended && pPostIts ) + { + // set still-existing PostIts to the first paragraph of the table + InsertAttrs( std::move(*pPostIts) ); + pPostIts.reset(); + } + + pTCntxt->SetTableNode( const_cast<SwTableNode *>(pNd->FindTableNode()) ); + + auto pTableNode = pTCntxt->GetTableNode(); + pCurTable->SetTable( pTableNode, std::move(pTCntxt), + nLeftSpace, nRightSpace, + pSwTable, bForceFrame ); + + OSL_ENSURE( !pPostIts, "unused PostIts" ); + } + else + { + // still open sections need to be deleted + if( EndSections( bParentLFStripped ) ) + bParentLFStripped = false; + + if( pCurTable->HasParentSection() ) + { + // after that, we remove a possibly redundant empty paragraph, + // but only if it was empty before we stripped the LFs + if( !bParentLFStripped ) + StripTrailingPara(); + + if( pPostIts ) + { + // move still existing PostIts to the end of the current paragraph + InsertAttrs( std::move(*pPostIts) ); + pPostIts.reset(); + } + } + + SwNode const*const pNd = & m_pPam->GetPoint()->GetNode(); + const SwStartNode *pStNd = (m_xTable->m_bFirstCell ? pNd->FindTableNode() + : pNd->FindTableBoxStartNode() ); + + pCurTable->SetTable( pStNd, std::move(pTCntxt), nLeftSpace, nRightSpace ); + } + + // Freeze the context stack, since there could be attributes set + // outside of cells. Can't happen earlier, since there may be + // searches in the stack + m_nContextStMin = m_aContexts.size(); + m_nContextStAttrMin = m_nContextStMin; + } + + xSaveStruct.reset(new CellSaveStruct(*this, pCurTable, bHead, bReadOptions)); + + // If the first GetNextToken() doesn't succeed (pending input), must re-read from the beginning. + SaveState( HtmlTokenId::NONE ); + } + + if( nToken == HtmlTokenId::NONE ) + nToken = GetNextToken(); // Token after <TABLE> + + bool bDone = false; + while( (IsParserWorking() && !bDone) || bPending ) + { + SaveState( nToken ); + + nToken = FilterToken( nToken ); + + OSL_ENSURE( !m_vPendingStack.empty() || !m_bCallNextToken || xSaveStruct->IsInSection(), + "Where is the section??" ); + if( m_vPendingStack.empty() && m_bCallNextToken && xSaveStruct->IsInSection() ) + { + // Call NextToken directly (e.g. ignore the content of floating frames or applets) + NextToken( nToken ); + } + else switch( nToken ) + { + case HtmlTokenId::TABLEHEADER_ON: + case HtmlTokenId::TABLEDATA_ON: + case HtmlTokenId::TABLEROW_ON: + case HtmlTokenId::TABLEROW_OFF: + case HtmlTokenId::THEAD_ON: + case HtmlTokenId::THEAD_OFF: + case HtmlTokenId::TFOOT_ON: + case HtmlTokenId::TFOOT_OFF: + case HtmlTokenId::TBODY_ON: + case HtmlTokenId::TBODY_OFF: + case HtmlTokenId::TABLE_OFF: + SkipToken(); + [[fallthrough]]; + case HtmlTokenId::TABLEHEADER_OFF: + case HtmlTokenId::TABLEDATA_OFF: + bDone = true; + break; + case HtmlTokenId::TABLE_ON: + { + bool bHasToFly = false; + SvxAdjust eTabAdjust = SvxAdjust::End; + if( m_vPendingStack.empty() ) + { + // only if we create a new table, but not if we're still + // reading in the table after a Pending + xSaveStruct->m_xTable = m_xTable; + + // HACK: create a section for a table that goes in a text frame + if( !xSaveStruct->IsInSection() ) + { + // The loop needs to be forward, since the + // first option always wins + bool bNeedsSection = false; + const HTMLOptions& rHTMLOptions = GetOptions(); + for (const auto & rOption : rHTMLOptions) + { + if( HtmlOptionId::ALIGN==rOption.GetToken() ) + { + SvxAdjust eAdjust = rOption.GetEnum( aHTMLPAlignTable, SvxAdjust::End ); + bNeedsSection = SvxAdjust::Left == eAdjust || + SvxAdjust::Right == eAdjust; + break; + } + } + if( bNeedsSection ) + { + xSaveStruct->AddContents( + InsertTableContents(bHead ) ); + } + } + else + { + // If Flys are anchored in the current paragraph, + // the table needs to get in a text frame + bHasToFly = HasCurrentParaFlys(false,true); + } + + // There could be a section in the cell + eTabAdjust = m_xAttrTab->pAdjust + ? static_cast<const SvxAdjustItem&>(m_xAttrTab->pAdjust->GetItem()). + GetAdjust() + : SvxAdjust::End; + } + + std::shared_ptr<HTMLTable> xSubTable = BuildTable(eTabAdjust, + bHead, + xSaveStruct->IsInSection(), + bHasToFly); + if( SvParserState::Pending != GetStatus() ) + { + // Only if the table is really complete + if (xSubTable) + { + OSL_ENSURE( xSubTable->GetTableAdjust(false)!= SvxAdjust::Left && + xSubTable->GetTableAdjust(false)!= SvxAdjust::Right, + "left or right aligned tables belong in frames" ); + + auto& rParentContents = xSubTable->GetParentContents(); + if (rParentContents) + { + OSL_ENSURE( !xSaveStruct->IsInSection(), + "Where is the section" ); + + // If there's no table coming, we have a section + xSaveStruct->AddContents(std::move(rParentContents)); + } + + const SwStartNode *pCapStNd = + xSubTable->GetCaptionStartNode(); + + if (xSubTable->GetContext()) + { + OSL_ENSURE( !xSubTable->GetContext()->GetFrameFormat(), + "table in frame" ); + + if( pCapStNd && xSubTable->IsTopCaption() ) + { + xSaveStruct->AddContents( + std::make_unique<HTMLTableCnts>(pCapStNd) ); + } + + xSaveStruct->AddContents( + std::make_unique<HTMLTableCnts>(xSubTable) ); + + if( pCapStNd && !xSubTable->IsTopCaption() ) + { + xSaveStruct->AddContents( + std::make_unique<HTMLTableCnts>(pCapStNd) ); + } + + // We don't have a section anymore + xSaveStruct->ClearIsInSection(); + } + else if( pCapStNd ) + { + // Since we can't delete this section (it might + // belong to the first box), we'll add it + xSaveStruct->AddContents( + std::make_unique<HTMLTableCnts>(pCapStNd) ); + + // We don't have a section anymore + xSaveStruct->ClearIsInSection(); + } + } + + m_xTable = xSaveStruct->m_xTable; + } + } + break; + + case HtmlTokenId::NOBR_ON: + // HACK for MS: Is the <NOBR> at the start of the cell? + xSaveStruct->StartNoBreak( *m_pPam->GetPoint() ); + break; + + case HtmlTokenId::NOBR_OFF: + xSaveStruct->EndNoBreak( *m_pPam->GetPoint() ); + break; + + case HtmlTokenId::COMMENT: + // Spaces are not gonna be deleted with comment fields, + // and we don't want a new cell for a comment + NextToken( nToken ); + break; + + case HtmlTokenId::MARQUEE_ON: + if( !xSaveStruct->IsInSection() ) + { + // create a new section, the PaM is gonna be there + xSaveStruct->AddContents( + InsertTableContents( bHead ) ); + } + m_bCallNextToken = true; + NewMarquee( pCurTable ); + break; + + case HtmlTokenId::TEXTTOKEN: + // Don't add a section for an empty string + if( !xSaveStruct->IsInSection() && 1==aToken.getLength() && + ' '==aToken[0] ) + break; + [[fallthrough]]; + default: + if( !xSaveStruct->IsInSection() ) + { + // add a new section, the PaM's gonna be there + xSaveStruct->AddContents( + InsertTableContents( bHead ) ); + } + + if( IsParserWorking() || bPending ) + NextToken( nToken ); + break; + } + + OSL_ENSURE( !bPending || m_vPendingStack.empty(), + "SwHTMLParser::BuildTableCell: There is a PendStack again" ); + bPending = false; + if( IsParserWorking() ) + SaveState( HtmlTokenId::NONE ); + + if( !bDone ) + nToken = GetNextToken(); + } + + if( SvParserState::Pending == GetStatus() ) + { + m_vPendingStack.emplace_back( bHead ? HtmlTokenId::TABLEHEADER_ON + : HtmlTokenId::TABLEDATA_ON ); + m_vPendingStack.back().pData = std::move(xSaveStruct); + + return; + } + + // If the content of the cell was empty, we need to create an empty content + // We also create an empty content if the cell ended with a table and had no + // COL tags. Otherwise, it was probably exported by us and we don't + // want to have an additional paragraph + if( !xSaveStruct->HasFirstContents() || + (!xSaveStruct->IsInSection() && !pCurTable->HasColTags()) ) + { + OSL_ENSURE( xSaveStruct->HasFirstContents() || + !xSaveStruct->IsInSection(), + "Section or not, that is the question here" ); + const SwStartNode *pStNd = + InsertTableSection( static_cast< sal_uInt16 >(xSaveStruct->IsHeaderCell() + ? RES_POOLCOLL_TABLE_HDLN + : RES_POOLCOLL_TABLE )); + + if (!pStNd) + eState = SvParserState::Error; + else + { + const SwEndNode *pEndNd = pStNd->EndOfSectionNode(); + SwContentNode *pCNd = m_xDoc->GetNodes()[pEndNd->GetIndex()-1] ->GetContentNode(); + if (!pCNd) + eState = SvParserState::Error; + else + { + //Added defaults to CJK and CTL + SvxFontHeightItem aFontHeight( 40, 100, RES_CHRATR_FONTSIZE ); + pCNd->SetAttr( aFontHeight ); + SvxFontHeightItem aFontHeightCJK( 40, 100, RES_CHRATR_CJK_FONTSIZE ); + pCNd->SetAttr( aFontHeightCJK ); + SvxFontHeightItem aFontHeightCTL( 40, 100, RES_CHRATR_CTL_FONTSIZE ); + pCNd->SetAttr( aFontHeightCTL ); + } + } + + xSaveStruct->AddContents( std::make_unique<HTMLTableCnts>(pStNd) ); + xSaveStruct->ClearIsInSection(); + } + + if( xSaveStruct->IsInSection() ) + { + xSaveStruct->CheckNoBreak( *m_pPam->GetPoint() ); + + // End all open contexts. We'll take AttrMin because nContextStMin might + // have been modified. Since it's gonna be restored by EndContext, it's okay + while( m_aContexts.size() > m_nContextStAttrMin+1 ) + { + std::unique_ptr<HTMLAttrContext> xCntxt(PopContext()); + EndContext(xCntxt.get()); + } + + // Remove LFs at the paragraph end + if (StripTrailingLF() == 0 && !m_pPam->GetPoint()->GetContentIndex()) + { + HTMLTableContext* pTableContext = m_xTable ? m_xTable->GetContext() : nullptr; + SwPosition* pSavedPos = pTableContext ? pTableContext->GetPos() : nullptr; + const bool bDeleteSafe = !pSavedPos || pSavedPos->GetNode() != m_pPam->GetPoint()->GetNode(); + if (bDeleteSafe) + StripTrailingPara(); + } + + // If there was an adjustment set for the cell, we need to close it + std::unique_ptr<HTMLAttrContext> xCntxt(PopContext()); + if (xCntxt) + EndContext(xCntxt.get()); + } + else + { + // Close all still open contexts + while( m_aContexts.size() > m_nContextStAttrMin ) + { + std::unique_ptr<HTMLAttrContext> xCntxt(PopContext()); + if (!xCntxt) + break; + ClearContext(xCntxt.get()); + } + } + + // end an enumeration + GetNumInfo().Clear(); + + SetAttr( false ); + + xSaveStruct->InsertCell( *this, pCurTable ); + + // we're probably before a <TH>, <TD>, <TR> or </TABLE> + xSaveStruct.reset(); +} + +namespace { + +class RowSaveStruct : public SwPendingData +{ +public: + SvxAdjust eAdjust; + sal_Int16 eVertOri; + bool bHasCells; + + RowSaveStruct() : + eAdjust( SvxAdjust::End ), eVertOri( text::VertOrientation::TOP ), bHasCells( false ) + {} +}; + +} + +void SwHTMLParser::BuildTableRow( HTMLTable *pCurTable, bool bReadOptions, + SvxAdjust eGrpAdjust, + sal_Int16 eGrpVertOri ) +{ + // <TR> was already read + + if( !IsParserWorking() && m_vPendingStack.empty() ) + return; + + HtmlTokenId nToken = HtmlTokenId::NONE; + std::unique_ptr<RowSaveStruct> xSaveStruct; + + bool bPending = false; + if( !m_vPendingStack.empty() ) + { + xSaveStruct.reset(static_cast<RowSaveStruct*>(m_vPendingStack.back().pData.release())); + + m_vPendingStack.pop_back(); + nToken = !m_vPendingStack.empty() ? m_vPendingStack.back().nToken : GetSaveToken(); + bPending = SvParserState::Error == eState && !m_vPendingStack.empty(); + + SaveState( nToken ); + } + else + { + SvxAdjust eAdjust = eGrpAdjust; + sal_Int16 eVertOri = eGrpVertOri; + Color aBGColor; + OUString aBGImage, aStyle, aId, aClass; + bool bBGColor = false; + xSaveStruct.reset(new RowSaveStruct); + + if( bReadOptions ) + { + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::ALIGN: + eAdjust = rOption.GetEnum( aHTMLPAlignTable, eAdjust ); + break; + case HtmlOptionId::VALIGN: + eVertOri = rOption.GetEnum( aHTMLTableVAlignTable, eVertOri ); + break; + case HtmlOptionId::BGCOLOR: + // Ignore empty BGCOLOR on <TABLE>, <TR> and <TD>/>TH> like Netscape + // *really* not on other tags + if( !rOption.GetString().isEmpty() ) + { + rOption.GetColor( aBGColor ); + bBGColor = true; + } + break; + case HtmlOptionId::BACKGROUND: + aBGImage = rOption.GetString(); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass= rOption.GetString(); + break; + default: break; + } + } + } + + if( !aId.isEmpty() ) + InsertBookmark( aId ); + + std::unique_ptr<SvxBrushItem> xBrushItem( + CreateBrushItem( bBGColor ? &aBGColor : nullptr, aBGImage, aStyle, + aId, aClass )); + pCurTable->OpenRow(eAdjust, eVertOri, xBrushItem); + // If the first GetNextToken() doesn't succeed (pending input), must re-read from the beginning. + SaveState( HtmlTokenId::NONE ); + } + + if( nToken == HtmlTokenId::NONE ) + nToken = GetNextToken(); + + bool bDone = false; + while( (IsParserWorking() && !bDone) || bPending ) + { + SaveState( nToken ); + + nToken = FilterToken( nToken ); + + OSL_ENSURE( !m_vPendingStack.empty() || !m_bCallNextToken || + pCurTable->GetContext() || pCurTable->HasParentSection(), + "Where is the section??" ); + if( m_vPendingStack.empty() && m_bCallNextToken && + (pCurTable->GetContext() || pCurTable->HasParentSection()) ) + { + /// Call NextToken directly (e.g. ignore the content of floating frames or applets) + NextToken( nToken ); + } + else switch( nToken ) + { + case HtmlTokenId::TABLE_ON: + if( !pCurTable->GetContext() ) + { + SkipToken(); + bDone = true; + } + + break; + case HtmlTokenId::TABLEROW_ON: + case HtmlTokenId::THEAD_ON: + case HtmlTokenId::THEAD_OFF: + case HtmlTokenId::TBODY_ON: + case HtmlTokenId::TBODY_OFF: + case HtmlTokenId::TFOOT_ON: + case HtmlTokenId::TFOOT_OFF: + case HtmlTokenId::TABLE_OFF: + SkipToken(); + [[fallthrough]]; + case HtmlTokenId::TABLEROW_OFF: + bDone = true; + break; + case HtmlTokenId::TABLEHEADER_ON: + case HtmlTokenId::TABLEDATA_ON: + BuildTableCell( pCurTable, true, HtmlTokenId::TABLEHEADER_ON==nToken ); + if( SvParserState::Pending != GetStatus() ) + { + xSaveStruct->bHasCells = true; + bDone = m_xTable->IsOverflowing(); + } + break; + case HtmlTokenId::CAPTION_ON: + BuildTableCaption( pCurTable ); + bDone = m_xTable->IsOverflowing(); + break; + case HtmlTokenId::CAPTION_OFF: + case HtmlTokenId::TABLEHEADER_OFF: + case HtmlTokenId::TABLEDATA_OFF: + case HtmlTokenId::COLGROUP_ON: + case HtmlTokenId::COLGROUP_OFF: + case HtmlTokenId::COL_ON: + case HtmlTokenId::COL_OFF: + // Where no cell started, there can't be a cell ending + // all the other tokens are bogus anyway and only break the table + break; + case HtmlTokenId::MULTICOL_ON: + // we can't add columned text frames here + break; + case HtmlTokenId::FORM_ON: + NewForm( false ); // don't create a new paragraph + break; + case HtmlTokenId::FORM_OFF: + EndForm( false ); // don't create a new paragraph + break; + case HtmlTokenId::COMMENT: + NextToken( nToken ); + break; + case HtmlTokenId::MAP_ON: + // an image map doesn't add anything, so we can parse it without a cell + NextToken( nToken ); + break; + case HtmlTokenId::TEXTTOKEN: + if( (pCurTable->GetContext() || + !pCurTable->HasParentSection()) && + 1==aToken.getLength() && ' '==aToken[0] ) + break; + [[fallthrough]]; + default: + pCurTable->MakeParentContents(); + NextToken( nToken ); + break; + } + + OSL_ENSURE( !bPending || m_vPendingStack.empty(), + "SwHTMLParser::BuildTableRow: There is a PendStack again" ); + bPending = false; + if( IsParserWorking() ) + SaveState( HtmlTokenId::NONE ); + + if( !bDone ) + nToken = GetNextToken(); + } + + if( SvParserState::Pending == GetStatus() ) + { + m_vPendingStack.emplace_back( HtmlTokenId::TABLEROW_ON ); + m_vPendingStack.back().pData = std::move(xSaveStruct); + } + else + { + pCurTable->CloseRow(!xSaveStruct->bHasCells); + xSaveStruct.reset(); + } + + // we're probably before <TR> or </TABLE> +} + +void SwHTMLParser::BuildTableSection( HTMLTable *pCurTable, + bool bReadOptions, + bool bHead ) +{ + // <THEAD>, <TBODY> resp. <TFOOT> were read already + if( !IsParserWorking() && m_vPendingStack.empty() ) + return; + + HtmlTokenId nToken = HtmlTokenId::NONE; + bool bPending = false; + std::unique_ptr<RowSaveStruct> xSaveStruct; + + if( !m_vPendingStack.empty() ) + { + xSaveStruct.reset(static_cast<RowSaveStruct*>(m_vPendingStack.back().pData.release())); + + m_vPendingStack.pop_back(); + nToken = !m_vPendingStack.empty() ? m_vPendingStack.back().nToken : GetSaveToken(); + bPending = SvParserState::Error == eState && !m_vPendingStack.empty(); + + SaveState( nToken ); + } + else + { + xSaveStruct.reset(new RowSaveStruct); + + if( bReadOptions ) + { + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + InsertBookmark( rOption.GetString() ); + break; + case HtmlOptionId::ALIGN: + xSaveStruct->eAdjust = + rOption.GetEnum( aHTMLPAlignTable, xSaveStruct->eAdjust ); + break; + case HtmlOptionId::VALIGN: + xSaveStruct->eVertOri = + rOption.GetEnum( aHTMLTableVAlignTable, + xSaveStruct->eVertOri ); + break; + default: break; + } + } + } + + // If the first GetNextToken() doesn't succeed (pending input), must re-read from the beginning. + SaveState( HtmlTokenId::NONE ); + } + + if( nToken == HtmlTokenId::NONE ) + nToken = GetNextToken(); + + bool bDone = false; + while( (IsParserWorking() && !bDone) || bPending ) + { + SaveState( nToken ); + + nToken = FilterToken( nToken ); + + OSL_ENSURE( !m_vPendingStack.empty() || !m_bCallNextToken || + pCurTable->GetContext() || pCurTable->HasParentSection(), + "Where is the section?" ); + if( m_vPendingStack.empty() && m_bCallNextToken && + (pCurTable->GetContext() || pCurTable->HasParentSection()) ) + { + // Call NextToken directly (e.g. ignore the content of floating frames or applets) + NextToken( nToken ); + } + else switch( nToken ) + { + case HtmlTokenId::TABLE_ON: + if( !pCurTable->GetContext() ) + { + SkipToken(); + bDone = true; + } + + break; + case HtmlTokenId::THEAD_ON: + case HtmlTokenId::TFOOT_ON: + case HtmlTokenId::TBODY_ON: + case HtmlTokenId::TABLE_OFF: + SkipToken(); + [[fallthrough]]; + case HtmlTokenId::THEAD_OFF: + case HtmlTokenId::TBODY_OFF: + case HtmlTokenId::TFOOT_OFF: + bDone = true; + break; + case HtmlTokenId::CAPTION_ON: + BuildTableCaption( pCurTable ); + bDone = m_xTable->IsOverflowing(); + break; + case HtmlTokenId::CAPTION_OFF: + break; + case HtmlTokenId::TABLEHEADER_ON: + case HtmlTokenId::TABLEDATA_ON: + SkipToken(); + BuildTableRow( pCurTable, false, xSaveStruct->eAdjust, + xSaveStruct->eVertOri ); + bDone = m_xTable->IsOverflowing(); + break; + case HtmlTokenId::TABLEROW_ON: + BuildTableRow( pCurTable, true, xSaveStruct->eAdjust, + xSaveStruct->eVertOri ); + bDone = m_xTable->IsOverflowing(); + break; + case HtmlTokenId::MULTICOL_ON: + // we can't add columned text frames here + break; + case HtmlTokenId::FORM_ON: + NewForm( false ); // don't create a new paragraph + break; + case HtmlTokenId::FORM_OFF: + EndForm( false ); // don't create a new paragraph + break; + case HtmlTokenId::TEXTTOKEN: + // blank strings may be a series of CR+LF and no text + if( (pCurTable->GetContext() || + !pCurTable->HasParentSection()) && + 1==aToken.getLength() && ' ' == aToken[0] ) + break; + [[fallthrough]]; + default: + pCurTable->MakeParentContents(); + NextToken( nToken ); + } + + OSL_ENSURE( !bPending || m_vPendingStack.empty(), + "SwHTMLParser::BuildTableSection: There is a PendStack again" ); + bPending = false; + if( IsParserWorking() ) + SaveState( HtmlTokenId::NONE ); + + if( !bDone ) + nToken = GetNextToken(); + } + + if( SvParserState::Pending == GetStatus() ) + { + m_vPendingStack.emplace_back( bHead ? HtmlTokenId::THEAD_ON + : HtmlTokenId::TBODY_ON ); + m_vPendingStack.back().pData = std::move(xSaveStruct); + } + else + { + pCurTable->CloseSection( bHead ); + xSaveStruct.reset(); + } + + // now we stand (perhaps) in front of <TBODY>,... or </TABLE> +} + +namespace { + +struct TableColGrpSaveStruct : public SwPendingData +{ + sal_uInt16 nColGrpSpan; + sal_uInt16 nColGrpWidth; + bool bRelColGrpWidth; + SvxAdjust eColGrpAdjust; + sal_Int16 eColGrpVertOri; + + inline TableColGrpSaveStruct(); + + inline void CloseColGroup( HTMLTable *pTable ); +}; + +} + +inline TableColGrpSaveStruct::TableColGrpSaveStruct() : + nColGrpSpan( 1 ), nColGrpWidth( 0 ), + bRelColGrpWidth( false ), eColGrpAdjust( SvxAdjust::End ), + eColGrpVertOri( text::VertOrientation::TOP ) +{} + +inline void TableColGrpSaveStruct::CloseColGroup( HTMLTable *pTable ) +{ + pTable->CloseColGroup( nColGrpSpan, nColGrpWidth, + bRelColGrpWidth, eColGrpAdjust, eColGrpVertOri ); +} + +void SwHTMLParser::BuildTableColGroup( HTMLTable *pCurTable, + bool bReadOptions ) +{ + // <COLGROUP> was read already if bReadOptions is set + + if( !IsParserWorking() && m_vPendingStack.empty() ) + return; + + HtmlTokenId nToken = HtmlTokenId::NONE; + bool bPending = false; + std::unique_ptr<TableColGrpSaveStruct> pSaveStruct; + + if( !m_vPendingStack.empty() ) + { + pSaveStruct.reset(static_cast<TableColGrpSaveStruct*>(m_vPendingStack.back().pData.release())); + + + m_vPendingStack.pop_back(); + nToken = !m_vPendingStack.empty() ? m_vPendingStack.back().nToken : GetSaveToken(); + bPending = SvParserState::Error == eState && !m_vPendingStack.empty(); + + SaveState( nToken ); + } + else + { + + pSaveStruct.reset(new TableColGrpSaveStruct); + if( bReadOptions ) + { + const HTMLOptions& rColGrpOptions = GetOptions(); + for (size_t i = rColGrpOptions.size(); i; ) + { + const HTMLOption& rOption = rColGrpOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + InsertBookmark( rOption.GetString() ); + break; + case HtmlOptionId::SPAN: + pSaveStruct->nColGrpSpan = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + if (pSaveStruct->nColGrpSpan > 256) + { + SAL_INFO("sw.html", "ignoring huge SPAN " << pSaveStruct->nColGrpSpan); + pSaveStruct->nColGrpSpan = 1; + } + break; + case HtmlOptionId::WIDTH: + pSaveStruct->nColGrpWidth = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + pSaveStruct->bRelColGrpWidth = + (rOption.GetString().indexOf('*') != -1); + break; + case HtmlOptionId::ALIGN: + pSaveStruct->eColGrpAdjust = + rOption.GetEnum( aHTMLPAlignTable, pSaveStruct->eColGrpAdjust ); + break; + case HtmlOptionId::VALIGN: + pSaveStruct->eColGrpVertOri = + rOption.GetEnum( aHTMLTableVAlignTable, + pSaveStruct->eColGrpVertOri ); + break; + default: break; + } + } + } + // If the first GetNextToken() doesn't succeed (pending input), must re-read from the beginning. + SaveState( HtmlTokenId::NONE ); + } + + if( nToken == HtmlTokenId::NONE ) + nToken = GetNextToken(); // naechstes Token + + bool bDone = false; + while( (IsParserWorking() && !bDone) || bPending ) + { + SaveState( nToken ); + + nToken = FilterToken( nToken ); + + OSL_ENSURE( !m_vPendingStack.empty() || !m_bCallNextToken || + pCurTable->GetContext() || pCurTable->HasParentSection(), + "Where is the section?" ); + if( m_vPendingStack.empty() && m_bCallNextToken && + (pCurTable->GetContext() || pCurTable->HasParentSection()) ) + { + // Call NextToken directly (e.g. ignore the content of floating frames or applets) + NextToken( nToken ); + } + else switch( nToken ) + { + case HtmlTokenId::TABLE_ON: + if( !pCurTable->GetContext() ) + { + SkipToken(); + bDone = true; + } + + break; + case HtmlTokenId::COLGROUP_ON: + case HtmlTokenId::THEAD_ON: + case HtmlTokenId::TFOOT_ON: + case HtmlTokenId::TBODY_ON: + case HtmlTokenId::TABLEROW_ON: + case HtmlTokenId::TABLE_OFF: + SkipToken(); + [[fallthrough]]; + case HtmlTokenId::COLGROUP_OFF: + bDone = true; + break; + case HtmlTokenId::COL_ON: + { + sal_uInt16 nColSpan = 1; + sal_uInt16 nColWidth = pSaveStruct->nColGrpWidth; + bool bRelColWidth = pSaveStruct->bRelColGrpWidth; + SvxAdjust eColAdjust = pSaveStruct->eColGrpAdjust; + sal_Int16 eColVertOri = pSaveStruct->eColGrpVertOri; + + const HTMLOptions& rColOptions = GetOptions(); + for (size_t i = rColOptions.size(); i; ) + { + const HTMLOption& rOption = rColOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + InsertBookmark( rOption.GetString() ); + break; + case HtmlOptionId::SPAN: + nColSpan = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + if (nColSpan > 256) + { + SAL_INFO("sw.html", "ignoring huge SPAN " << nColSpan); + nColSpan = 1; + } + break; + case HtmlOptionId::WIDTH: + nColWidth = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + bRelColWidth = + (rOption.GetString().indexOf('*') != -1); + break; + case HtmlOptionId::ALIGN: + eColAdjust = rOption.GetEnum( aHTMLPAlignTable, eColAdjust ); + break; + case HtmlOptionId::VALIGN: + eColVertOri = + rOption.GetEnum( aHTMLTableVAlignTable, eColVertOri ); + break; + default: break; + } + } + pCurTable->InsertCol( nColSpan, nColWidth, bRelColWidth, + eColAdjust, eColVertOri ); + + // the attributes in <COLGRP> should be ignored, if there are <COL> elements + pSaveStruct->nColGrpSpan = 0; + } + break; + case HtmlTokenId::COL_OFF: + break; // Ignore + case HtmlTokenId::MULTICOL_ON: + // we can't add columned text frames here + break; + case HtmlTokenId::TEXTTOKEN: + if( (pCurTable->GetContext() || + !pCurTable->HasParentSection()) && + 1==aToken.getLength() && ' '==aToken[0] ) + break; + [[fallthrough]]; + default: + pCurTable->MakeParentContents(); + NextToken( nToken ); + } + + OSL_ENSURE( !bPending || m_vPendingStack.empty(), + "SwHTMLParser::BuildTableColGrp: There is a PendStack again" ); + bPending = false; + if( IsParserWorking() ) + SaveState( HtmlTokenId::NONE ); + + if( !bDone ) + nToken = GetNextToken(); + } + + if( SvParserState::Pending == GetStatus() ) + { + m_vPendingStack.emplace_back( HtmlTokenId::COL_ON ); + m_vPendingStack.back().pData = std::move(pSaveStruct); + } + else + { + pSaveStruct->CloseColGroup( pCurTable ); + } +} + +class CaptionSaveStruct : public SectionSaveStruct +{ + SwPosition m_aSavePos; + SwHTMLNumRuleInfo m_aNumRuleInfo; // valid numbering + +public: + + std::shared_ptr<HTMLAttrTable> m_xAttrTab; // attributes + + CaptionSaveStruct( SwHTMLParser& rParser, SwPosition aPos ) : + SectionSaveStruct( rParser ), m_aSavePos(std::move( aPos )), + m_xAttrTab(std::make_shared<HTMLAttrTable>()) + { + rParser.SaveAttrTab(m_xAttrTab); + + // The current numbering was remembered and just needs to be closed + m_aNumRuleInfo.Set( rParser.GetNumInfo() ); + rParser.GetNumInfo().Clear(); + } + + const SwPosition& GetPos() const { return m_aSavePos; } + + void RestoreAll( SwHTMLParser& rParser ) + { + // Recover the old stack + Restore( rParser ); + + // Recover the old attribute tables + rParser.RestoreAttrTab(m_xAttrTab); + + // Re-open the old numbering + rParser.GetNumInfo().Set( m_aNumRuleInfo ); + } +}; + +void SwHTMLParser::BuildTableCaption( HTMLTable *pCurTable ) +{ + // <CAPTION> was read already + + if( !IsParserWorking() && m_vPendingStack.empty() ) + return; + + HtmlTokenId nToken = HtmlTokenId::NONE; + std::unique_ptr<CaptionSaveStruct> xSaveStruct; + + if( !m_vPendingStack.empty() ) + { + xSaveStruct.reset(static_cast<CaptionSaveStruct*>(m_vPendingStack.back().pData.release())); + + m_vPendingStack.pop_back(); + nToken = !m_vPendingStack.empty() ? m_vPendingStack.back().nToken : GetSaveToken(); + OSL_ENSURE( m_vPendingStack.empty(), "Where does a PendStack coming from?" ); + + SaveState( nToken ); + } + else + { + if (m_xTable->IsOverflowing()) + { + SaveState( HtmlTokenId::NONE ); + return; + } + + bool bTop = true; + const HTMLOptions& rHTMLOptions = GetOptions(); + for ( size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + if( HtmlOptionId::ALIGN == rOption.GetToken() ) + { + if (rOption.GetString().equalsIgnoreAsciiCase( + OOO_STRING_SVTOOLS_HTML_VA_bottom)) + { + bTop = false; + } + } + } + + // Remember old PaM position + xSaveStruct.reset(new CaptionSaveStruct(*this, *m_pPam->GetPoint())); + + // Add a text section in the icon section as a container for the header + // and set the PaM there + const SwStartNode *pStNd; + if (m_xTable.get() == pCurTable) + pStNd = InsertTempTableCaptionSection(); + else + pStNd = InsertTableSection( RES_POOLCOLL_TEXT ); + + std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(HtmlTokenId::CAPTION_ON)); + + // Table headers are always centered + NewAttr(m_xAttrTab, &m_xAttrTab->pAdjust, SvxAdjustItem(SvxAdjust::Center, RES_PARATR_ADJUST)); + + HTMLAttrs &rAttrs = xCntxt->GetAttrs(); + rAttrs.push_back( m_xAttrTab->pAdjust ); + + PushContext(xCntxt); + + // Remember the start node of the section at the table + pCurTable->SetCaption( pStNd, bTop ); + + // If the first GetNextToken() doesn't succeed (pending input), must re-read from the beginning. + SaveState( HtmlTokenId::NONE ); + } + + if( nToken == HtmlTokenId::NONE ) + nToken = GetNextToken(); + + // </CAPTION> is needed according to DTD + bool bDone = false; + while( IsParserWorking() && !bDone ) + { + SaveState( nToken ); + + nToken = FilterToken( nToken ); + + switch( nToken ) + { + case HtmlTokenId::TABLE_ON: + if( m_vPendingStack.empty() ) + { + xSaveStruct->m_xTable = m_xTable; + bool bHasToFly = xSaveStruct->m_xTable.get() != pCurTable; + BuildTable( pCurTable->GetTableAdjust( true ), + false, true, bHasToFly ); + } + else + { + BuildTable( SvxAdjust::End ); + } + if( SvParserState::Pending != GetStatus() ) + { + m_xTable = xSaveStruct->m_xTable; + } + break; + case HtmlTokenId::TABLE_OFF: + case HtmlTokenId::COLGROUP_ON: + case HtmlTokenId::THEAD_ON: + case HtmlTokenId::TFOOT_ON: + case HtmlTokenId::TBODY_ON: + case HtmlTokenId::TABLEROW_ON: + SkipToken(); + bDone = true; + break; + + case HtmlTokenId::CAPTION_OFF: + bDone = true; + break; + default: + if( !m_vPendingStack.empty() ) + { + m_vPendingStack.pop_back(); + OSL_ENSURE( m_vPendingStack.empty(), "Further it can't go!" ); + } + + if( IsParserWorking() ) + NextToken( nToken ); + break; + } + + if( IsParserWorking() ) + SaveState( HtmlTokenId::NONE ); + + if( !bDone ) + nToken = GetNextToken(); + } + + if( SvParserState::Pending==GetStatus() ) + { + m_vPendingStack.emplace_back( HtmlTokenId::CAPTION_ON ); + m_vPendingStack.back().pData = std::move(xSaveStruct); + return; + } + + // end all still open contexts + while( m_aContexts.size() > m_nContextStAttrMin+1 ) + { + std::unique_ptr<HTMLAttrContext> xCntxt(PopContext()); + EndContext(xCntxt.get()); + } + + bool bLFStripped = StripTrailingLF() > 0; + + if (m_xTable.get() == pCurTable) + { + // On moving the caption later, the last paragraph isn't moved as well. + // That means, there has to be an empty paragraph at the end of the section + if( m_pPam->GetPoint()->GetContentIndex() || bLFStripped ) + AppendTextNode( AM_NOSPACE ); + } + else + { + // Strip LFs at the end of the paragraph + if( !m_pPam->GetPoint()->GetContentIndex() && !bLFStripped ) + StripTrailingPara(); + } + + // If there's an adjustment for the cell, we need to close it + std::unique_ptr<HTMLAttrContext> xCntxt(PopContext()); + if (xCntxt) + { + EndContext(xCntxt.get()); + xCntxt.reset(); + } + + SetAttr( false ); + + // Recover stack and attribute table + xSaveStruct->RestoreAll(*this); + + // Recover PaM + *m_pPam->GetPoint() = xSaveStruct->GetPos(); +} + +namespace { + +class TableSaveStruct : public SwPendingData +{ +public: + std::shared_ptr<HTMLTable> m_xCurrentTable; + + explicit TableSaveStruct(std::shared_ptr<HTMLTable> xCurTable) + : m_xCurrentTable(std::move(xCurTable)) + { + } + + // Initiate creation of the table and put the table in a text frame if + // needed. If it returns true, we need to insert a paragraph. + void MakeTable( sal_uInt16 nWidth, SwPosition& rPos, SwDoc *pDoc ); +}; + +} + +void TableSaveStruct::MakeTable( sal_uInt16 nWidth, SwPosition& rPos, SwDoc *pDoc ) +{ + m_xCurrentTable->MakeTable(nullptr, nWidth); + + HTMLTableContext *pTCntxt = m_xCurrentTable->GetContext(); + OSL_ENSURE( pTCntxt, "Where is the table context" ); + + SwTableNode *pTableNd = pTCntxt->GetTableNode(); + OSL_ENSURE( pTableNd, "Where is the table node" ); + + if( pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() && pTableNd ) + { + // If there's already a layout, the BoxFrames need to be regenerated at this table + + if( pTCntxt->GetFrameFormat() ) + { + pTCntxt->GetFrameFormat()->DelFrames(); + pTableNd->DelFrames(); + pTCntxt->GetFrameFormat()->MakeFrames(); + } + else + { + pTableNd->DelFrames(); + SwNodeIndex aIdx( *pTableNd->EndOfSectionNode(), 1 ); + OSL_ENSURE( aIdx.GetIndex() <= pTCntxt->GetPos()->GetNodeIndex(), + "unexpected node for table layout" ); + pTableNd->MakeOwnFrames(); + } + } + + rPos = *pTCntxt->GetPos(); +} + +HTMLTableOptions::HTMLTableOptions( const HTMLOptions& rOptions, + SvxAdjust eParentAdjust ) : + nCols( 0 ), + nWidth( 0 ), nHeight( 0 ), + nCellPadding( USHRT_MAX ), nCellSpacing( USHRT_MAX ), + nBorder( USHRT_MAX ), + nHSpace( 0 ), nVSpace( 0 ), + eAdjust( eParentAdjust ), eVertOri( text::VertOrientation::CENTER ), + eFrame( HTMLTableFrame::Void ), eRules( HTMLTableRules::NONE ), + bPercentWidth( false ), + bTableAdjust( false ), + bBGColor( false ), + aBorderColor( COL_GRAY ) +{ + bool bBorderColor = false; + bool bHasFrame = false, bHasRules = false; + + for (size_t i = rOptions.size(); i; ) + { + const HTMLOption& rOption = rOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::COLS: + nCols = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + break; + case HtmlOptionId::WIDTH: + nWidth = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + bPercentWidth = (rOption.GetString().indexOf('%') != -1); + if( bPercentWidth && nWidth>100 ) + nWidth = 100; + break; + case HtmlOptionId::HEIGHT: + nHeight = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + if( rOption.GetString().indexOf('%') != -1 ) + nHeight = 0; // don't use % attributes + break; + case HtmlOptionId::CELLPADDING: + nCellPadding = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + break; + case HtmlOptionId::CELLSPACING: + nCellSpacing = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + break; + case HtmlOptionId::ALIGN: + { + if( rOption.GetEnum( eAdjust, aHTMLPAlignTable ) ) + { + bTableAdjust = true; + } + } + break; + case HtmlOptionId::VALIGN: + eVertOri = rOption.GetEnum( aHTMLTableVAlignTable, eVertOri ); + break; + case HtmlOptionId::BORDER: + // Handle BORDER and BORDER=BORDER like BORDER=1 + if (!rOption.GetString().isEmpty() && + !rOption.GetString().equalsIgnoreAsciiCase( + OOO_STRING_SVTOOLS_HTML_O_border)) + { + nBorder = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + } + else + nBorder = 1; + + if( !bHasFrame ) + eFrame = ( nBorder ? HTMLTableFrame::Box : HTMLTableFrame::Void ); + if( !bHasRules ) + eRules = ( nBorder ? HTMLTableRules::All : HTMLTableRules::NONE ); + break; + case HtmlOptionId::FRAME: + eFrame = rOption.GetTableFrame(); + bHasFrame = true; + break; + case HtmlOptionId::RULES: + eRules = rOption.GetTableRules(); + bHasRules = true; + break; + case HtmlOptionId::BGCOLOR: + // Ignore empty BGCOLOR on <TABLE>, <TR> and <TD>/<TH> like Netscape + // *really* not on other tags + if( !rOption.GetString().isEmpty() ) + { + rOption.GetColor( aBGColor ); + bBGColor = true; + } + break; + case HtmlOptionId::BACKGROUND: + aBGImage = rOption.GetString(); + break; + case HtmlOptionId::BORDERCOLOR: + rOption.GetColor( aBorderColor ); + bBorderColor = true; + break; + case HtmlOptionId::BORDERCOLORDARK: + if( !bBorderColor ) + rOption.GetColor( aBorderColor ); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::DIR: + aDir = rOption.GetString(); + break; + case HtmlOptionId::HSPACE: + nHSpace = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + break; + case HtmlOptionId::VSPACE: + nVSpace = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + break; + default: break; + } + } + + if( nCols && !nWidth ) + { + nWidth = 100; + bPercentWidth = true; + } + + // If BORDER=0 or no BORDER given, then there shouldn't be a border + if( 0==nBorder || USHRT_MAX==nBorder ) + { + eFrame = HTMLTableFrame::Void; + eRules = HTMLTableRules::NONE; + } +} + +void SwHTMLParser::DeleteSection(SwStartNode* pSttNd) +{ + //if section to be deleted contains a pending m_pMarquee, it will be deleted + //so clear m_pMarquee pointer if that's the case + SwFrameFormat* pObjectFormat = m_pMarquee ? ::FindFrameFormat(m_pMarquee.get()) : nullptr; + FrameDeleteWatch aWatch(pObjectFormat); + + m_xDoc->getIDocumentContentOperations().DeleteSection(pSttNd); + + if (pObjectFormat) + { + if (aWatch.WasDeleted()) + m_pMarquee = nullptr; + else + aWatch.EndListeningAll(); + } +} + +std::shared_ptr<HTMLTable> SwHTMLParser::BuildTable(SvxAdjust eParentAdjust, + bool bIsParentHead, + bool bHasParentSection, + bool bHasToFly) +{ + TableDepthGuard aGuard(*this); + if (aGuard.TooDeep()) + eState = SvParserState::Error; + + if (!IsParserWorking() && m_vPendingStack.empty()) + return std::shared_ptr<HTMLTable>(); + + ::comphelper::FlagRestorationGuard g(m_isInTableStructure, true); + HtmlTokenId nToken = HtmlTokenId::NONE; + bool bPending = false; + std::unique_ptr<TableSaveStruct> xSaveStruct; + + if( !m_vPendingStack.empty() ) + { + xSaveStruct.reset(static_cast<TableSaveStruct*>(m_vPendingStack.back().pData.release())); + + m_vPendingStack.pop_back(); + nToken = !m_vPendingStack.empty() ? m_vPendingStack.back().nToken : GetSaveToken(); + bPending = SvParserState::Error == eState && !m_vPendingStack.empty(); + + SaveState( nToken ); + } + else + { + m_xTable.reset(); + + // Parse CSS on the table. + OUString aStyle; + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i;) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + if (rOption.GetToken() == HtmlOptionId::STYLE) + { + aStyle = rOption.GetString(); + } + } + if (!aStyle.isEmpty()) + { + // Have inline CSS. + SfxItemSet aItemSet(m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap()); + SvxCSS1PropertyInfo aPropInfo; + if (ParseStyleOptions(aStyle, /*aId=*/OUString(), /*aClass=*/OUString(), aItemSet, + aPropInfo)) + { + if (aPropInfo.m_eLeftMarginType == SVX_CSS1_LTYPE_AUTO + && aPropInfo.m_eRightMarginType == SVX_CSS1_LTYPE_AUTO) + { + // Both left & right is set to auto: that's our center. + eParentAdjust = SvxAdjust::Center; + } + } + } + + HTMLTableOptions aTableOptions(GetOptions(), eParentAdjust); + + if (!aTableOptions.aId.isEmpty()) + InsertBookmark(aTableOptions.aId); + + std::shared_ptr<HTMLTable> xCurTable(std::make_shared<HTMLTable>(this, + bIsParentHead, + bHasParentSection, + bHasToFly, + aTableOptions)); + m_xTable = xCurTable; + + xSaveStruct.reset(new TableSaveStruct(xCurTable)); + + // Is pending on the first GetNextToken, needs to be re-read on each construction + SaveState( HtmlTokenId::NONE ); + } + + std::shared_ptr<HTMLTable> xCurTable = xSaveStruct->m_xCurrentTable; + + // </TABLE> is needed according to DTD + if( nToken == HtmlTokenId::NONE ) + nToken = GetNextToken(); + + bool bDone = false; + while( (IsParserWorking() && !bDone) || bPending ) + { + SaveState( nToken ); + + nToken = FilterToken( nToken ); + + OSL_ENSURE( !m_vPendingStack.empty() || !m_bCallNextToken || + xCurTable->GetContext() || xCurTable->HasParentSection(), + "Where is the section?" ); + if( m_vPendingStack.empty() && m_bCallNextToken && + (xCurTable->GetContext() || xCurTable->HasParentSection()) ) + { + /// Call NextToken directly (e.g. ignore the content of floating frames or applets) + NextToken( nToken ); + } + else switch( nToken ) + { + case HtmlTokenId::TABLE_ON: + if( !xCurTable->GetContext() ) + { + // If there's no table added, read the next table' + SkipToken(); + bDone = true; + } + + break; + case HtmlTokenId::TABLE_OFF: + bDone = true; + break; + case HtmlTokenId::CAPTION_ON: + BuildTableCaption(xCurTable.get()); + bDone = m_xTable->IsOverflowing(); + break; + case HtmlTokenId::COL_ON: + SkipToken(); + BuildTableColGroup(xCurTable.get(), false); + break; + case HtmlTokenId::COLGROUP_ON: + BuildTableColGroup(xCurTable.get(), true); + break; + case HtmlTokenId::TABLEROW_ON: + case HtmlTokenId::TABLEHEADER_ON: + case HtmlTokenId::TABLEDATA_ON: + SkipToken(); + BuildTableSection(xCurTable.get(), false, false); + bDone = m_xTable->IsOverflowing(); + break; + case HtmlTokenId::THEAD_ON: + case HtmlTokenId::TFOOT_ON: + case HtmlTokenId::TBODY_ON: + BuildTableSection(xCurTable.get(), true, HtmlTokenId::THEAD_ON==nToken); + bDone = m_xTable->IsOverflowing(); + break; + case HtmlTokenId::MULTICOL_ON: + // we can't add columned text frames here + break; + case HtmlTokenId::FORM_ON: + NewForm( false ); // don't add a new paragraph + break; + case HtmlTokenId::FORM_OFF: + EndForm( false ); // don't add a new paragraph + break; + case HtmlTokenId::TEXTTOKEN: + // blank strings may be a series of CR+LF and no text + if( (xCurTable->GetContext() || + !xCurTable->HasParentSection()) && + 1==aToken.getLength() && ' '==aToken[0] ) + break; + [[fallthrough]]; + default: + xCurTable->MakeParentContents(); + NextToken( nToken ); + break; + } + + OSL_ENSURE( !bPending || m_vPendingStack.empty(), + "SwHTMLParser::BuildTable: There is a PendStack again" ); + bPending = false; + if( IsParserWorking() ) + SaveState( HtmlTokenId::NONE ); + + if( !bDone ) + nToken = GetNextToken(); + } + + if( SvParserState::Pending == GetStatus() ) + { + m_vPendingStack.emplace_back( HtmlTokenId::TABLE_ON ); + m_vPendingStack.back().pData = std::move(xSaveStruct); + return std::shared_ptr<HTMLTable>(); + } + + HTMLTableContext *pTCntxt = xCurTable->GetContext(); + if( pTCntxt ) + { + + // Modify table structure + xCurTable->CloseTable(); + + // end contexts that began out of cells. Needs to exist before (!) we move the table, + // since the current one doesn't exist anymore afterwards + while( m_aContexts.size() > m_nContextStAttrMin ) + { + std::unique_ptr<HTMLAttrContext> xCntxt(PopContext()); + if (!xCntxt) + break; + ClearContext(xCntxt.get()); + } + + m_nContextStMin = pTCntxt->GetContextStMin(); + m_nContextStAttrMin = pTCntxt->GetContextStAttrMin(); + + if (m_xTable == xCurTable) + { + // Set table caption + const SwStartNode *pCapStNd = m_xTable->GetCaptionStartNode(); + if( pCapStNd ) + { + // The last paragraph of the section is never part of the copy. + // That's why the section needs to contain at least two paragraphs + + if( pCapStNd->EndOfSectionIndex() - pCapStNd->GetIndex() > SwNodeOffset(2) ) + { + // Don't copy start node and the last paragraph + SwNodeRange aSrcRg( *pCapStNd, SwNodeOffset(1), + *pCapStNd->EndOfSectionNode(), SwNodeOffset(-1) ); + + bool bTop = m_xTable->IsTopCaption(); + SwStartNode *pTableStNd = pTCntxt->GetTableNode(); + + OSL_ENSURE( pTableStNd, "Where is the table node" ); + OSL_ENSURE( pTableStNd == m_pPam->GetPointNode().FindTableNode(), + "Are we in the wrong table?" ); + + if (pTableStNd) + { + SwNode* pNd; + if( bTop ) + pNd = pTableStNd; + else + pNd = pTableStNd->EndOfSectionNode(); + SwNodeIndex aDstIdx( *pNd, bTop ? 0 : 1 ); + + m_xDoc->getIDocumentContentOperations().MoveNodeRange( aSrcRg, aDstIdx.GetNode(), + SwMoveFlags::DEFAULT ); + + // If the caption was added before the table, a page style on that table + // needs to be moved to the first paragraph of the header. + // Additionally, all remembered indices that point to the table node + // need to be moved + if( bTop ) + { + MovePageDescAttrs( pTableStNd, aSrcRg.aStart.GetIndex(), + false ); + } + } + } + + // The section isn't needed anymore + m_pPam->SetMark(); + m_pPam->DeleteMark(); + DeleteSection(const_cast<SwStartNode*>(pCapStNd)); + m_xTable->SetCaption( nullptr, false ); + } + + // Process SwTable + sal_uInt16 nBrowseWidth = o3tl::narrowing<sal_uInt16>(GetCurrentBrowseWidth()); + xSaveStruct->MakeTable(nBrowseWidth, *m_pPam->GetPoint(), m_xDoc.get()); + } + + GetNumInfo().Set( pTCntxt->GetNumInfo() ); + pTCntxt->RestorePREListingXMP( *this ); + RestoreAttrTab(pTCntxt->m_xAttrTab); + + if (m_xTable == xCurTable) + { + // Set upper paragraph spacing + m_bUpperSpace = true; + SetTextCollAttrs(); + + SwTableNode* pTableNode = pTCntxt->GetTableNode(); + size_t nTableBoxSize = pTableNode ? pTableNode->GetTable().GetTabSortBoxes().size() : 0; + m_nParaCnt = m_nParaCnt - std::min(m_nParaCnt, nTableBoxSize); + + // Jump to a table if needed + if( JumpToMarks::Table == m_eJumpTo && m_xTable->GetSwTable() && + m_xTable->GetSwTable()->GetFrameFormat()->GetName() == m_sJmpMark ) + { + m_bChkJumpMark = true; + m_eJumpTo = JumpToMarks::NONE; + } + + // If the import was canceled, don't call Show again here since + // the SwViewShell was already deleted + // That's not enough. Even in the ACCEPTING_STATE, a Show mustn't be called + // because otherwise the parser's gonna be destroyed on the reschedule, + // if there's still a DataAvailable link coming. So: only in the WORKING state + if( !m_nParaCnt && SvParserState::Working == GetStatus() ) + Show(); + } + } + else if (m_xTable == xCurTable) + { + // There was no table read + + // We maybe need to delete a read caption + const SwStartNode *pCapStNd = xCurTable->GetCaptionStartNode(); + if( pCapStNd ) + { + m_pPam->SetMark(); + m_pPam->DeleteMark(); + DeleteSection(const_cast<SwStartNode*>(pCapStNd)); + xCurTable->SetCaption( nullptr, false ); + } + } + + if (m_xTable == xCurTable) + { + xSaveStruct->m_xCurrentTable.reset(); + m_xTable.reset(); + } + + std::shared_ptr<HTMLTable> xRetTable = xSaveStruct->m_xCurrentTable; + xSaveStruct.reset(); + + return xRetTable; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmltabw.cxx b/sw/source/filter/html/htmltabw.cxx new file mode 100644 index 0000000000..b1ef282396 --- /dev/null +++ b/sw/source/filter/html/htmltabw.cxx @@ -0,0 +1,1156 @@ +/* -*- 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 <hintids.hxx> +#include <vcl/svapp.hxx> +#include <svtools/htmlout.hxx> +#include <svtools/htmltokn.h> +#include <svtools/htmlkywd.hxx> +#include <svtools/HtmlWriter.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/boxitem.hxx> +#include <fmtornt.hxx> +#include <frmfmt.hxx> +#include <fmtfsize.hxx> +#include <fmtsrnd.hxx> +#include <frmatr.hxx> +#include <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <swrect.hxx> +#include <cellatr.hxx> +#include <poolfmt.hxx> +#include <swtable.hxx> +#include <htmltbl.hxx> +#include "htmlnum.hxx" +#include "wrthtml.hxx" +#include <wrtswtbl.hxx> +#ifdef DBG_UTIL +#include <viewsh.hxx> +#include <viewopt.hxx> +#endif +#include <rtl/strbuf.hxx> +#include <sal/types.h> +#include <osl/diagnose.h> + +#define MAX_DEPTH (3) + +using namespace ::com::sun::star; + +namespace { + +class SwHTMLWrtTable : public SwWriteTable +{ + static void Pixelize( sal_uInt16& rValue ); + void PixelizeBorders(); + + /// Writes a single table cell. + /// + /// bCellRowSpan decides if the cell's row span should be written or not. + void OutTableCell( SwHTMLWriter& rWrt, const SwWriteTableCell *pCell, + bool bOutVAlign, + bool bCellRowSpan ) const; + + /// Writes a single table row. + /// + /// rSkipRows decides if the next N rows should be skipped or written. + void OutTableCells( SwHTMLWriter& rWrt, + const SwWriteTableCells& rCells, + const SvxBrushItem *pBrushItem, + sal_uInt16& rSkipRows ) const; + + virtual bool ShouldExpandSub( const SwTableBox *pBox, + bool bExpandedBefore, sal_uInt16 nDepth ) const override; + + static bool HasTabBackground( const SwTableLine& rLine, + bool bTop, bool bBottom, bool bLeft, bool bRight ); + static bool HasTabBackground( const SwTableBox& rBox, + bool bTop, bool bBottom, bool bLeft, bool bRight ); + +public: + SwHTMLWrtTable( const SwTableLines& rLines, tools::Long nWidth, sal_uInt32 nBWidth, + bool bRel, sal_uInt16 nLeftSub, sal_uInt16 nRightSub, + sal_uInt16 nNumOfRowsToRepeat ); + explicit SwHTMLWrtTable( const SwHTMLTableLayout *pLayoutInfo ); + + void Write( SwHTMLWriter& rWrt, sal_Int16 eAlign=text::HoriOrientation::NONE, + bool bTHead=false, const SwFrameFormat *pFrameFormat=nullptr, + const OUString *pCaption=nullptr, bool bTopCaption=false, + sal_uInt16 nHSpace=0, sal_uInt16 nVSpace=0 ) const; +}; + +} + +SwHTMLWrtTable::SwHTMLWrtTable( const SwTableLines& rLines, tools::Long nWidth, + sal_uInt32 nBWidth, bool bRel, + sal_uInt16 nLSub, sal_uInt16 nRSub, + sal_uInt16 nNumOfRowsToRepeat ) + : SwWriteTable(nullptr, rLines, nWidth, nBWidth, bRel, MAX_DEPTH, nLSub, nRSub, nNumOfRowsToRepeat) +{ + PixelizeBorders(); +} + +SwHTMLWrtTable::SwHTMLWrtTable( const SwHTMLTableLayout *pLayoutInfo ) + : SwWriteTable(nullptr, pLayoutInfo) +{ + // Adjust some Twip values to pixel limits + if( m_bCollectBorderWidth ) + PixelizeBorders(); +} + +void SwHTMLWrtTable::Pixelize( sal_uInt16& rValue ) +{ + if( rValue ) + { + rValue = o3tl::convert(SwHTMLWriter::ToPixel(rValue), o3tl::Length::px, o3tl::Length::twip); + } +} + +void SwHTMLWrtTable::PixelizeBorders() +{ + Pixelize( m_nBorder ); + Pixelize( m_nCellSpacing ); + Pixelize( m_nCellPadding ); +} + +bool SwHTMLWrtTable::HasTabBackground( const SwTableBox& rBox, + bool bTop, bool bBottom, bool bLeft, bool bRight ) +{ + OSL_ENSURE( bTop || bBottom || bLeft || bRight, + "HasTabBackground: cannot be called" ); + + bool bRet = false; + if( rBox.GetSttNd() ) + { + std::unique_ptr<SvxBrushItem> aBrushItem = + rBox.GetFrameFormat()->makeBackgroundBrushItem(); + + /// The table box has a background, if its background color is not "no fill"/ + /// "auto fill" or it has a background graphic. + bRet = aBrushItem->GetColor() != COL_TRANSPARENT || + !aBrushItem->GetGraphicLink().isEmpty() || aBrushItem->GetGraphic(); + } + else + { + const SwTableLines& rLines = rBox.GetTabLines(); + const SwTableLines::size_type nCount = rLines.size(); + bool bLeftRight = bLeft || bRight; + for( SwTableLines::size_type i=0; !bRet && i<nCount; ++i ) + { + bool bT = bTop && 0 == i; + bool bB = bBottom && nCount-1 == i; + if( bT || bB || bLeftRight ) + bRet = HasTabBackground( *rLines[i], bT, bB, bLeft, bRight); + } + } + + return bRet; +} + +bool SwHTMLWrtTable::HasTabBackground( const SwTableLine& rLine, + bool bTop, bool bBottom, bool bLeft, bool bRight ) +{ + OSL_ENSURE( bTop || bBottom || bLeft || bRight, + "HasTabBackground: cannot be called" ); + + std::unique_ptr<SvxBrushItem> aBrushItem = rLine.GetFrameFormat()->makeBackgroundBrushItem(); + /// The table line has a background, if its background color is not "no fill"/ + /// "auto fill" or it has a background graphic. + bool bRet = aBrushItem->GetColor() != COL_TRANSPARENT || + !aBrushItem->GetGraphicLink().isEmpty() || aBrushItem->GetGraphic(); + + if( !bRet ) + { + const SwTableBoxes& rBoxes = rLine.GetTabBoxes(); + const SwTableBoxes::size_type nCount = rBoxes.size(); + bool bTopBottom = bTop || bBottom; + for( SwTableBoxes::size_type i=0; !bRet && i<nCount; ++i ) + { + bool bL = bLeft && 0 == i; + bool bR = bRight && nCount-1 == i; + if( bTopBottom || bL || bR ) + bRet = HasTabBackground( *rBoxes[i], bTop, bBottom, bL, bR ); + } + } + + return bRet; +} + +static bool lcl_TableLine_HasTabBorders( const SwTableLine* pLine, bool *pBorders ); + +static bool lcl_TableBox_HasTabBorders( const SwTableBox* pBox, bool *pBorders ) +{ + if( *pBorders ) + return false; + + if( !pBox->GetSttNd() ) + { + for( const auto& rpLine : pBox->GetTabLines() ) + { + if ( lcl_TableLine_HasTabBorders( rpLine, pBorders ) ) + break; + } + } + else + { + const SvxBoxItem& rBoxItem = + pBox->GetFrameFormat()->GetFormatAttr( RES_BOX ); + + *pBorders = rBoxItem.GetTop() || rBoxItem.GetBottom() || + rBoxItem.GetLeft() || rBoxItem.GetRight(); + } + + return !*pBorders; +} + +static bool lcl_TableLine_HasTabBorders( const SwTableLine* pLine, bool *pBorders ) +{ + if( *pBorders ) + return false; + + for( const auto& rpBox : pLine->GetTabBoxes() ) + { + if ( lcl_TableBox_HasTabBorders( rpBox, pBorders ) ) + break; + } + return !*pBorders; +} + +bool SwHTMLWrtTable::ShouldExpandSub( const SwTableBox *pBox, + bool bExpandedBefore, + sal_uInt16 nDepth ) const +{ + bool bExpand = !pBox->GetSttNd() && nDepth>0; + if( bExpand && bExpandedBefore ) + { + // MIB 30.6.97: If a box was already expanded, another one is only + // expanded when it has a border. + bool bBorders = false; + lcl_TableBox_HasTabBorders( pBox, &bBorders ); + if( !bBorders ) + bBorders = HasTabBackground( *pBox, true, true, true, true ); + bExpand = bBorders; + } + + return bExpand; +} + +// Write a box as single cell +void SwHTMLWrtTable::OutTableCell( SwHTMLWriter& rWrt, + const SwWriteTableCell *pCell, + bool bOutVAlign, + bool bCellRowSpan ) const +{ + const SwTableBox *pBox = pCell->GetBox(); + sal_uInt16 nRow = pCell->GetRow(); + sal_uInt16 nCol = pCell->GetCol(); + sal_uInt16 nRowSpan = pCell->GetRowSpan(); + sal_uInt16 nColSpan = pCell->GetColSpan(); + + if ( !nRowSpan ) + return; + + const SwStartNode* pSttNd = pBox->GetSttNd(); + bool bHead = false; + if( pSttNd ) + { + SwNodeOffset nNdPos = pSttNd->GetIndex()+1; + + // determine the type of cell (TD/TH) + SwNode* pNd; + while( !( pNd = rWrt.m_pDoc->GetNodes()[nNdPos])->IsEndNode() ) + { + if( pNd->IsTextNode() ) + { + // The only paragraphs relevant for the distinction are those + // where the style is one of the two table related styles + // or inherits from one of these. + const SwFormat *pFormat = &static_cast<SwTextNode*>(pNd)->GetAnyFormatColl(); + sal_uInt16 nPoolId = pFormat->GetPoolFormatId(); + while( !pFormat->IsDefault() && + RES_POOLCOLL_TABLE_HDLN!=nPoolId && + RES_POOLCOLL_TABLE!=nPoolId ) + { + pFormat = pFormat->DerivedFrom(); + nPoolId = pFormat->GetPoolFormatId(); + } + + if( !pFormat->IsDefault() ) + { + bHead = (RES_POOLCOLL_TABLE_HDLN==nPoolId); + break; + } + } + nNdPos++; + } + } + + rWrt.OutNewLine(); // <TH>/<TD> in new line + OStringBuffer sOut("<"); + OString aTag(bHead ? OOO_STRING_SVTOOLS_HTML_tableheader : OOO_STRING_SVTOOLS_HTML_tabledata); + sOut.append(rWrt.GetNamespace() + aTag); + + // output ROW- and COLSPAN + if (nRowSpan > 1 && bCellRowSpan) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_rowspan + "=\"" + OString::number(nRowSpan) + "\""); + } + if( nColSpan > 1 ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_colspan + "=\"" + OString::number(nColSpan) + "\""); + } + + tools::Long nWidth = 0; + bool bOutWidth = true; + sal_uInt32 nPercentWidth = SAL_MAX_UINT32; + + if( m_bLayoutExport ) + { + if( pCell->HasPercentWidthOpt() ) + { + nPercentWidth = pCell->GetWidthOpt(); + } + else + { + nWidth = pCell->GetWidthOpt(); + if( !nWidth ) + bOutWidth = false; + } + } + else + { + if( HasRelWidths() ) + nPercentWidth = GetPercentWidth(nCol, nColSpan); + else + nWidth = GetAbsWidth( nCol, nColSpan ); + } + + if (rWrt.mbReqIF) + // ReqIF implies strict XHTML: no width for <td>. + bOutWidth = false; + + tools::Long nHeight = pCell->GetHeight() > 0 + ? GetAbsHeight( pCell->GetHeight(), nRow, nRowSpan ) + : 0; + Size aPixelSz(SwHTMLWriter::ToPixel(nWidth), SwHTMLWriter::ToPixel(nHeight)); + + // output WIDTH: from layout or calculated + if( bOutWidth ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_width "=\""); + if( nPercentWidth != SAL_MAX_UINT32 ) + { + sOut.append(OString::number(static_cast<sal_Int32>(nPercentWidth)) + "%"); + } + else + { + sOut.append(static_cast<sal_Int32>(aPixelSz.Width())); + } + sOut.append("\""); + } + + if (rWrt.mbReqIF) + { + // ReqIF implies strict XHTML: no height for <td>. + nHeight = 0; + } + + if( nHeight ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_height + "=\"" + OString::number(aPixelSz.Height()) + "\""); + } + + const SfxItemSet& rItemSet = pBox->GetFrameFormat()->GetAttrSet(); + + // ALIGN is only outputted at the paragraphs from now on + + // output VALIGN + if( bOutVAlign ) + { + sal_Int16 eVertOri = pCell->GetVertOri(); + if( text::VertOrientation::TOP==eVertOri || text::VertOrientation::BOTTOM==eVertOri ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_valign + "=\"").append(text::VertOrientation::TOP==eVertOri ? + OOO_STRING_SVTOOLS_HTML_VA_top : + OOO_STRING_SVTOOLS_HTML_VA_bottom) + .append("\""); + } + } + + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + + rWrt.m_bTextAttr = false; + rWrt.m_bOutOpts = true; + const SvxBrushItem *pBrushItem = rItemSet.GetItemIfSet( RES_BACKGROUND, false ); + if( !pBrushItem ) + pBrushItem = pCell->GetBackground(); + + if( pBrushItem ) + { + // output background + if (!rWrt.mbReqIF) + // Avoid non-CSS version in the ReqIF case. + rWrt.OutBackground( pBrushItem, false ); + + if (!rWrt.m_bCfgOutStyles) + pBrushItem = nullptr; + } + + // tdf#132739 with rWrt.m_bCfgOutStyles of true bundle the brush item css + // properties into the same "style" tag as the borders so there is only one + // style tag + rWrt.OutCSS1_TableCellBordersAndBG(*pBox->GetFrameFormat(), pBrushItem); + + sal_uInt32 nNumFormat = 0; + double nValue = 0.0; + bool bNumFormat = false, bValue = false; + if( const SwTableBoxNumFormat* pItem = rItemSet.GetItemIfSet( RES_BOXATR_FORMAT, false ) ) + { + nNumFormat = pItem->GetValue(); + bNumFormat = true; + } + if( const SwTableBoxValue* pItem = rItemSet.GetItemIfSet( RES_BOXATR_VALUE, false ) ) + { + nValue = pItem->GetValue(); + bValue = true; + if( !bNumFormat ) + nNumFormat = pBox->GetFrameFormat()->GetTableBoxNumFormat().GetValue(); + } + + if ((bNumFormat || bValue) && !rWrt.mbXHTML) + { + sOut.append(HTMLOutFuncs::CreateTableDataOptionsValNum(bValue, nValue, + nNumFormat, *rWrt.m_pDoc->GetNumberFormatter())); + } + sOut.append('>'); + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + rWrt.SetLFPossible(true); + + rWrt.IncIndentLevel(); // indent the content of <TD>...</TD> + + if( pSttNd ) + { + HTMLSaveData aSaveData( rWrt, pSttNd->GetIndex()+1, + pSttNd->EndOfSectionIndex() ); + rWrt.Out_SwDoc( rWrt.m_pCurrentPam.get() ); + } + else + { + sal_uInt16 nTWidth; + sal_uInt32 nBWidth; + sal_uInt16 nLSub, nRSub; + if( HasRelWidths() ) + { + nTWidth = 100; + nBWidth = GetRawWidth( nCol, nColSpan ); + nLSub = 0; + nRSub = 0; + } + else + { + nTWidth = GetAbsWidth( nCol, nColSpan ); + nBWidth = nTWidth; + nLSub = GetLeftSpace( nCol ); + nRSub = GetRightSpace( nCol, nColSpan ); + } + + SwHTMLWrtTable aTableWrt( pBox->GetTabLines(), nTWidth, + nBWidth, HasRelWidths(), nLSub, nRSub, /*nNumOfRowsToRepeat*/0 ); + aTableWrt.Write( rWrt ); + } + + rWrt.DecIndentLevel(); // indent the content of <TD>...</TD> + + if (rWrt.IsLFPossible()) + rWrt.OutNewLine(); + aTag = bHead ? OOO_STRING_SVTOOLS_HTML_tableheader : OOO_STRING_SVTOOLS_HTML_tabledata; + HTMLOutFuncs::Out_AsciiTag(rWrt.Strm(), Concat2View(rWrt.GetNamespace() + aTag), false); + rWrt.SetLFPossible(true); +} + +// output a line as lines +void SwHTMLWrtTable::OutTableCells( SwHTMLWriter& rWrt, + const SwWriteTableCells& rCells, + const SvxBrushItem *pBrushItem, + sal_uInt16& rSkipRows ) const +{ + // If the line contains more the one cell and all cells have the same + // alignment, then output the VALIGN at the line instead of the cell. + sal_Int16 eRowVertOri = text::VertOrientation::NONE; + if( rCells.size() > 1 ) + { + for (SwWriteTableCells::size_type nCell = 0; nCell < rCells.size(); ++nCell) + { + sal_Int16 eCellVertOri = rCells[nCell]->GetVertOri(); + if( 0==nCell ) + { + eRowVertOri = eCellVertOri; + } + else if( eRowVertOri != eCellVertOri ) + { + eRowVertOri = text::VertOrientation::NONE; + break; + } + } + } + + rWrt.OutNewLine(); // <TR> in new line + rWrt.Strm().WriteChar( '<' ).WriteOString( Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_tablerow) ); + if( pBrushItem ) + { + if (!rWrt.mbXHTML) + { + rWrt.OutBackground(pBrushItem, false); + } + + rWrt.m_bTextAttr = false; + rWrt.m_bOutOpts = true; + if (rWrt.m_bCfgOutStyles || rWrt.mbXHTML) + OutCSS1_TableBGStyleOpt( rWrt, *pBrushItem ); + } + + if( text::VertOrientation::TOP==eRowVertOri || text::VertOrientation::BOTTOM==eRowVertOri ) + { + OStringBuffer sOut; + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_valign + "=\"").append(text::VertOrientation::TOP==eRowVertOri ? OOO_STRING_SVTOOLS_HTML_VA_top : OOO_STRING_SVTOOLS_HTML_VA_bottom) + .append("\""); + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + } + + rWrt.Strm().WriteChar( '>' ); + + rWrt.IncIndentLevel(); // indent content of <TR>...</TR> + + bool bCellRowSpan = true; + if (!rCells.empty() && rCells[0]->GetRowSpan() > 1) + { + // Skip the rowspan attrs of <td> elements if they are the same for every cell of this row. + bCellRowSpan = std::adjacent_find(rCells.begin(), rCells.end(), + [](const std::unique_ptr<SwWriteTableCell>& pA, + const std::unique_ptr<SwWriteTableCell>& pB) + { return pA->GetRowSpan() != pB->GetRowSpan(); }) + != rCells.end(); + if (!bCellRowSpan) + { + // If no rowspan is written, then skip rows which would only contain covered cells, but + // not the current row. + rSkipRows = rCells[0]->GetRowSpan() - 1; + } + } + + for (const auto &rpCell : rCells) + { + OutTableCell(rWrt, rpCell.get(), text::VertOrientation::NONE == eRowVertOri, bCellRowSpan); + } + + rWrt.DecIndentLevel(); // indent content of <TR>...</TR> + + rWrt.OutNewLine(); // </TR> in new line + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_tablerow), false ); +} + +void SwHTMLWrtTable::Write( SwHTMLWriter& rWrt, sal_Int16 eAlign, + bool bTHead, const SwFrameFormat *pFrameFormat, + const OUString *pCaption, bool bTopCaption, + sal_uInt16 nHSpace, sal_uInt16 nVSpace ) const +{ + // determine value of RULES + bool bRowsHaveBorder = false; + bool bRowsHaveBorderOnly = true; + assert(m_aRows.begin() != m_aRows.end()); + for (auto row = m_aRows.begin(), next = std::next(row); next < m_aRows.end(); ++row, ++next) + { + SwWriteTableRow* pRow = row->get(); + SwWriteTableRow* pNextRow = next->get(); + bool bBorder = ( pRow->HasBottomBorder() || pNextRow->HasTopBorder() ); + bRowsHaveBorder |= bBorder; + bRowsHaveBorderOnly &= bBorder; + + pRow->SetBottomBorder(bBorder); + pNextRow->SetTopBorder(bBorder); + } + + bool bColsHaveBorder = false; + bool bColsHaveBorderOnly = true; + assert(m_aCols.begin() != m_aCols.end()); + for (auto col = m_aCols.begin(), next = std::next(col); next < m_aCols.end(); ++col, ++next) + { + SwWriteTableCol* pCol = col->get(); + SwWriteTableCol* pNextCol = next->get(); + bool bBorder = ( pCol->m_bRightBorder || pNextCol->m_bLeftBorder ); + bColsHaveBorder |= bBorder; + bColsHaveBorderOnly &= bBorder; + pCol->m_bRightBorder = bBorder; + pNextCol->m_bLeftBorder = bBorder; + } + + // close previous numbering, etc + rWrt.ChangeParaToken( HtmlTokenId::NONE ); + + if (rWrt.IsLFPossible()) + rWrt.OutNewLine(); // <TABLE> in new line + OStringBuffer sOut("<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_table); + + const SvxFrameDirection nOldDirection = rWrt.m_nDirection; + if( pFrameFormat ) + rWrt.m_nDirection = rWrt.GetHTMLDirection( pFrameFormat->GetAttrSet() ); + if( rWrt.m_bOutFlyFrame || nOldDirection != rWrt.m_nDirection ) + { + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + rWrt.OutDirection( rWrt.m_nDirection ); + } + + // output ALIGN= + if( text::HoriOrientation::RIGHT == eAlign ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_align + "=\"" OOO_STRING_SVTOOLS_HTML_AL_right "\""); + } + else if( text::HoriOrientation::CENTER == eAlign ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_align + "=\"" OOO_STRING_SVTOOLS_HTML_AL_center "\""); + } + else if( text::HoriOrientation::LEFT == eAlign ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_align + "=\"" OOO_STRING_SVTOOLS_HTML_AL_left "\""); + } + + // output WIDTH: from layout or calculated + if( m_nTabWidth ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_width "=\""); + if( HasRelWidths() ) + sOut.append(OString::number(static_cast<sal_Int32>(m_nTabWidth)) + "%"); + else + { + sal_Int32 nPixWidth = SwHTMLWriter::ToPixel(m_nTabWidth); + sOut.append(nPixWidth); + } + sOut.append("\""); + } + + if( (nHSpace || nVSpace) && !rWrt.mbReqIF) + { + if (auto nPixHSpace = SwHTMLWriter::ToPixel(nHSpace)) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_hspace + "=\"" + OString::number(nPixHSpace) + "\""); + } + + if (auto nPixVSpace = SwHTMLWriter::ToPixel(nVSpace)) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_vspace + "=\"" + OString::number(nPixVSpace) + "\""); + } + } + + // output CELLPADDING: from layout or calculated + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_cellpadding + "=\"" + OString::number(SwHTMLWriter::ToPixel(m_nCellPadding)) + "\""); + + // output CELLSPACING: from layout or calculated + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_cellspacing + "=\"" + OString::number(SwHTMLWriter::ToPixel(m_nCellSpacing)) + "\""); + + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + + // output background + if( pFrameFormat ) + { + if (!rWrt.mbXHTML) + { + rWrt.OutBackground(pFrameFormat->GetAttrSet(), false); + } + + if (rWrt.m_bCfgOutStyles || rWrt.mbXHTML) + { + rWrt.OutCSS1_TableFrameFormatOptions( *pFrameFormat ); + } + } + + sOut.append('>'); + rWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + + rWrt.IncIndentLevel(); // indent content of table + + // output caption + if( pCaption && !pCaption->isEmpty() ) + { + rWrt.OutNewLine(); // <CAPTION> in new line + OStringBuffer sOutStr(OOO_STRING_SVTOOLS_HTML_caption); + sOutStr.append(" " OOO_STRING_SVTOOLS_HTML_O_align "=\"") + .append(bTopCaption ? OOO_STRING_SVTOOLS_HTML_VA_top : OOO_STRING_SVTOOLS_HTML_VA_bottom) + .append("\""); + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + sOutStr) ); + HTMLOutFuncs::Out_String( rWrt.Strm(), *pCaption ); + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_caption), false ); + } + + // output <COLGRP>/<COL>: If exporting via layout only when during import + // some were there, otherwise always. + bool bColGroups = (bColsHaveBorder && !bColsHaveBorderOnly); + if( m_bColTags ) + { + if( bColGroups ) + { + rWrt.OutNewLine(); // <COLGRP> in new line + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_colgroup) ); + + rWrt.IncIndentLevel(); // indent content of <COLGRP> + } + + const SwWriteTableCols::size_type nCols = m_aCols.size(); + for( SwWriteTableCols::size_type nCol=0; nCol<nCols; ++nCol ) + { + rWrt.OutNewLine(); // </COL> in new line + + const SwWriteTableCol *pColumn = m_aCols[nCol].get(); + + HtmlWriter html(rWrt.Strm(), rWrt.maNamespace); + html.prettyPrint(false); // We add newlines ourself + html.start(OOO_STRING_SVTOOLS_HTML_col ""_ostr); + + sal_uInt32 nWidth; + bool bRel; + if( m_bLayoutExport ) + { + bRel = pColumn->HasRelWidthOpt(); + nWidth = pColumn->GetWidthOpt(); + } + else + { + bRel = HasRelWidths(); + nWidth = bRel ? GetRelWidth(nCol,1) : GetAbsWidth(nCol,1); + } + + if( bRel ) + html.attribute(OOO_STRING_SVTOOLS_HTML_O_width, Concat2View(OString::number(nWidth) + "*")); + else + html.attribute(OOO_STRING_SVTOOLS_HTML_O_width, OString::number(SwHTMLWriter::ToPixel(nWidth))); + html.end(); + + if( bColGroups && pColumn->m_bRightBorder && nCol<nCols-1 ) + { + rWrt.DecIndentLevel(); // indent content of <COLGRP> + rWrt.OutNewLine(); // </COLGRP> in new line + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_colgroup), + false ); + rWrt.OutNewLine(); // <COLGRP> in new line + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_colgroup) ); + rWrt.IncIndentLevel(); // indent content of <COLGRP> + } + } + if( bColGroups ) + { + rWrt.DecIndentLevel(); // indent content of <COLGRP> + + rWrt.OutNewLine(); // </COLGRP> in new line + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_colgroup), + false ); + } + } + + // output the lines as table lines + + // Output <TBODY>? + bool bTSections = (bRowsHaveBorder && !bRowsHaveBorderOnly); + bool bTBody = bTSections; + + // If sections must be outputted, then a THEAD around the first line only + // can be outputted if there is a line below the cell. + if( bTHead && + (bTSections || bColGroups) && + m_nHeadEndRow<m_aRows.size()-1 && !m_aRows[m_nHeadEndRow]->HasBottomBorder() ) + bTHead = false; + + // Output <TBODY> only if <THEAD> is outputted. + bTSections |= bTHead; + + if( bTSections ) + { + rWrt.OutNewLine(); // <THEAD>/<TDATA> in new line + OString aTag = bTHead ? OOO_STRING_SVTOOLS_HTML_thead : OOO_STRING_SVTOOLS_HTML_tbody; + HTMLOutFuncs::Out_AsciiTag(rWrt.Strm(), Concat2View(rWrt.GetNamespace() + aTag)); + + rWrt.IncIndentLevel(); // indent content of <THEAD>/<TDATA> + } + + sal_uInt16 nSkipRows = 0; + for( SwWriteTableRows::size_type nRow = 0; nRow < m_aRows.size(); ++nRow ) + { + const SwWriteTableRow *pRow = m_aRows[nRow].get(); + + if (nSkipRows == 0) + { + OutTableCells(rWrt, pRow->GetCells(), pRow->GetBackground(), nSkipRows); + } + else + { + --nSkipRows; + } + if( ( (bTHead && nRow==m_nHeadEndRow) || + (bTBody && pRow->HasBottomBorder()) ) && + nRow < m_aRows.size()-1 ) + { + rWrt.DecIndentLevel(); // indent content of <THEAD>/<TDATA> + rWrt.OutNewLine(); // </THEAD>/</TDATA> in new line + OString aTag = bTHead ? OOO_STRING_SVTOOLS_HTML_thead : OOO_STRING_SVTOOLS_HTML_tbody; + HTMLOutFuncs::Out_AsciiTag(rWrt.Strm(), Concat2View(rWrt.GetNamespace() + aTag), false); + rWrt.OutNewLine(); // <THEAD>/<TDATA> in new line + + if( bTHead && nRow==m_nHeadEndRow ) + bTHead = false; + + aTag = bTHead ? OOO_STRING_SVTOOLS_HTML_thead : OOO_STRING_SVTOOLS_HTML_tbody; + HTMLOutFuncs::Out_AsciiTag(rWrt.Strm(), Concat2View(rWrt.GetNamespace() + aTag)); + rWrt.IncIndentLevel(); // indent content of <THEAD>/<TDATA> + } + } + + if( bTSections ) + { + rWrt.DecIndentLevel(); // indent content of <THEAD>/<TDATA> + + rWrt.OutNewLine(); // </THEAD>/</TDATA> in new line + OString aTag = bTHead ? OOO_STRING_SVTOOLS_HTML_thead : OOO_STRING_SVTOOLS_HTML_tbody; + HTMLOutFuncs::Out_AsciiTag(rWrt.Strm(), Concat2View(rWrt.GetNamespace() + aTag), false); + } + + rWrt.DecIndentLevel(); // indent content of <TABLE> + + rWrt.OutNewLine(); // </TABLE> in new line + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_table), false ); + + rWrt.m_nDirection = nOldDirection; +} + +SwHTMLWriter& OutHTML_SwTableNode( SwHTMLWriter& rWrt, SwTableNode & rNode, + const SwFrameFormat *pFlyFrameFormat, + const OUString *pCaption, bool bTopCaption ) +{ + + SwTable& rTable = rNode.GetTable(); + + rWrt.m_bOutTable = true; + + // The horizontal alignment of the frame (if exists) has priority. + // NONE means that no horizontal alignment was outputted. + sal_Int16 eFlyHoriOri = text::HoriOrientation::NONE; + css::text::WrapTextMode eSurround = css::text::WrapTextMode_NONE; + sal_uInt8 nFlyPercentWidth = 0; + tools::Long nFlyWidth = 0; + sal_uInt16 nFlyHSpace = 0; + sal_uInt16 nFlyVSpace = 0; + if( pFlyFrameFormat ) + { + eSurround = pFlyFrameFormat->GetSurround().GetSurround(); + const SwFormatFrameSize& rFrameSize = pFlyFrameFormat->GetFrameSize(); + nFlyPercentWidth = rFrameSize.GetWidthPercent(); + nFlyWidth = rFrameSize.GetSize().Width(); + + eFlyHoriOri = pFlyFrameFormat->GetHoriOrient().GetHoriOrient(); + if( text::HoriOrientation::NONE == eFlyHoriOri ) + eFlyHoriOri = text::HoriOrientation::LEFT; + + const SvxLRSpaceItem& rLRSpace = pFlyFrameFormat->GetLRSpace(); + nFlyHSpace = static_cast< sal_uInt16 >((rLRSpace.GetLeft() + rLRSpace.GetRight()) / 2); + + const SvxULSpaceItem& rULSpace = pFlyFrameFormat->GetULSpace(); + nFlyVSpace = (rULSpace.GetUpper() + rULSpace.GetLower()) / 2; + } + + // maybe open a FORM + bool bPreserveForm = false; + if( !rWrt.m_bPreserveForm ) + { + rWrt.OutForm( true, &rNode ); + bPreserveForm = rWrt.mxFormComps.is(); + rWrt.m_bPreserveForm = bPreserveForm; + } + + SwFrameFormat *pFormat = rTable.GetFrameFormat(); + + const SwFormatFrameSize& rFrameSize = pFormat->GetFrameSize(); + tools::Long nWidth = rFrameSize.GetSize().Width(); + sal_uInt8 nPercentWidth = rFrameSize.GetWidthPercent(); + sal_uInt16 nBaseWidth = o3tl::narrowing<sal_uInt16>(nWidth); + + sal_Int16 eTabHoriOri = pFormat->GetHoriOrient().GetHoriOrient(); + + // text::HoriOrientation::NONE and text::HoriOrientation::FULL tables need relative widths + sal_uInt16 nNewDefListLvl = 0; + bool bRelWidths = false; + bool bCheckDefList = false; + switch( eTabHoriOri ) + { + case text::HoriOrientation::FULL: + // Tables with automatic alignment become tables with 100% width. + bRelWidths = true; + nWidth = 100; + eTabHoriOri = text::HoriOrientation::LEFT; + break; + case text::HoriOrientation::NONE: + { + const SvxLRSpaceItem& aLRItem = pFormat->GetLRSpace(); + if( aLRItem.GetRight() ) + { + // The table width is defined on the basis of the left and + // right margin. Therefore we try to define the actual + // width of the table. If that's not possible we transform + // it to a table with width 100%. + nWidth = pFormat->FindLayoutRect(true).Width(); + if( !nWidth ) + { + bRelWidths = true; + nWidth = 100; + } + + } + else if( nPercentWidth ) + { + // Without a right border the %-width is maintained. + nWidth = nPercentWidth; + bRelWidths = true; + } + else + { + // Without a right margin also an absolute width is maintained. + // We still try to define the actual width via the layout. + tools::Long nRealWidth = pFormat->FindLayoutRect(true).Width(); + if( nRealWidth ) + nWidth = nRealWidth; + } + bCheckDefList = true; + } + break; + case text::HoriOrientation::LEFT_AND_WIDTH: + eTabHoriOri = text::HoriOrientation::LEFT; + bCheckDefList = true; + [[fallthrough]]; + default: + // In all other case it's possible to use directly an absolute + // or relative width. + if( nPercentWidth ) + { + bRelWidths = true; + nWidth = nPercentWidth; + } + break; + } + + // In ReqIF case, do not emulate indentation with fake description list + if( bCheckDefList && !rWrt.mbReqIF ) + { + OSL_ENSURE( !rWrt.GetNumInfo().GetNumRule() || + rWrt.GetNextNumInfo(), + "NumInfo for next paragraph is missing!" ); + const SvxLRSpaceItem& aLRItem = pFormat->GetLRSpace(); + if( aLRItem.GetLeft() > 0 && rWrt.m_nDefListMargin > 0 && + ( !rWrt.GetNumInfo().GetNumRule() || + ( rWrt.GetNextNumInfo() && + (rWrt.GetNumInfo().GetNumRule() != rWrt.GetNextNumInfo()->GetNumRule() || + rWrt.GetNextNumInfo()->IsRestart(rWrt.GetNumInfo())) ) ) ) + { + // If the paragraph before the table is not numbered or the + // paragraph after the table starts with a new numbering or with + // a different rule, we can maintain the indentation with a DL. + // Otherwise we keep the indentation of the numbering. + nNewDefListLvl = static_cast< sal_uInt16 >( + (aLRItem.GetLeft() + (rWrt.m_nDefListMargin/2)) / + rWrt.m_nDefListMargin ); + } + } + + if( !pFlyFrameFormat && !rWrt.mbReqIF && nNewDefListLvl != rWrt.m_nDefListLvl ) + rWrt.OutAndSetDefList( nNewDefListLvl ); + + // eFlyHoriOri and eTabHoriOri now only contain the values of + // LEFT/CENTER and RIGHT! + if( eFlyHoriOri!=text::HoriOrientation::NONE ) + { + eTabHoriOri = eFlyHoriOri; + // MIB 4.7.97: If the table has a relative width, then the width is + // adjusted to the width of the frame, therefore we export its width. + // If fixed width, the table width is relevant. Whoever puts tables with + // relative width <100% into frames is to blame when the result looks bad. + if( bRelWidths ) + { + nWidth = nFlyPercentWidth ? nFlyPercentWidth : nFlyWidth; + bRelWidths = nFlyPercentWidth > 0; + } + } + + sal_Int16 eDivHoriOri = text::HoriOrientation::NONE; + switch( eTabHoriOri ) + { + case text::HoriOrientation::LEFT: + // If a left-aligned table has no right sided flow, then we don't need + // an ALIGN=LEFT in the table. + if( eSurround==css::text::WrapTextMode_NONE || eSurround==css::text::WrapTextMode_LEFT ) + eTabHoriOri = text::HoriOrientation::NONE; + break; + case text::HoriOrientation::RIGHT: + // Something like that also applies to right-aligned tables, + // here we use a <DIV ALIGN=RIGHT> instead. + if( eSurround==css::text::WrapTextMode_NONE || eSurround==css::text::WrapTextMode_RIGHT ) + { + eDivHoriOri = text::HoriOrientation::RIGHT; + eTabHoriOri = text::HoriOrientation::NONE; + } + break; + case text::HoriOrientation::CENTER: + // Almost nobody understands ALIGN=CENTER, therefore we abstain + // from it and use a <CENTER>. + eDivHoriOri = text::HoriOrientation::CENTER; + eTabHoriOri = text::HoriOrientation::NONE; + break; + default: + ; + } + if( text::HoriOrientation::NONE==eTabHoriOri ) + nFlyHSpace = nFlyVSpace = 0; + + if( !pFormat->GetName().isEmpty() ) + rWrt.OutImplicitMark( pFormat->GetName(), "table" ); + + if( text::HoriOrientation::NONE!=eDivHoriOri ) + { + if (rWrt.IsLFPossible()) + rWrt.OutNewLine(); // <CENTER> in new line + if( text::HoriOrientation::CENTER==eDivHoriOri ) + { + if (!rWrt.mbXHTML) + { + // Not XHTML's css center: start <center>. + HTMLOutFuncs::Out_AsciiTag( rWrt.Strm(), Concat2View(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_center) ); + } + } + else + { + if (rWrt.mbReqIF) + { + // In ReqIF, div cannot have an 'align' attribute. For now, use 'style' only + // for ReqIF; maybe it makes sense to use it in both cases? + static constexpr char sOut[] = OOO_STRING_SVTOOLS_HTML_division + " style=\"display: flex; flex-direction: column; align-items: flex-end\""; + HTMLOutFuncs::Out_AsciiTag(rWrt.Strm(), Concat2View(rWrt.GetNamespace() + sOut)); + } + else + { + static constexpr char sOut[] = OOO_STRING_SVTOOLS_HTML_division + " " OOO_STRING_SVTOOLS_HTML_O_align "=\"" OOO_STRING_SVTOOLS_HTML_AL_right "\""; + HTMLOutFuncs::Out_AsciiTag(rWrt.Strm(), Concat2View(rWrt.GetNamespace() + sOut)); + } + } + rWrt.IncIndentLevel(); // indent content of <CENTER> + rWrt.SetLFPossible(true); + } + + // If the table isn't in a frame, then you always can output a LF. + if( text::HoriOrientation::NONE==eTabHoriOri ) + rWrt.SetLFPossible(true); + + const SwHTMLTableLayout *pLayout = rTable.GetHTMLTableLayout(); + +#ifdef DBG_UTIL + { + SwViewShell *pSh = rWrt.m_pDoc->getIDocumentLayoutAccess().GetCurrentViewShell(); + if ( pSh && pSh->GetViewOptions()->IsTest1() ) + pLayout = nullptr; + } +#endif + + if( pLayout && pLayout->IsExportable() ) + { + SwHTMLWrtTable aTableWrt( pLayout ); + aTableWrt.Write( rWrt, eTabHoriOri, rTable.GetRowsToRepeat() > 0, + pFormat, pCaption, bTopCaption, + nFlyHSpace, nFlyVSpace ); + } + else + { + SwHTMLWrtTable aTableWrt( rTable.GetTabLines(), nWidth, + nBaseWidth, bRelWidths, 0, 0, rTable.GetRowsToRepeat() ); + aTableWrt.Write( rWrt, eTabHoriOri, rTable.GetRowsToRepeat() > 0, + pFormat, pCaption, bTopCaption, + nFlyHSpace, nFlyVSpace ); + } + + // If the table wasn't in a frame, then you always can output a LF. + if( text::HoriOrientation::NONE==eTabHoriOri ) + rWrt.SetLFPossible(true); + + if( text::HoriOrientation::NONE!=eDivHoriOri ) + { + rWrt.DecIndentLevel(); // indent content of <CENTER> + rWrt.OutNewLine(); // </CENTER> in new line + OString aTag = text::HoriOrientation::CENTER == eDivHoriOri + ? OOO_STRING_SVTOOLS_HTML_center + : OOO_STRING_SVTOOLS_HTML_division; + if (!rWrt.mbXHTML || eDivHoriOri != text::HoriOrientation::CENTER) + { + // Not XHTML's css center: end <center>. + HTMLOutFuncs::Out_AsciiTag(rWrt.Strm(), Concat2View(rWrt.GetNamespace() + aTag), false); + } + rWrt.SetLFPossible(true); + } + + // move Pam behind the table + rWrt.m_pCurrentPam->GetPoint()->Assign( *rNode.EndOfSectionNode() ); + + if( bPreserveForm ) + { + rWrt.m_bPreserveForm = false; + rWrt.OutForm( false ); + } + + rWrt.m_bOutTable = false; + + if( rWrt.GetNextNumInfo() && + rWrt.GetNextNumInfo()->GetNumRule() == rWrt.GetNumInfo().GetNumRule() && + !rWrt.GetNextNumInfo()->IsRestart(rWrt.GetNumInfo()) ) + { + // If the paragraph after the table is numbered with the same rule as the + // one before, then the NumInfo of the next paragraph holds the level of + // paragraph before the table. Therefore NumInfo must be fetched again + // to maybe close the Num list. + rWrt.ClearNextNumInfo(); + rWrt.FillNextNumInfo(); + OutHTML_NumberBulletListEnd( rWrt, *rWrt.GetNextNumInfo() ); + } + return rWrt; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/parcss1.cxx b/sw/source/filter/html/parcss1.cxx new file mode 100644 index 0000000000..f3145f1fa5 --- /dev/null +++ b/sw/source/filter/html/parcss1.cxx @@ -0,0 +1,1388 @@ +/* -*- 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 <o3tl/string_view.hxx> +#include <osl/diagnose.h> +#include <rtl/character.hxx> +#include <rtl/ustrbuf.hxx> +#include <tools/color.hxx> +#include <tools/solar.h> +#include <svtools/htmltokn.h> +#include <comphelper/string.hxx> +#include "parcss1.hxx" + +// Loop-Check: Used to avoid infinite loops, is checked after every +// loop, if there is progress of the input position +#define LOOP_CHECK + +#ifdef LOOP_CHECK + +#define LOOP_CHECK_DECL \ + sal_Int32 nOldInPos = SAL_MAX_INT32; +#define LOOP_CHECK_RESTART \ + nOldInPos = SAL_MAX_INT32; +#define LOOP_CHECK_CHECK( where ) \ + OSL_ENSURE( nOldInPos!=m_nInPos || m_cNextCh==sal_Unicode(EOF), where ); \ + if( nOldInPos==m_nInPos && m_cNextCh!=sal_Unicode(EOF) ) \ + break; \ + else \ + nOldInPos = m_nInPos; + +#else + +#define LOOP_CHECK_DECL +#define LOOP_CHECK_RESTART +#define LOOP_CHECK_CHECK( where ) + +#endif + +const sal_Int32 MAX_LEN = 1024; + +void CSS1Parser::InitRead( const OUString& rIn ) +{ + m_nlLineNr = 0; + m_nlLinePos = 0; + + m_bWhiteSpace = true; // if nothing was read it's like there was WS + m_bEOF = false; + m_eState = CSS1_PAR_WORKING; + m_nValue = 0.; + + m_aIn = rIn; + m_nInPos = 0; + m_cNextCh = GetNextChar(); + m_nToken = GetNextToken(); +} + +sal_Unicode CSS1Parser::GetNextChar() +{ + if( m_nInPos >= m_aIn.getLength() ) + { + m_bEOF = true; + return sal_Unicode(EOF); + } + + sal_Unicode c = m_aIn[m_nInPos]; + m_nInPos++; + + if( c == '\n' ) + { + ++m_nlLineNr; + m_nlLinePos = 1; + } + else + ++m_nlLinePos; + + return c; +} + +// This function implements the scanner described in + +// http://www.w3.org/pub/WWW/TR/WD-css1.html +// resp. http://www.w3.org/pub/WWW/TR/WD-css1-960220.html + +// for CSS1. It's a direct implementation of the +// described Lex grammar. + +CSS1Token CSS1Parser::GetNextToken() +{ + CSS1Token nRet = CSS1_NULL; + m_aToken.clear(); + + do { + // remember if white space was read + bool bPrevWhiteSpace = m_bWhiteSpace; + m_bWhiteSpace = false; + + bool bNextCh = true; + switch( m_cNextCh ) + { + case '/': // COMMENT | '/' + { + m_cNextCh = GetNextChar(); + if( '*' == m_cNextCh ) + { + // COMMENT + m_cNextCh = GetNextChar(); + + bool bAsterisk = false; + while( !(bAsterisk && '/'==m_cNextCh) && !IsEOF() ) + { + bAsterisk = ('*'==m_cNextCh); + m_cNextCh = GetNextChar(); + } + } + else + { + // '/' + bNextCh = false; + nRet = CSS1_SLASH; + } + } + break; + + case '@': // '@import' | '@XXX' + { + m_cNextCh = GetNextChar(); + if (rtl::isAsciiAlpha(m_cNextCh)) + { + // scan the next identifier + OUStringBuffer sTmpBuffer(32); + do { + sTmpBuffer.append( m_cNextCh ); + m_cNextCh = GetNextChar(); + } while( (rtl::isAsciiAlphanumeric(m_cNextCh) || + '-' == m_cNextCh) && !IsEOF() ); + + m_aToken += sTmpBuffer; + + // check if we know it + switch( m_aToken[0] ) + { + case 'i': + case 'I': + if( m_aToken.equalsIgnoreAsciiCase( "import" ) ) + nRet = CSS1_IMPORT_SYM; + break; + case 'p': + case 'P': + if( m_aToken.equalsIgnoreAsciiCase( "page" ) ) + nRet = CSS1_PAGE_SYM; + break; + } + + // error handling: ignore '@indent' and the rest until + // semicolon at end of the next block + if( CSS1_NULL==nRet ) + { + m_aToken.clear(); + int nBlockLvl = 0; + sal_Unicode cQuoteCh = 0; + bool bDone = false, bEscape = false; + while( !bDone && !IsEOF() ) + { + bool bOldEscape = bEscape; + bEscape = false; + switch( m_cNextCh ) + { + case '{': + if( !cQuoteCh && !bOldEscape ) + nBlockLvl++; + break; + case ';': + if( !cQuoteCh && !bOldEscape ) + bDone = nBlockLvl==0; + break; + case '}': + if( !cQuoteCh && !bOldEscape ) + bDone = --nBlockLvl==0; + break; + case '\"': + case '\'': + if( !bOldEscape ) + { + if( cQuoteCh ) + { + if( cQuoteCh == m_cNextCh ) + cQuoteCh = 0; + } + else + { + cQuoteCh = m_cNextCh; + } + } + break; + case '\\': + if( !bOldEscape ) + bEscape = true; + break; + } + m_cNextCh = GetNextChar(); + } + } + + bNextCh = false; + } + } + break; + + case '!': // '!' 'legal' | '!' 'important' | syntax error + { + // ignore white space + m_cNextCh = GetNextChar(); + while( ( ' ' == m_cNextCh || + (m_cNextCh >= 0x09 && m_cNextCh <= 0x0d) ) && !IsEOF() ) + { + m_bWhiteSpace = true; + m_cNextCh = GetNextChar(); + } + + if( 'i'==m_cNextCh || 'I'==m_cNextCh) + { + // scan next identifier + OUStringBuffer sTmpBuffer(32); + do { + sTmpBuffer.append( m_cNextCh ); + m_cNextCh = GetNextChar(); + } while( (rtl::isAsciiAlphanumeric(m_cNextCh) || + '-' == m_cNextCh) && !IsEOF() ); + + m_aToken += sTmpBuffer; + + if( ( 'i'==m_aToken[0] || 'I'==m_aToken[0] ) && + m_aToken.equalsIgnoreAsciiCase( "important" ) ) + { + // '!' 'important' + nRet = CSS1_IMPORTANT_SYM; + } + else + { + // error handling: ignore '!', not IDENT + nRet = CSS1_IDENT; + } + + m_bWhiteSpace = false; + bNextCh = false; + } + else + { + // error handling: ignore '!' + bNextCh = false; + } + } + break; + + case '\"': + case '\'': // STRING + { + // \... isn't possible yet!!! + sal_Unicode cQuoteChar = m_cNextCh; + m_cNextCh = GetNextChar(); + + OUStringBuffer sTmpBuffer( MAX_LEN ); + do { + sTmpBuffer.append( m_cNextCh ); + m_cNextCh = GetNextChar(); + } while( cQuoteChar != m_cNextCh && !IsEOF() ); + + m_aToken += sTmpBuffer; + + nRet = CSS1_STRING; + } + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': // NUMBER | PERCENTAGE | LENGTH + { + // save current position + std::size_t nInPosSave = m_nInPos; + sal_Unicode cNextChSave = m_cNextCh; + sal_uInt32 nlLineNrSave = m_nlLineNr; + sal_uInt32 nlLinePosSave = m_nlLinePos; + bool bEOFSave = m_bEOF; + + // first try to parse a hex digit + OUStringBuffer sTmpBuffer( 16 ); + do { + sTmpBuffer.append( m_cNextCh ); + m_cNextCh = GetNextChar(); + } while( sTmpBuffer.getLength() < 7 && + ( ('0'<=m_cNextCh && '9'>=m_cNextCh) || + ('A'<=m_cNextCh && 'F'>=m_cNextCh) || + ('a'<=m_cNextCh && 'f'>=m_cNextCh) ) && + !IsEOF() ); + + if( sTmpBuffer.getLength()==6 ) + { + // we found a color in hex + m_aToken += sTmpBuffer; + nRet = CSS1_HEXCOLOR; + bNextCh = false; + + break; + } + + // otherwise we try a number + m_nInPos = nInPosSave; + m_cNextCh = cNextChSave; + m_nlLineNr = nlLineNrSave; + m_nlLinePos = nlLinePosSave; + m_bEOF = bEOFSave; + + // first parse the number + sTmpBuffer.setLength( 0 ); + do { + sTmpBuffer.append( m_cNextCh ); + m_cNextCh = GetNextChar(); + } while( (('0'<=m_cNextCh && '9'>=m_cNextCh) || '.'==m_cNextCh) && + !IsEOF() ); + + m_aToken += sTmpBuffer; + m_nValue = m_aToken.toDouble(); + + // ignore white space + while( ( ' ' == m_cNextCh || + (m_cNextCh >= 0x09 && m_cNextCh <= 0x0d) ) && !IsEOF() ) + { + m_bWhiteSpace = true; + m_cNextCh = GetNextChar(); + } + + // check now, of there is a unit + switch( m_cNextCh ) + { + case '%': // PERCENTAGE + m_bWhiteSpace = false; + nRet = CSS1_PERCENTAGE; + break; + + case 'c': + case 'C': // LENGTH cm | LENGTH IDENT + case 'e': + case 'E': // LENGTH (em | ex) | LENGTH IDENT + case 'i': + case 'I': // LENGTH inch | LENGTH IDENT + case 'p': + case 'P': // LENGTH (pt | px | pc) | LENGTH IDENT + case 'm': + case 'M': // LENGTH mm | LENGTH IDENT + { + // save current position + sal_Int32 nInPosOld = m_nInPos; + sal_Unicode cNextChOld = m_cNextCh; + sal_uInt32 nlLineNrOld = m_nlLineNr; + sal_uInt32 nlLinePosOld = m_nlLinePos; + bool bEOFOld = m_bEOF; + + // parse the next identifier + OUString aIdent; + OUStringBuffer sTmpBuffer2(64); + do { + sTmpBuffer2.append( m_cNextCh ); + m_cNextCh = GetNextChar(); + } while( (rtl::isAsciiAlphanumeric(m_cNextCh) || + '-' == m_cNextCh) && !IsEOF() ); + + aIdent += sTmpBuffer2; + + // Is it a unit? + const char *pCmp1 = nullptr, *pCmp2 = nullptr, *pCmp3 = nullptr; + double nScale1 = 1., nScale2 = 1.; + CSS1Token nToken1 = CSS1_LENGTH, + nToken2 = CSS1_LENGTH, + nToken3 = CSS1_LENGTH; + switch( aIdent[0] ) + { + case 'c': + case 'C': + pCmp1 = "cm"; + nScale1 = (72.*20.)/2.54; // twip + break; + case 'e': + case 'E': + pCmp1 = "em"; + nToken1 = CSS1_EMS; + + pCmp2 = "ex"; + nToken2 = CSS1_EMX; + break; + case 'i': + case 'I': + pCmp1 = "in"; + nScale1 = 72.*20.; // twip + break; + case 'm': + case 'M': + pCmp1 = "mm"; + nScale1 = (72.*20.)/25.4; // twip + break; + case 'p': + case 'P': + pCmp1 = "pt"; + nScale1 = 20.; // twip + + pCmp2 = "pc"; + nScale2 = 12.*20.; // twip + + pCmp3 = "px"; + nToken3 = CSS1_PIXLENGTH; + break; + } + + double nScale = 0.0; + OSL_ENSURE( pCmp1, "Where does the first digit come from?" ); + if( aIdent.equalsIgnoreAsciiCaseAscii( pCmp1 ) ) + { + nScale = nScale1; + nRet = nToken1; + } + else if( pCmp2 && + aIdent.equalsIgnoreAsciiCaseAscii( pCmp2 ) ) + { + nScale = nScale2; + nRet = nToken2; + } + else if( pCmp3 && + aIdent.equalsIgnoreAsciiCaseAscii( pCmp3 ) ) + { + nScale = 1.; // nScale3 + nRet = nToken3; + } + else + { + nRet = CSS1_NUMBER; + } + + if( CSS1_LENGTH==nRet && nScale!=1.0 ) + m_nValue *= nScale; + + if( nRet == CSS1_NUMBER ) + { + m_nInPos = nInPosOld; + m_cNextCh = cNextChOld; + m_nlLineNr = nlLineNrOld; + m_nlLinePos = nlLinePosOld; + m_bEOF = bEOFOld; + } + else + { + m_bWhiteSpace = false; + } + bNextCh = false; + } + break; + default: // NUMBER IDENT + bNextCh = false; + nRet = CSS1_NUMBER; + break; + } + } + break; + + case ':': // ':' + // catch link/visited/active !!! + nRet = CSS1_COLON; + break; + + case '.': // DOT_W_WS | DOT_WO_WS + nRet = bPrevWhiteSpace ? CSS1_DOT_W_WS : CSS1_DOT_WO_WS; + break; + + case '+': // '+' + nRet = CSS1_PLUS; + break; + + case '-': // '-' + nRet = CSS1_MINUS; + break; + + case '{': // '{' + nRet = CSS1_OBRACE; + break; + + case '}': // '}' + nRet = CSS1_CBRACE; + break; + + case ';': // ';' + nRet = CSS1_SEMICOLON; + break; + + case ',': // ',' + nRet = CSS1_COMMA; + break; + + case '#': // '#' + m_cNextCh = GetNextChar(); + if( ('0'<=m_cNextCh && '9'>=m_cNextCh) || + ('a'<=m_cNextCh && 'f'>=m_cNextCh) || + ('A'<=m_cNextCh && 'F'>=m_cNextCh) ) + { + // save current position + sal_Int32 nInPosSave = m_nInPos; + sal_Unicode cNextChSave = m_cNextCh; + sal_uInt32 nlLineNrSave = m_nlLineNr; + sal_uInt32 nlLinePosSave = m_nlLinePos; + bool bEOFSave = m_bEOF; + + // first try to parse a hex digit + OUStringBuffer sTmpBuffer(8); + do { + sTmpBuffer.append( m_cNextCh ); + m_cNextCh = GetNextChar(); + } while( sTmpBuffer.getLength() < 9 && + ( ('0'<=m_cNextCh && '9'>=m_cNextCh) || + ('A'<=m_cNextCh && 'F'>=m_cNextCh) || + ('a'<=m_cNextCh && 'f'>=m_cNextCh) ) && + !IsEOF() ); + + if( sTmpBuffer.getLength()==6 || sTmpBuffer.getLength()==3 ) + { + // we found a color in hex (RGB) + m_aToken += sTmpBuffer; + nRet = CSS1_HEXCOLOR; + bNextCh = false; + + break; + } + + if( sTmpBuffer.getLength()==8 ) + { + // we found a color in hex (RGBA) + // we convert it to RGB assuming white background + sal_uInt32 nColor = sTmpBuffer.makeStringAndClear().toUInt32(16); + sal_uInt32 nRed = (nColor & 0xff000000) >> 24; + sal_uInt32 nGreen = (nColor & 0xff0000) >> 16; + sal_uInt32 nBlue = (nColor & 0xff00) >> 8; + double nAlpha = (nColor & 0xff) / 255.0; + nRed = (1 - nAlpha) * 255 + nAlpha * nRed; + nGreen = (1 - nAlpha) * 255 + nAlpha * nGreen; + nBlue = (1 - nAlpha) * 255 + nAlpha * nBlue; + nColor = (nRed << 16) + (nGreen << 8) + nBlue; + m_aToken += OUString::number(nColor, 16); + nRet = CSS1_HEXCOLOR; + bNextCh = false; + + break; + } + + // otherwise we try a number + m_nInPos = nInPosSave; + m_cNextCh = cNextChSave; + m_nlLineNr = nlLineNrSave; + m_nlLinePos = nlLinePosSave; + m_bEOF = bEOFSave; + } + + nRet = CSS1_HASH; + bNextCh = false; + break; + + case ' ': + case '\t': + case '\r': + case '\n': // White-Space + m_bWhiteSpace = true; + break; + + case sal_Unicode(EOF): + if( IsEOF() ) + { + m_eState = CSS1_PAR_ACCEPTED; + bNextCh = false; + break; + } + [[fallthrough]]; + + default: // IDENT | syntax error + if (rtl::isAsciiAlpha(m_cNextCh)) + { + // IDENT + + bool bHexColor = true; + + // parse the next identifier + OUStringBuffer sTmpBuffer(64); + do { + sTmpBuffer.append( m_cNextCh ); + if( bHexColor ) + { + bHexColor = sTmpBuffer.getLength()<7 && + ( ('0'<=m_cNextCh && '9'>=m_cNextCh) || + ('A'<=m_cNextCh && 'F'>=m_cNextCh) || + ('a'<=m_cNextCh && 'f'>=m_cNextCh) ); + } + m_cNextCh = GetNextChar(); + } while( (rtl::isAsciiAlphanumeric(m_cNextCh) || + '-' == m_cNextCh) && !IsEOF() ); + + m_aToken += sTmpBuffer; + + if( bHexColor && sTmpBuffer.getLength()==6 ) + { + bNextCh = false; + nRet = CSS1_HEXCOLOR; + + break; + } + if( '('==m_cNextCh && + ( (('u'==m_aToken[0] || 'U'==m_aToken[0]) && + m_aToken.equalsIgnoreAsciiCase( "url" )) || + (('r'==m_aToken[0] || 'R'==m_aToken[0]) && + (m_aToken.equalsIgnoreAsciiCase( "rgb" ) || m_aToken.equalsIgnoreAsciiCase( "rgba" ) ) + ) ) ) + { + int nNestCnt = 0; + OUStringBuffer sTmpBuffer2(64); + do { + sTmpBuffer2.append( m_cNextCh ); + switch( m_cNextCh ) + { + case '(': nNestCnt++; break; + case ')': nNestCnt--; break; + } + m_cNextCh = GetNextChar(); + } while( (nNestCnt>1 || ')'!=m_cNextCh) && !IsEOF() ); + sTmpBuffer2.append( m_cNextCh ); + m_aToken += sTmpBuffer2; + bNextCh = true; + nRet = 'u'==m_aToken[0] || 'U'==m_aToken[0] + ? CSS1_URL + : CSS1_RGB; + } + else + { + bNextCh = false; + nRet = CSS1_IDENT; + } + } + // error handling: ignore digit + break; + } + if( bNextCh ) + m_cNextCh = GetNextChar(); + + } while( CSS1_NULL==nRet && IsParserWorking() ); + + return nRet; +} + +// These functions implement the parser described in + +// http://www.w3.org/pub/WWW/TR/WD-css1.html +// resp. http://www.w3.org/pub/WWW/TR/WD-css1-960220.html + +// for CSS1. It's a direct implementation of the +// described Lex grammar. + +// stylesheet +// : import* rule* + +// import +// : IMPORT_SYM url + +// url +// : STRING + +void CSS1Parser::ParseStyleSheet() +{ + LOOP_CHECK_DECL + + // import* + bool bDone = false; + while( !bDone && IsParserWorking() ) + { + LOOP_CHECK_CHECK( "Infinite loop in ParseStyleSheet()/import *" ) + + switch( m_nToken ) + { + case CSS1_IMPORT_SYM: + // IMPORT_SYM url + // URL are skipped without checks + m_nToken = GetNextToken(); + break; + case CSS1_IDENT: // Look-Aheads + case CSS1_DOT_W_WS: + case CSS1_HASH: + case CSS1_PAGE_SYM: + // rule + bDone = true; + break; + default: + // error handling: ignore + break; + } + + if( !bDone ) + m_nToken = GetNextToken(); + } + + LOOP_CHECK_RESTART + + // rule * + while( IsParserWorking() ) + { + LOOP_CHECK_CHECK( "Infinite loop in ParseStyleSheet()/rule *" ) + + switch( m_nToken ) + { + case CSS1_IDENT: // Look-Aheads + case CSS1_DOT_W_WS: + case CSS1_HASH: + case CSS1_PAGE_SYM: + // rule + ParseRule(); + break; + default: + // error handling: ignore + m_nToken = GetNextToken(); + break; + } + } +} + +// rule +// : selector [ ',' selector ]* +// '{' declaration [ ';' declaration ]* '}' + +void CSS1Parser::ParseRule() +{ + // selector + std::unique_ptr<CSS1Selector> pSelector = ParseSelector(); + if( !pSelector ) + return; + + // process selector + SelectorParsed( std::move(pSelector), true ); + + LOOP_CHECK_DECL + + // [ ',' selector ]* + while( CSS1_COMMA==m_nToken && IsParserWorking() ) + { + LOOP_CHECK_CHECK( "Infinite loop in ParseRule()/selector *" ) + + // ignore ',' + m_nToken = GetNextToken(); + + // selector + pSelector = ParseSelector(); + if( !pSelector ) + return; + + // process selector + SelectorParsed( std::move(pSelector), false ); + } + + // '{' + if( CSS1_OBRACE != m_nToken ) + return; + m_nToken = GetNextToken(); + + // declaration + OUString aProperty; + std::unique_ptr<CSS1Expression> pExpr = ParseDeclaration( aProperty ); + if( !pExpr ) + return; + + // process expression + DeclarationParsed( aProperty, std::move(pExpr) ); + + LOOP_CHECK_RESTART + + // [ ';' declaration ]* + while( CSS1_SEMICOLON==m_nToken && IsParserWorking() ) + { + LOOP_CHECK_CHECK( "Infinite loop in ParseRule()/declaration *" ) + + // ';' + m_nToken = GetNextToken(); + + // declaration + if( CSS1_IDENT == m_nToken ) + { + std::unique_ptr<CSS1Expression> pExp = ParseDeclaration( aProperty ); + if( pExp ) + { + // process expression + DeclarationParsed( aProperty, std::move(pExp)); + } + } + } + + // '}' + if( CSS1_CBRACE == m_nToken ) + m_nToken = GetNextToken(); +} + +// selector +// : simple_selector+ [ ':' pseudo_element ]? + +// simple_selector +// : element_name [ DOT_WO_WS class ]? +// | DOT_W_WS class +// | id_selector + +// element_name +// : IDENT + +// class +// : IDENT + +// id_selector +// : '#' IDENT + +// pseudo_element +// : IDENT + +std::unique_ptr<CSS1Selector> CSS1Parser::ParseSelector() +{ + std::unique_ptr<CSS1Selector> pRoot; + CSS1Selector *pLast = nullptr; + + bool bDone = false; + CSS1Selector *pNew = nullptr; + + LOOP_CHECK_DECL + + // simple_selector+ + while( !bDone && IsParserWorking() ) + { + LOOP_CHECK_CHECK( "Infinite loop in ParseSelector()" ) + + bool bNextToken = true; + + switch( m_nToken ) + { + case CSS1_IDENT: + { + // element_name [ DOT_WO_WS class ]? + + // element_name + OUString aElement = m_aToken; + CSS1SelectorType eType = CSS1_SELTYPE_ELEMENT; + m_nToken = GetNextToken(); + + if( CSS1_DOT_WO_WS == m_nToken ) + { + // DOT_WO_WS + m_nToken = GetNextToken(); + + // class + if( CSS1_IDENT == m_nToken ) + { + aElement += "." + m_aToken; + eType = CSS1_SELTYPE_ELEM_CLASS; + } + else + { + // missing class + return pRoot; + } + } + else + { + // that was a look-ahead + bNextToken = false; + } + pNew = new CSS1Selector( eType, aElement ); + } + break; + case CSS1_DOT_W_WS: + // DOT_W_WS class + + // DOT_W_WS + m_nToken = GetNextToken(); + + if( CSS1_IDENT==m_nToken ) + { + // class + pNew = new CSS1Selector( CSS1_SELTYPE_CLASS, m_aToken ); + } + else + { + // missing class + return pRoot; + } + break; + case CSS1_HASH: + // '#' id_selector + + // '#' + m_nToken = GetNextToken(); + + if( CSS1_IDENT==m_nToken ) + { + // id_selector + pNew = new CSS1Selector( CSS1_SELTYPE_ID, m_aToken ); + } + else + { + // missing id_selector + return pRoot; + } + break; + + case CSS1_PAGE_SYM: + { + // @page + pNew = new CSS1Selector( CSS1_SELTYPE_PAGE, m_aToken ); + } + break; + + default: + // stop because we don't know what's next + bDone = true; + break; + } + + // if created a new selector then save it + if( pNew ) + { + OSL_ENSURE( (pRoot!=nullptr) == (pLast!=nullptr), + "Root-Selector, but no Last" ); + if( pLast ) + pLast->SetNext( pNew ); + else + pRoot.reset(pNew); + + pLast = pNew; + pNew = nullptr; + } + + if( bNextToken && !bDone ) + m_nToken = GetNextToken(); + } + + if( !pRoot ) + { + // missing simple_selector + return pRoot; + } + + // [ ':' pseudo_element ]? + if( CSS1_COLON==m_nToken && IsParserWorking() ) + { + // ':' pseudo element + m_nToken = GetNextToken(); + if( CSS1_IDENT==m_nToken ) + { + if (pLast) + pLast->SetNext( new CSS1Selector(CSS1_SELTYPE_PSEUDO,m_aToken) ); + m_nToken = GetNextToken(); + } + else + { + // missing pseudo_element + return pRoot; + } + } + + return pRoot; +} + +// declaration +// : property ':' expr prio? +// | /* empty */ + +// expression +// : term [ operator term ]* + +// term +// : unary_operator? +// [ NUMBER | STRING | PERCENTAGE | LENGTH | EMS | EXS | IDENT | +// HEXCOLOR | URL | RGB ] + +// operator +// : '/' | ',' | /* empty */ + +// unary_operator +// : '-' | '+' + +// property +// : ident + +// the sign is only used for numeric values (except PERCENTAGE) +// and it's applied on nValue! +std::unique_ptr<CSS1Expression> CSS1Parser::ParseDeclaration( OUString& rProperty ) +{ + std::unique_ptr<CSS1Expression> pRoot; + CSS1Expression *pLast = nullptr; + + // property + if( CSS1_IDENT != m_nToken ) + { + // missing property + return pRoot; + } + rProperty = m_aToken; + + m_nToken = GetNextToken(); + + // ':' + if( CSS1_COLON != m_nToken ) + { + // missing ':' + return pRoot; + } + m_nToken = GetNextToken(); + + // term [operator term]* + // here we're pretty lax regarding the syntax, but this shouldn't + // be a problem + bool bDone = false; + sal_Unicode cSign = 0, cOp = 0; + CSS1Expression *pNew = nullptr; + + LOOP_CHECK_DECL + + while( !bDone && IsParserWorking() ) + { + LOOP_CHECK_CHECK( "Infinite loop in ParseDeclaration()" ) + + switch( m_nToken ) + { + case CSS1_MINUS: + cSign = '-'; + break; + + case CSS1_PLUS: + cSign = '+'; + break; + + case CSS1_NUMBER: + case CSS1_LENGTH: + case CSS1_PIXLENGTH: + case CSS1_EMS: + case CSS1_EMX: + if( '-'==cSign ) + m_nValue = -m_nValue; + [[fallthrough]]; + case CSS1_STRING: + case CSS1_PERCENTAGE: + case CSS1_IDENT: + case CSS1_URL: + case CSS1_RGB: + case CSS1_HEXCOLOR: + pNew = new CSS1Expression( m_nToken, m_aToken, m_nValue, cOp ); + m_nValue = 0; // otherwise this also is applied to next ident + cSign = 0; + cOp = 0; + break; + + case CSS1_SLASH: + cOp = '/'; + cSign = 0; + break; + + case CSS1_COMMA: + cOp = ','; + cSign = 0; + break; + + default: + bDone = true; + break; + } + + // if created a new expression save it + if( pNew ) + { + OSL_ENSURE( (pRoot!=nullptr) == (pLast!=nullptr), + "Root-Selector, but no Last" ); + if( pLast ) + pLast->SetNext( pNew ); + else + pRoot.reset(pNew); + + pLast = pNew; + pNew = nullptr; + } + + if( !bDone ) + m_nToken = GetNextToken(); + } + + if( !pRoot ) + { + // missing term + return pRoot; + } + + // prio? + if( CSS1_IMPORTANT_SYM==m_nToken ) + { + // IMPORTANT_SYM + m_nToken = GetNextToken(); + } + + return pRoot; +} + +CSS1Parser::CSS1Parser() + : m_bWhiteSpace(false) + , m_bEOF(false) + , m_cNextCh(0) + , m_nInPos(0) + , m_nlLineNr(0) + , m_nlLinePos(0) + , m_nValue(0) + , m_eState(CSS1_PAR_ACCEPTED) + , m_nToken(CSS1_NULL) +{ +} + +CSS1Parser::~CSS1Parser() +{ +} + +void CSS1Parser::ParseStyleSheet( const OUString& rIn ) +{ + OUString aTmp( rIn ); + + sal_Unicode c; + while( !aTmp.isEmpty() && + ( ' '==(c=aTmp[0]) || '\t'==c || '\r'==c || '\n'==c ) ) + aTmp = aTmp.copy( 1 ); + + while( !aTmp.isEmpty() && ( ' '==(c=aTmp[aTmp.getLength()-1]) + || '\t'==c || '\r'==c || '\n'==c ) ) + aTmp = aTmp.copy( 0, aTmp.getLength()-1 ); + + // remove SGML comments + if( aTmp.getLength() >= 4 && + aTmp.startsWith( "<!--" ) ) + aTmp = aTmp.copy( 4 ); + + if( aTmp.getLength() >=3 && + aTmp.endsWith("-->") ) + aTmp = aTmp.copy( 0, aTmp.getLength() - 3 ); + + if( aTmp.isEmpty() ) + return; + + InitRead( aTmp ); + + ParseStyleSheet(); +} + +void CSS1Parser::ParseStyleOption( const OUString& rIn ) +{ + if( rIn.isEmpty() ) + return; + + InitRead( rIn ); + + // fdo#41796: skip over spurious semicolons + while (CSS1_SEMICOLON == m_nToken) + { + m_nToken = GetNextToken(); + } + + OUString aProperty; + std::unique_ptr<CSS1Expression> pExpr = ParseDeclaration( aProperty ); + if( !pExpr ) + return; + + // process expression + DeclarationParsed( aProperty, std::move(pExpr) ); + + LOOP_CHECK_DECL + + // [ ';' declaration ]* + while( CSS1_SEMICOLON==m_nToken && IsParserWorking() ) + { + LOOP_CHECK_CHECK( "Infinite loop in ParseStyleOption()" ) + + m_nToken = GetNextToken(); + if( CSS1_IDENT==m_nToken ) + { + std::unique_ptr<CSS1Expression> pExp = ParseDeclaration( aProperty ); + if( pExp ) + { + // process expression + DeclarationParsed( aProperty, std::move(pExp) ); + } + } + } +} + +void CSS1Parser::SelectorParsed( std::unique_ptr<CSS1Selector> /* pSelector */, bool /*bFirst*/ ) +{ +} + +void CSS1Parser::DeclarationParsed( const OUString& /*rProperty*/, + std::unique_ptr<CSS1Expression> /* pExpr */ ) +{ +} + +CSS1Selector::~CSS1Selector() +{ + delete m_pNext; +} + +CSS1Expression::~CSS1Expression() +{ + delete pNext; +} + +void CSS1Expression::GetURL( OUString& rURL ) const +{ + OSL_ENSURE( CSS1_URL==eType, "CSS1-Expression is not URL" ); + + OSL_ENSURE( aValue.startsWithIgnoreAsciiCase( "url" ) && + aValue.getLength() > 5 && + '(' == aValue[3] && + ')' == aValue[aValue.getLength()-1], + "no valid URL(...)" ); + + if( aValue.getLength() <= 5 ) + return; + + rURL = aValue.copy( 4, aValue.getLength() - 5 ); + + // tdf#94088 original stripped only spaces, but there may also be + // double quotes in CSS style URLs, so be prepared to spaces followed + // by a single quote followed by spaces + const sal_Unicode aSpace(' '); + const sal_Unicode aSingleQuote('\''); + + rURL = comphelper::string::strip(rURL, aSpace); + rURL = comphelper::string::strip(rURL, aSingleQuote); + rURL = comphelper::string::strip(rURL, aSpace); +} + +bool CSS1Expression::GetColor( Color &rColor ) const +{ + OSL_ENSURE( CSS1_IDENT==eType || CSS1_RGB==eType || + CSS1_HEXCOLOR==eType || CSS1_STRING==eType, + "CSS1-Expression cannot be colour" ); + + bool bRet = false; + sal_uInt32 nColor = SAL_MAX_UINT32; + + switch( eType ) + { + case CSS1_RGB: + { + // fourth value to 255 means no alpha transparency + // so the right by default value + sal_uInt8 aColors[4] = { 0, 0, 0, 255 }; + + // it can be "rgb" or "rgba" + if (!aValue.startsWithIgnoreAsciiCase( "rgb" ) || aValue.getLength() < 6 || + (aValue[3] != '(' && aValue[4] != '(' ) || aValue[aValue.getLength()-1] != ')') + { + break; + } + + sal_Int32 nPos = aValue.startsWithIgnoreAsciiCase( "rgba" )?5:4; // start after "rgba(" or "rgb(" + char cSep = (aValue.indexOf(',') != -1)?',':' '; + // alpha value can be after a "/" or "," + bool bIsSepAlphaDiv = (aValue.indexOf('/') != -1)?true:false; + for ( int nCol = 0; nCol < 4 && nPos > 0; ++nCol ) + { + const std::u16string_view aNumber = o3tl::getToken(aValue, 0, cSep, nPos); + + sal_Int32 nNumber = o3tl::toInt32(aNumber); + if( nNumber<0 ) + { + nNumber = 0; + } + else if( aNumber.find('%') != std::u16string_view::npos ) + { + if( nNumber > 100 ) + nNumber = 100; + nNumber *= 255; + nNumber /= 100; + } + else if( nNumber > 255 ) + nNumber = 255; + else if( aNumber.find('.') != std::u16string_view::npos ) + { + // in this case aNumber contains something like "0.3" so not an sal_Int32 + nNumber = static_cast<sal_Int32>(255.0*o3tl::toDouble(aNumber)); + } + aColors[nCol] = static_cast<sal_uInt8>(nNumber); + // rgb with alpha and '/' has this form: rgb(255 0 0 / 50%) + if (bIsSepAlphaDiv && nCol == 2) + { + // but there can be some spaces or not before and after the "/", so skip them + while (aValue[nPos] == '/' || aValue[nPos] == ' ') + ++nPos; + } + } + + rColor.SetRed( aColors[0] ); + rColor.SetGreen( aColors[1] ); + rColor.SetBlue( aColors[2] ); + rColor.SetAlpha( aColors[3] ); + + bRet = true; // something different than a colour isn't possible + } + break; + + case CSS1_IDENT: + case CSS1_STRING: + { + OUString aTmp( aValue.toAsciiUpperCase() ); + nColor = GetHTMLColor( aTmp ); + bRet = nColor != SAL_MAX_UINT32; + } + if( bRet || CSS1_STRING != eType || aValue.isEmpty() || + aValue[0] != '#' ) + break; + [[fallthrough]]; + case CSS1_HEXCOLOR: + { + // MS-IE hack: colour can also be a string + sal_Int32 nOffset = CSS1_STRING==eType ? 1 : 0; + bool bDouble = aValue.getLength()-nOffset == 3; + sal_Int32 i = nOffset, nEnd = (bDouble ? 3 : 6) + nOffset; + + nColor = 0; + for( ; i<nEnd; i++ ) + { + sal_Unicode c = (i<aValue.getLength() ? aValue[i] + : '0' ); + if( c >= '0' && c <= '9' ) + c -= 48; + else if( c >= 'A' && c <= 'F' ) + c -= 55; + else if( c >= 'a' && c <= 'f' ) + c -= 87; + else + c = 16; + + nColor *= 16; + if( c<16 ) + nColor += c; + if( bDouble ) + { + nColor *= 16; + if( c<16 ) + nColor += c; + } + } + bRet = true; + } + break; + default: + ; + } + + if( bRet && nColor!=SAL_MAX_UINT32 ) + { + rColor.SetRed( static_cast<sal_uInt8>((nColor & 0x00ff0000UL) >> 16) ); + rColor.SetGreen( static_cast<sal_uInt8>((nColor & 0x0000ff00UL) >> 8) ); + rColor.SetBlue( static_cast<sal_uInt8>(nColor & 0x000000ffUL) ); + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/parcss1.hxx b/sw/source/filter/html/parcss1.hxx new file mode 100644 index 0000000000..99f46af8e8 --- /dev/null +++ b/sw/source/filter/html/parcss1.hxx @@ -0,0 +1,264 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_FILTER_HTML_PARCSS1_HXX +#define INCLUDED_SW_SOURCE_FILTER_HTML_PARCSS1_HXX + +#include <rtl/ustring.hxx> +#include <tools/color.hxx> + +#include <memory> +#include <utility> + +// tokens of the CSS1 parser +enum CSS1Token +{ + CSS1_NULL, + + CSS1_IDENT, + CSS1_STRING, + CSS1_NUMBER, + CSS1_PERCENTAGE, + CSS1_LENGTH, // absolute length in 1/100 MM + CSS1_PIXLENGTH, // length in pixels + CSS1_EMS, + CSS1_EMX, + CSS1_HEXCOLOR, + + CSS1_DOT_W_WS, + CSS1_DOT_WO_WS, + CSS1_COLON, + CSS1_SLASH, + CSS1_PLUS, + CSS1_MINUS, + CSS1_OBRACE, + CSS1_CBRACE, + CSS1_SEMICOLON, + CSS1_COMMA, + CSS1_HASH, + + CSS1_IMPORT_SYM, + CSS1_PAGE_SYM, // Feature: PrintExt + + CSS1_IMPORTANT_SYM, + + CSS1_URL, + CSS1_RGB +}; + +enum CSS1ParserState +{ + CSS1_PAR_ACCEPTED = 0, + CSS1_PAR_WORKING +}; + +enum CSS1SelectorType +{ + CSS1_SELTYPE_ELEMENT, + CSS1_SELTYPE_ELEM_CLASS, + CSS1_SELTYPE_CLASS, + CSS1_SELTYPE_ID, + CSS1_SELTYPE_PSEUDO, + CSS1_SELTYPE_PAGE // Feature: PrintExt +}; + +/** A simple selector + * + * This class represents a simple selector, e.g. + * - a HTML element name + * - a HTML element name with a class (separated by a dot) + * - a class (without a dot) + * - an ID (set with ID=xxx) + * - a pseudo element + * + * These simple selectors are chained in a list to complete selectors + */ +class CSS1Selector +{ + CSS1SelectorType m_eType; // the type + OUString m_aSelector; // the selector itself + CSS1Selector *m_pNext; // the following component + +public: + CSS1Selector( CSS1SelectorType eTyp, OUString aSel ) + : m_eType(eTyp), m_aSelector(std::move( aSel )), m_pNext( nullptr ) + {} + + ~CSS1Selector(); + + CSS1SelectorType GetType() const { return m_eType; } + const OUString& GetString() const { return m_aSelector; } + + void SetNext( CSS1Selector *pNxt ) { m_pNext = pNxt; } + const CSS1Selector *GetNext() const { return m_pNext; } +}; + +/** a subexpression of a CSS1 declaration + * + * It contains + * - the type of this expression (= token) + * - the value as string (and/or double, with algebraic sign for NUMBER and LENGTH) + * - the operator with that it is connected with the *predecessor* expression + */ +struct CSS1Expression +{ +private: + sal_Unicode cOp; // type of the link with its predecessor + CSS1Token eType; // type of the expression + OUString aValue; // value as string + double nValue; // value as number (TWIPs for LENGTH) + CSS1Expression *pNext; // the following component + +public: + CSS1Expression( CSS1Token eTyp, OUString aVal, + double nVal, sal_Unicode cO = 0 ) + : cOp(cO), eType(eTyp), aValue(std::move(aVal)), nValue(nVal), pNext(nullptr) + {} + + ~CSS1Expression(); + + inline void Set( CSS1Token eTyp, const OUString &rVal, double nVal ); + + CSS1Token GetType() const { return eType; } + const OUString& GetString() const { return aValue; } + double GetNumber() const { return nValue; } + inline sal_uInt32 GetULength() const; + inline sal_Int32 GetSLength() const; + sal_Unicode GetOp() const { return cOp; } + + void GetURL( OUString& rURL ) const; + bool GetColor( Color &rRGB ) const; + + void SetNext( CSS1Expression *pNxt ) { pNext = pNxt; } + const CSS1Expression *GetNext() const { return pNext; } +}; + +inline void CSS1Expression::Set( CSS1Token eTyp, const OUString &rVal, + double nVal ) +{ + cOp = 0; eType = eTyp; aValue = rVal; nValue = nVal; pNext = nullptr; +} + +inline sal_uInt32 CSS1Expression::GetULength() const +{ + return nValue < 0. ? 0UL : static_cast<sal_uInt32>(nValue + .5); +} + +inline sal_Int32 CSS1Expression::GetSLength() const +{ + return static_cast<sal_Int32>(nValue + (nValue < 0. ? -.5 : .5 )); +} + +/** Parser of a style element/option + * + * This class parses the content of a style element or a style option and preprocesses it. + * + * The result of the parser is forwarded to derived parsers by the methods SelectorParsed() + * and DeclarationParsed(). Example: + * H1, H2 { font-weight: bold; text-align: right } + * | | | | + * | | | DeclP( 'text-align', 'right' ) + * | | DeclP( 'font-weight', 'bold' ) + * | SelP( 'H2', false ) + * SelP( 'H1', true ) + */ +class CSS1Parser +{ + bool m_bWhiteSpace : 1; // read a whitespace? + bool m_bEOF : 1; // is end of "file"? + + sal_Unicode m_cNextCh; // next character + + sal_Int32 m_nInPos; // current position in the input string + + sal_uInt32 m_nlLineNr; // current row number + sal_uInt32 m_nlLinePos; // current column number + + double m_nValue; // value of the token as number + + CSS1ParserState m_eState; // current state of the parser + CSS1Token m_nToken; // the current token + + OUString m_aIn; // the string to parse + OUString m_aToken; // token as string + + /// prepare parsing + void InitRead( const OUString& rIn ); + + /// @returns the next character to parse + sal_Unicode GetNextChar(); + + /// @returns the next token to parse + CSS1Token GetNextToken(); + + /// Is the parser still working? + bool IsParserWorking() const { return CSS1_PAR_WORKING == m_eState; } + + bool IsEOF() const { return m_bEOF; } + + // parse parts of the grammar + void ParseRule(); + std::unique_ptr<CSS1Selector> ParseSelector(); + std::unique_ptr<CSS1Expression> ParseDeclaration( OUString& rProperty ); + +protected: + void ParseStyleSheet(); + + /** parse the content of a HTML style element + * + * For each selector and each declaration the methods SelectorParsed() + * or DeclarationParsed() need to be called afterwards + * + * @param rIn the style element as string + */ + void ParseStyleSheet( const OUString& rIn ); + + /** parse the content of a HTML style option + * + * For each selector and each declaration the methods SelectorParsed() + * or DeclarationParsed() need to be called afterwards. + * + * @param rIn the style option as string + * @return true if ??? + */ + void ParseStyleOption( const OUString& rIn ); + + /** Called after a selector was parsed. + * + * @param pSelector The selector that was parsed + * @param bFirst if true, a new declaration starts with this selector + */ + virtual void SelectorParsed( std::unique_ptr<CSS1Selector> pSelector, bool bFirst ); + + /** Called after a declaration or property was parsed + * + * @param rProperty The declaration/property + * @param pExpr ??? + */ + virtual void DeclarationParsed( const OUString& rProperty, + std::unique_ptr<CSS1Expression> pExpr ); + +public: + CSS1Parser(); + virtual ~CSS1Parser(); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/svxcss1.cxx b/sw/source/filter/html/svxcss1.cxx new file mode 100644 index 0000000000..d75cc487da --- /dev/null +++ b/sw/source/filter/html/svxcss1.cxx @@ -0,0 +1,3153 @@ +/* -*- 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 <algorithm> +#include <cmath> +#include <limits> +#include <memory> +#include <stdlib.h> + +#include <svx/svxids.hrc> +#include <i18nlangtag/languagetag.hxx> +#include <svtools/ctrltool.hxx> +#include <svl/urihelper.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/blinkitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/lspcitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/cmapitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/borderline.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/langitem.hxx> +#include <svl/itempool.hxx> +#include <editeng/spltitem.hxx> +#include <editeng/widwitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <editeng/orphitem.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <o3tl/string_view.hxx> + +#include <hintids.hxx> + +#include "css1kywd.hxx" +#include "svxcss1.hxx" +#include "htmlnum.hxx" + +using namespace ::com::sun::star; + +/// type of functions to parse CSS1 properties +typedef void (*FnParseCSS1Prop)( const CSS1Expression *pExpr, + SfxItemSet& rItemSet, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& rParser ); + +CSS1PropertyEnum const aFontSizeTable[] = +{ + { "xx-small", 0 }, + { "x-small", 1 }, + { "small", 2 }, + { "medium", 3 }, + { "large", 4 }, + { "x-large", 5 }, + { "xx-large", 6 }, + { nullptr, 0 } +}; + +CSS1PropertyEnum const aFontWeightTable[] = +{ + { "extra-light", WEIGHT_NORMAL }, // WEIGHT_ULTRALIGHT (OBS) + { "light", WEIGHT_NORMAL }, // WEIGHT_LIGHT (OBSOLETE) + { "demi-light", WEIGHT_NORMAL }, // WEIGHT_SEMILIGHT (OBS) + { "medium", WEIGHT_NORMAL }, // WEIGHT_MEDIUM (OBS) + { "normal", WEIGHT_NORMAL }, // WEIGHT_MEDIUM + { "demi-bold", WEIGHT_NORMAL }, // WEIGHT_SEMIBOLD (OBS) + { "bold", WEIGHT_BOLD }, // WEIGHT_BOLD (OBSOLETE) + { "extra-bold", WEIGHT_BOLD }, // WEIGHT_ULTRABOLD (OBS) + { "bolder", WEIGHT_BOLD }, + { "lighter", WEIGHT_NORMAL }, + { nullptr, 0 } +}; + +CSS1PropertyEnum const aFontStyleTable[] = +{ + { "normal", ITALIC_NONE }, + { "italic", ITALIC_NORMAL }, + { "oblique", ITALIC_NORMAL }, + { nullptr, 0 } +}; + +CSS1PropertyEnum const aFontVariantTable[] = +{ + { "normal", sal_uInt16(SvxCaseMap::NotMapped) }, + { "small-caps", sal_uInt16(SvxCaseMap::SmallCaps) }, + { nullptr, 0 } +}; + +CSS1PropertyEnum const aTextTransformTable[] = +{ + { "uppercase", sal_uInt16(SvxCaseMap::Uppercase) }, + { "lowercase", sal_uInt16(SvxCaseMap::Lowercase) }, + { "capitalize", sal_uInt16(SvxCaseMap::Capitalize) }, + { nullptr, 0 } +}; + +CSS1PropertyEnum const aDirectionTable[] = +{ + { "ltr", sal_uInt16(SvxFrameDirection::Horizontal_LR_TB) }, + { "rtl", sal_uInt16(SvxFrameDirection::Horizontal_RL_TB) }, + { "inherit", sal_uInt16(SvxFrameDirection::Environment) }, + { nullptr, 0 } +}; + +CSS1PropertyEnum const aBGRepeatTable[] = +{ + { "repeat", GPOS_TILED }, + { "repeat-x", GPOS_TILED }, + { "repeat-y", GPOS_TILED }, + { "no-repeat", GPOS_NONE }, + { nullptr, 0 } +}; + +CSS1PropertyEnum const aBGHoriPosTable[] = +{ + { "left", GPOS_LT }, + { "center", GPOS_MT }, + { "right", GPOS_RT }, + { nullptr, 0 } +}; + +CSS1PropertyEnum const aBGVertPosTable[] = +{ + { "top", GPOS_LT }, + { "middle", GPOS_LM }, + { "bottom", GPOS_LB }, + { nullptr, 0 } +}; + +CSS1PropertyEnum const aTextAlignTable[] = +{ + { "left", sal_uInt16(SvxAdjust::Left) }, + { "center", sal_uInt16(SvxAdjust::Center) }, + { "right", sal_uInt16(SvxAdjust::Right) }, + { "justify", sal_uInt16(SvxAdjust::Block) }, + { nullptr, 0 } +}; + +CSS1PropertyEnum const aBorderWidthTable[] = +{ + { "thin", 0 }, // DEF_LINE_WIDTH_0 / DEF_DOUBLE_LINE0 + { "medium", 1 }, // DEF_LINE_WIDTH_1 / DEF_DOUBLE_LINE1 + { "thick", 2 }, // DEF_LINE_WIDTH_2 / DEF_DOUBLE_LINE2 + { nullptr, 0 } +}; + +namespace { + +enum CSS1BorderStyle { CSS1_BS_NONE, CSS1_BS_SINGLE, CSS1_BS_DOUBLE, CSS1_BS_DOTTED, CSS1_BS_DASHED, CSS1_BS_GROOVE, CSS1_BS_RIDGE, CSS1_BS_INSET, CSS1_BS_OUTSET }; + +} + +CSS1PropertyEnum const aBorderStyleTable[] = +{ + { "none", CSS1_BS_NONE }, + { "dotted", CSS1_BS_DOTTED }, + { "dashed", CSS1_BS_DASHED }, + { "solid", CSS1_BS_SINGLE }, + { "double", CSS1_BS_DOUBLE }, + { "groove", CSS1_BS_GROOVE }, + { "ridge", CSS1_BS_RIDGE }, + { "inset", CSS1_BS_INSET }, + { "outset", CSS1_BS_OUTSET }, + { nullptr, 0 } +}; + +CSS1PropertyEnum const aFloatTable[] = +{ + { "left", sal_uInt16(SvxAdjust::Left) }, + { "right", sal_uInt16(SvxAdjust::Right) }, + { "none", sal_uInt16(SvxAdjust::End) }, + { nullptr, 0 } +}; + +CSS1PropertyEnum const aPositionTable[] = +{ + { "absolute", SVX_CSS1_POS_ABSOLUTE }, + { "relative", SVX_CSS1_POS_RELATIVE }, + { "static", SVX_CSS1_POS_STATIC }, + { nullptr, 0 } +}; + +// Feature: PrintExt +CSS1PropertyEnum const aSizeTable[] = +{ + { "auto", SVX_CSS1_STYPE_AUTO }, + { "landscape", SVX_CSS1_STYPE_LANDSCAPE }, + { "portrait", SVX_CSS1_STYPE_PORTRAIT }, + { nullptr, 0 } +}; + +CSS1PropertyEnum const aPageBreakTable[] = +{ + { "auto", SVX_CSS1_PBREAK_AUTO }, + { "always", SVX_CSS1_PBREAK_ALWAYS }, + { "avoid", SVX_CSS1_PBREAK_AVOID }, + { "left", SVX_CSS1_PBREAK_LEFT }, + { "right", SVX_CSS1_PBREAK_RIGHT }, + { nullptr, 0 } +}; + +CSS1PropertyEnum const aNumberStyleTable[] = +{ + { "decimal", SVX_NUM_ARABIC }, + { "lower-alpha", SVX_NUM_CHARS_LOWER_LETTER }, + { "lower-latin", SVX_NUM_CHARS_LOWER_LETTER }, + { "lower-roman", SVX_NUM_ROMAN_LOWER }, + { "upper-alpha", SVX_NUM_CHARS_UPPER_LETTER }, + { "upper-latin", SVX_NUM_CHARS_UPPER_LETTER }, + { "upper-roman", SVX_NUM_ROMAN_UPPER }, + { nullptr, 0 } +}; + +CSS1PropertyEnum const aBulletStyleTable[] = +{ + { "circle", HTML_BULLETCHAR_CIRCLE }, + { "disc", HTML_BULLETCHAR_DISC }, + { "square", HTML_BULLETCHAR_SQUARE }, + { nullptr, 0 } +}; + +sal_uInt16 const aBorderWidths[] = +{ + SvxBorderLineWidth::Hairline, + SvxBorderLineWidth::VeryThin, + SvxBorderLineWidth::Thin +}; + +#undef SBORDER_ENTRY +#undef DBORDER_ENTRY + +namespace { + +struct SvxCSS1ItemIds +{ + sal_uInt16 nFont; + sal_uInt16 nFontCJK; + sal_uInt16 nFontCTL; + sal_uInt16 nPosture; + sal_uInt16 nPostureCJK; + sal_uInt16 nPostureCTL; + sal_uInt16 nWeight; + sal_uInt16 nWeightCJK; + sal_uInt16 nWeightCTL; + sal_uInt16 nFontHeight; + sal_uInt16 nFontHeightCJK; + sal_uInt16 nFontHeightCTL; + sal_uInt16 nUnderline; + sal_uInt16 nOverline; + sal_uInt16 nCrossedOut; + sal_uInt16 nColor; + sal_uInt16 nKerning; + sal_uInt16 nCaseMap; + sal_uInt16 nBlink; + + sal_uInt16 nLineSpacing; + sal_uInt16 nAdjust; + sal_uInt16 nWidows; + sal_uInt16 nOrphans; + sal_uInt16 nFormatSplit; + + // this looks a bit superfluous? TypedWhichId<SvxLRSpaceItem> nLRSpace{0}; + TypedWhichId<SvxULSpaceItem> nULSpace{0}; + sal_uInt16 nBox; + sal_uInt16 nBrush; + + sal_uInt16 nLanguage; + sal_uInt16 nLanguageCJK; + sal_uInt16 nLanguageCTL; + sal_uInt16 nDirection; +}; + +} + +static SvxCSS1ItemIds aItemIds; + +struct SvxCSS1BorderInfo +{ + Color aColor; + sal_uInt16 nAbsWidth; + sal_uInt16 nNamedWidth; + CSS1BorderStyle eStyle; + + SvxCSS1BorderInfo() : + aColor( COL_BLACK ), nAbsWidth( USHRT_MAX ), + nNamedWidth( USHRT_MAX ), eStyle( CSS1_BS_NONE ) + {} + + void SetBorderLine( SvxBoxItemLine nLine, SvxBoxItem &rBoxItem ) const; +}; + +void SvxCSS1BorderInfo::SetBorderLine( SvxBoxItemLine nLine, SvxBoxItem &rBoxItem ) const +{ + if( CSS1_BS_NONE==eStyle || nAbsWidth==0 || + (nAbsWidth==USHRT_MAX && nNamedWidth==USHRT_MAX) ) + { + rBoxItem.SetLine( nullptr, nLine ); + return; + } + + ::editeng::SvxBorderLine aBorderLine( &aColor ); + + // Line style double or single? + switch ( eStyle ) + { + case CSS1_BS_SINGLE: + aBorderLine.SetBorderLineStyle(SvxBorderLineStyle::SOLID); + break; + case CSS1_BS_DOUBLE: + aBorderLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE); + break; + case CSS1_BS_DOTTED: + aBorderLine.SetBorderLineStyle(SvxBorderLineStyle::DOTTED); + break; + case CSS1_BS_DASHED: + aBorderLine.SetBorderLineStyle(SvxBorderLineStyle::DASHED); + break; + case CSS1_BS_GROOVE: + aBorderLine.SetBorderLineStyle(SvxBorderLineStyle::ENGRAVED); + break; + case CSS1_BS_RIDGE: + aBorderLine.SetBorderLineStyle(SvxBorderLineStyle::EMBOSSED); + break; + case CSS1_BS_INSET: + aBorderLine.SetBorderLineStyle(SvxBorderLineStyle::INSET); + break; + case CSS1_BS_OUTSET: + aBorderLine.SetBorderLineStyle(SvxBorderLineStyle::OUTSET); + break; + default: + aBorderLine.SetBorderLineStyle(SvxBorderLineStyle::NONE); + break; + } + + // convert named width, if no absolute is given + if( nAbsWidth==USHRT_MAX ) + aBorderLine.SetWidth( aBorderWidths[ nNamedWidth ] ); + else + aBorderLine.SetWidth( nAbsWidth ); + + rBoxItem.SetLine( &aBorderLine, nLine ); +} + +SvxCSS1PropertyInfo::SvxCSS1PropertyInfo() +{ + Clear(); +} + +SvxCSS1PropertyInfo::SvxCSS1PropertyInfo( const SvxCSS1PropertyInfo& rProp ) : + m_aId( rProp.m_aId ), + m_bTopMargin( rProp.m_bTopMargin ), + m_bBottomMargin( rProp.m_bBottomMargin ), + m_bLeftMargin( rProp.m_bLeftMargin ), + m_bRightMargin( rProp.m_bRightMargin ), + m_bTextIndent( rProp.m_bTextIndent ), + m_bNumbering ( rProp.m_bNumbering ), + m_bBullet ( rProp.m_bBullet ), + m_eFloat( rProp.m_eFloat ), + m_ePosition( rProp.m_ePosition ), + m_nTopBorderDistance( rProp.m_nTopBorderDistance ), + m_nBottomBorderDistance( rProp.m_nBottomBorderDistance ), + m_nLeftBorderDistance( rProp.m_nLeftBorderDistance ), + m_nRightBorderDistance( rProp.m_nRightBorderDistance ), + m_nNumberingType ( rProp.m_nNumberingType ), + m_cBulletChar( rProp.m_cBulletChar ), + m_nColumnCount( rProp.m_nColumnCount ), + m_nLeft( rProp.m_nLeft ), + m_nTop( rProp.m_nTop ), + m_nWidth( rProp.m_nWidth ), + m_nHeight( rProp.m_nHeight ), + m_nLeftMargin( rProp.m_nLeftMargin ), + m_nRightMargin( rProp.m_nRightMargin ), + m_eLeftType( rProp.m_eLeftType ), + m_eTopType( rProp.m_eTopType ), + m_eWidthType( rProp.m_eWidthType ), + m_eHeightType( rProp.m_eHeightType ), + m_eLeftMarginType( rProp.m_eLeftMarginType ), + m_eRightMarginType( rProp.m_eRightMarginType ), + m_eSizeType( rProp.m_eSizeType ), + m_ePageBreakBefore( rProp.m_ePageBreakBefore ), + m_ePageBreakAfter( rProp.m_ePageBreakAfter ) +{ + for( size_t i=0; i<m_aBorderInfos.size(); ++i ) + if (rProp.m_aBorderInfos[i]) + m_aBorderInfos[i].reset( new SvxCSS1BorderInfo( *rProp.m_aBorderInfos[i] ) ); +} + +SvxCSS1PropertyInfo::~SvxCSS1PropertyInfo() +{ +} + +void SvxCSS1PropertyInfo::DestroyBorderInfos() +{ + for(auto & rp : m_aBorderInfos) + rp.reset(); +} + +void SvxCSS1PropertyInfo::Clear() +{ + m_aId.clear(); + m_bTopMargin = m_bBottomMargin = false; + m_bLeftMargin = m_bRightMargin = m_bTextIndent = false; + m_bNumbering = m_bBullet = false; + m_nLeftMargin = m_nRightMargin = 0; + m_eFloat = SvxAdjust::End; + + m_ePosition = SVX_CSS1_POS_NONE; + m_nTopBorderDistance = m_nBottomBorderDistance = + m_nLeftBorderDistance = m_nRightBorderDistance = UNSET_BORDER_DISTANCE; + + m_nNumberingType = SVX_NUM_CHARS_UPPER_LETTER; + m_cBulletChar = ' '; + + m_nColumnCount = 0; + + m_nLeft = m_nTop = m_nWidth = m_nHeight = 0; + m_eLeftType = m_eTopType = m_eWidthType = m_eHeightType = SVX_CSS1_LTYPE_NONE; + m_eLeftMarginType = SVX_CSS1_LTYPE_NONE; + m_eRightMarginType = SVX_CSS1_LTYPE_NONE; + +// Feature: PrintExt + m_eSizeType = SVX_CSS1_STYPE_NONE; + m_ePageBreakBefore = SVX_CSS1_PBREAK_NONE; + m_ePageBreakAfter = SVX_CSS1_PBREAK_NONE; + + DestroyBorderInfos(); +} + +void SvxCSS1PropertyInfo::Merge( const SvxCSS1PropertyInfo& rProp ) +{ + if( rProp.m_bTopMargin ) + m_bTopMargin = true; + if( rProp.m_bBottomMargin ) + m_bBottomMargin = true; + + if( rProp.m_bLeftMargin ) + { + m_bLeftMargin = true; + m_nLeftMargin = rProp.m_nLeftMargin; + } + if( rProp.m_bRightMargin ) + { + m_bRightMargin = true; + m_nRightMargin = rProp.m_nRightMargin; + } + if( rProp.m_bTextIndent ) + m_bTextIndent = true; + + for( size_t i=0; i<m_aBorderInfos.size(); ++i ) + { + if( rProp.m_aBorderInfos[i] ) + m_aBorderInfos[i].reset( new SvxCSS1BorderInfo( *rProp.m_aBorderInfos[i] ) ); + } + + if( UNSET_BORDER_DISTANCE != rProp.m_nTopBorderDistance ) + m_nTopBorderDistance = rProp.m_nTopBorderDistance; + if( UNSET_BORDER_DISTANCE != rProp.m_nBottomBorderDistance ) + m_nBottomBorderDistance = rProp.m_nBottomBorderDistance; + if( UNSET_BORDER_DISTANCE != rProp.m_nLeftBorderDistance ) + m_nLeftBorderDistance = rProp.m_nLeftBorderDistance; + if( UNSET_BORDER_DISTANCE != rProp.m_nRightBorderDistance ) + m_nRightBorderDistance = rProp.m_nRightBorderDistance; + + m_nColumnCount = rProp.m_nColumnCount; + + if( rProp.m_eFloat != SvxAdjust::End ) + m_eFloat = rProp.m_eFloat; + + if( rProp.m_ePosition != SVX_CSS1_POS_NONE ) + m_ePosition = rProp.m_ePosition; + +// Feature: PrintExt + if( rProp.m_eSizeType != SVX_CSS1_STYPE_NONE ) + { + m_eSizeType = rProp.m_eSizeType; + m_nWidth = rProp.m_nWidth; + m_nHeight = rProp.m_nHeight; + } + + if( rProp.m_ePageBreakBefore != SVX_CSS1_PBREAK_NONE ) + m_ePageBreakBefore = rProp.m_ePageBreakBefore; + + if( rProp.m_ePageBreakAfter != SVX_CSS1_PBREAK_NONE ) + m_ePageBreakAfter = rProp.m_ePageBreakAfter; + + if( rProp.m_eLeftType != SVX_CSS1_LTYPE_NONE ) + { + m_eLeftType = rProp.m_eLeftType; + m_nLeft = rProp.m_nLeft; + } + + if( rProp.m_eTopType != SVX_CSS1_LTYPE_NONE ) + { + m_eTopType = rProp.m_eTopType; + m_nTop = rProp.m_nTop; + } + + if( rProp.m_eWidthType != SVX_CSS1_LTYPE_NONE ) + { + m_eWidthType = rProp.m_eWidthType; + m_nWidth = rProp.m_nWidth; + } + + if( rProp.m_eHeightType != SVX_CSS1_LTYPE_NONE ) + { + m_eHeightType = rProp.m_eHeightType; + m_nHeight = rProp.m_nHeight; + } +} + +SvxCSS1BorderInfo *SvxCSS1PropertyInfo::GetBorderInfo( SvxBoxItemLine nLine, bool bCreate ) +{ + sal_uInt16 nPos = 0; + switch( nLine ) + { + case SvxBoxItemLine::TOP: nPos = 0; break; + case SvxBoxItemLine::BOTTOM: nPos = 1; break; + case SvxBoxItemLine::LEFT: nPos = 2; break; + case SvxBoxItemLine::RIGHT: nPos = 3; break; + } + + if( !m_aBorderInfos[nPos] && bCreate ) + m_aBorderInfos[nPos].reset( new SvxCSS1BorderInfo ); + + return m_aBorderInfos[nPos].get(); +} + +void SvxCSS1PropertyInfo::CopyBorderInfo( SvxBoxItemLine nSrcLine, SvxBoxItemLine nDstLine, + sal_uInt16 nWhat ) +{ + SvxCSS1BorderInfo *pSrcInfo = GetBorderInfo( nSrcLine, false ); + if( !pSrcInfo ) + return; + + SvxCSS1BorderInfo *pDstInfo = GetBorderInfo( nDstLine ); + if( (nWhat & SVX_CSS1_BORDERINFO_WIDTH) != 0 ) + { + pDstInfo->nAbsWidth = pSrcInfo->nAbsWidth; + pDstInfo->nNamedWidth = pSrcInfo->nNamedWidth; + } + + if( (nWhat & SVX_CSS1_BORDERINFO_COLOR) != 0 ) + pDstInfo->aColor = pSrcInfo->aColor; + + if( (nWhat & SVX_CSS1_BORDERINFO_STYLE) != 0 ) + pDstInfo->eStyle = pSrcInfo->eStyle; +} + +void SvxCSS1PropertyInfo::CopyBorderInfo( sal_uInt16 nCount, sal_uInt16 nWhat ) +{ + if( nCount==0 ) + { + CopyBorderInfo( SvxBoxItemLine::BOTTOM, SvxBoxItemLine::TOP, nWhat ); + CopyBorderInfo( SvxBoxItemLine::TOP, SvxBoxItemLine::LEFT, nWhat ); + } + if( nCount<=1 ) + { + CopyBorderInfo( SvxBoxItemLine::LEFT, SvxBoxItemLine::RIGHT, nWhat ); + } +} + +void SvxCSS1PropertyInfo::SetBoxItem( SfxItemSet& rItemSet, + sal_uInt16 nMinBorderDist, + const SvxBoxItem *pDfltItem ) +{ + bool bChg = m_nTopBorderDistance != UNSET_BORDER_DISTANCE || + m_nBottomBorderDistance != UNSET_BORDER_DISTANCE || + m_nLeftBorderDistance != UNSET_BORDER_DISTANCE || + m_nRightBorderDistance != UNSET_BORDER_DISTANCE; + + for( size_t i=0; !bChg && i<m_aBorderInfos.size(); ++i ) + bChg = m_aBorderInfos[i]!=nullptr; + + if( !bChg ) + return; + + std::shared_ptr<SvxBoxItem> aBoxItem(std::make_shared<SvxBoxItem>(aItemIds.nBox)); + if( pDfltItem ) + aBoxItem.reset(pDfltItem->Clone()); + + SvxCSS1BorderInfo *pInfo = GetBorderInfo( SvxBoxItemLine::TOP, false ); + if( pInfo ) + pInfo->SetBorderLine( SvxBoxItemLine::TOP, *aBoxItem ); + + pInfo = GetBorderInfo( SvxBoxItemLine::BOTTOM, false ); + if( pInfo ) + pInfo->SetBorderLine( SvxBoxItemLine::BOTTOM, *aBoxItem ); + + pInfo = GetBorderInfo( SvxBoxItemLine::LEFT, false ); + if( pInfo ) + pInfo->SetBorderLine( SvxBoxItemLine::LEFT, *aBoxItem ); + + pInfo = GetBorderInfo( SvxBoxItemLine::RIGHT, false ); + if( pInfo ) + pInfo->SetBorderLine( SvxBoxItemLine::RIGHT, *aBoxItem ); + + for( size_t i=0; i<m_aBorderInfos.size(); ++i ) + { + SvxBoxItemLine nLine = SvxBoxItemLine::TOP; + sal_uInt16 nDist = 0; + switch( i ) + { + case 0: nLine = SvxBoxItemLine::TOP; + nDist = m_nTopBorderDistance; + m_nTopBorderDistance = UNSET_BORDER_DISTANCE; + break; + case 1: nLine = SvxBoxItemLine::BOTTOM; + nDist = m_nBottomBorderDistance; + m_nBottomBorderDistance = UNSET_BORDER_DISTANCE; + break; + case 2: nLine = SvxBoxItemLine::LEFT; + nDist = m_nLeftBorderDistance; + m_nLeftBorderDistance = UNSET_BORDER_DISTANCE; + break; + case 3: nLine = SvxBoxItemLine::RIGHT; + nDist = m_nRightBorderDistance; + m_nRightBorderDistance = UNSET_BORDER_DISTANCE; + break; + } + + if( aBoxItem->GetLine( nLine ) ) + { + if( UNSET_BORDER_DISTANCE == nDist ) + nDist = aBoxItem->GetDistance( nLine ); + + if( nDist < nMinBorderDist ) + nDist = nMinBorderDist; + } + else + { + nDist = 0U; + } + + aBoxItem->SetDistance( nDist, nLine ); + } + + rItemSet.Put( *aBoxItem ); + + DestroyBorderInfos(); +} + +SvxCSS1MapEntry::SvxCSS1MapEntry( SfxItemSet aItemSet, + const SvxCSS1PropertyInfo& rProp ) : + m_aItemSet(std::move( aItemSet )), + m_aPropInfo( rProp ) +{} + +void SvxCSS1Parser::StyleParsed( const CSS1Selector * /*pSelector*/, + SfxItemSet& /*rItemSet*/, + SvxCSS1PropertyInfo& /*rPropInfo*/ ) +{ + // you see nothing is happening here +} + +void SvxCSS1Parser::SelectorParsed( std::unique_ptr<CSS1Selector> pSelector, bool bFirst ) +{ + if( bFirst ) + { + OSL_ENSURE( m_pSheetItemSet, "Where is the Item-Set for Style-Sheets?" ); + + for (const std::unique_ptr<CSS1Selector> & rpSelection : m_Selectors) + { + StyleParsed(rpSelection.get(), *m_pSheetItemSet, *m_pSheetPropInfo); + } + m_pSheetItemSet->ClearItem(); + m_pSheetPropInfo->Clear(); + + // prepare the next rule + m_Selectors.clear(); + } + + m_Selectors.push_back(std::move(pSelector)); +} + +SvxCSS1Parser::SvxCSS1Parser( SfxItemPool& rPool, OUString aBaseURL, + sal_uInt16 const *pWhichIds, sal_uInt16 nWhichIds ) : + m_sBaseURL(std::move( aBaseURL )), + m_pItemSet(nullptr), + m_pPropInfo( nullptr ), + m_eDefaultEnc( RTL_TEXTENCODING_DONTKNOW ), + m_bIgnoreFontFamily( false ) +{ + // also initialize item IDs + auto initTrueWhich = [&rPool, this](sal_uInt16 rWid) + { + rWid = rPool.GetTrueWhich(rWid, false); + m_aWhichMap = m_aWhichMap.MergeRange(rWid, rWid); + return rWid; + }; + + aItemIds.nFont = initTrueWhich( SID_ATTR_CHAR_FONT ); + aItemIds.nFontCJK = initTrueWhich( SID_ATTR_CHAR_CJK_FONT ); + aItemIds.nFontCTL = initTrueWhich( SID_ATTR_CHAR_CTL_FONT ); + aItemIds.nPosture = initTrueWhich( SID_ATTR_CHAR_POSTURE ); + aItemIds.nPostureCJK = initTrueWhich( SID_ATTR_CHAR_CJK_POSTURE ); + aItemIds.nPostureCTL = initTrueWhich( SID_ATTR_CHAR_CTL_POSTURE ); + aItemIds.nWeight = initTrueWhich( SID_ATTR_CHAR_WEIGHT ); + aItemIds.nWeightCJK = initTrueWhich( SID_ATTR_CHAR_CJK_WEIGHT ); + aItemIds.nWeightCTL = initTrueWhich( SID_ATTR_CHAR_CTL_WEIGHT ); + aItemIds.nFontHeight = initTrueWhich( SID_ATTR_CHAR_FONTHEIGHT ); + aItemIds.nFontHeightCJK = initTrueWhich( SID_ATTR_CHAR_CJK_FONTHEIGHT ); + aItemIds.nFontHeightCTL = initTrueWhich( SID_ATTR_CHAR_CTL_FONTHEIGHT ); + aItemIds.nUnderline = initTrueWhich( SID_ATTR_CHAR_UNDERLINE ); + aItemIds.nOverline = initTrueWhich( SID_ATTR_CHAR_OVERLINE ); + aItemIds.nCrossedOut = initTrueWhich( SID_ATTR_CHAR_STRIKEOUT ); + aItemIds.nColor = initTrueWhich( SID_ATTR_CHAR_COLOR ); + aItemIds.nKerning = initTrueWhich( SID_ATTR_CHAR_KERNING ); + aItemIds.nCaseMap = initTrueWhich( SID_ATTR_CHAR_CASEMAP ); + aItemIds.nBlink = initTrueWhich( SID_ATTR_FLASH ); + + aItemIds.nLineSpacing = initTrueWhich( SID_ATTR_PARA_LINESPACE ); + aItemIds.nAdjust = initTrueWhich( SID_ATTR_PARA_ADJUST ); + aItemIds.nWidows = initTrueWhich( SID_ATTR_PARA_WIDOWS ); + aItemIds.nOrphans = initTrueWhich( SID_ATTR_PARA_ORPHANS ); + aItemIds.nFormatSplit = initTrueWhich( SID_ATTR_PARA_SPLIT ); + + // every id that is used must be added + m_aWhichMap = m_aWhichMap.MergeRange(RES_MARGIN_FIRSTLINE, RES_MARGIN_FIRSTLINE); + m_aWhichMap = m_aWhichMap.MergeRange(RES_MARGIN_TEXTLEFT, RES_MARGIN_TEXTLEFT); + m_aWhichMap = m_aWhichMap.MergeRange(RES_MARGIN_RIGHT, RES_MARGIN_RIGHT); + aItemIds.nULSpace = TypedWhichId<SvxULSpaceItem>(initTrueWhich( SID_ATTR_ULSPACE )); + aItemIds.nBox = initTrueWhich( SID_ATTR_BORDER_OUTER ); + aItemIds.nBrush = initTrueWhich( SID_ATTR_BRUSH ); + + aItemIds.nLanguage = initTrueWhich( SID_ATTR_CHAR_LANGUAGE ); + aItemIds.nLanguageCJK = initTrueWhich( SID_ATTR_CHAR_CJK_LANGUAGE ); + aItemIds.nLanguageCTL = initTrueWhich( SID_ATTR_CHAR_CTL_LANGUAGE ); + aItemIds.nDirection = initTrueWhich( SID_ATTR_FRAMEDIRECTION ); + + if( pWhichIds && nWhichIds ) + for (sal_uInt16 i = 0; i < nWhichIds; ++i) + m_aWhichMap = m_aWhichMap.MergeRange(pWhichIds[i], pWhichIds[i]); + + m_pSheetItemSet.reset( new SfxItemSet( rPool, m_aWhichMap ) ); + m_pSheetPropInfo.reset( new SvxCSS1PropertyInfo ); +} + +SvxCSS1Parser::~SvxCSS1Parser() +{ + m_pSheetItemSet.reset(); + m_pSheetPropInfo.reset(); +} + +void SvxCSS1Parser::InsertId( const OUString& rId, + const SfxItemSet& rItemSet, + const SvxCSS1PropertyInfo& rProp ) +{ + InsertMapEntry( rId, rItemSet, rProp, m_Ids ); +} + +const SvxCSS1MapEntry* SvxCSS1Parser::GetId( const OUString& rId ) const +{ + CSS1Map::const_iterator itr = m_Ids.find(rId); + return itr == m_Ids.end() ? nullptr : itr->second.get(); +} + +void SvxCSS1Parser::InsertClass( const OUString& rClass, + const SfxItemSet& rItemSet, + const SvxCSS1PropertyInfo& rProp ) +{ + InsertMapEntry( rClass, rItemSet, rProp, m_Classes ); +} + +const SvxCSS1MapEntry* SvxCSS1Parser::GetClass( const OUString& rClass ) const +{ + CSS1Map::const_iterator itr = m_Classes.find(rClass); + return itr == m_Classes.end() ? nullptr : itr->second.get(); +} + +void SvxCSS1Parser::InsertPage( const OUString& rPage, + bool bPseudo, + const SfxItemSet& rItemSet, + const SvxCSS1PropertyInfo& rProp ) +{ + OUString aKey( rPage ); + if( bPseudo ) + aKey = ":" + aKey; + InsertMapEntry( aKey, rItemSet, rProp, m_Pages ); +} + +SvxCSS1MapEntry* SvxCSS1Parser::GetPage( const OUString& rPage, bool bPseudo ) +{ + OUString aKey( rPage ); + if( bPseudo ) + aKey = ":" + aKey; + + CSS1Map::iterator itr = m_Pages.find(aKey); + return itr == m_Pages.end() ? nullptr : itr->second.get(); +} + +void SvxCSS1Parser::InsertTag( const OUString& rTag, + const SfxItemSet& rItemSet, + const SvxCSS1PropertyInfo& rProp ) +{ + InsertMapEntry( rTag, rItemSet, rProp, m_Tags ); +} + +SvxCSS1MapEntry* SvxCSS1Parser::GetTag( const OUString& rTag ) +{ + CSS1Map::iterator itr = m_Tags.find(rTag); + return itr == m_Tags.end() ? nullptr : itr->second.get(); +} + +bool SvxCSS1Parser::ParseStyleSheet( const OUString& rIn ) +{ + m_pItemSet = m_pSheetItemSet.get(); + m_pPropInfo = m_pSheetPropInfo.get(); + + CSS1Parser::ParseStyleSheet( rIn ); + + for (const std::unique_ptr<CSS1Selector> & rpSelector : m_Selectors) + { + StyleParsed(rpSelector.get(), *m_pSheetItemSet, *m_pSheetPropInfo); + } + + // and clean up a little bit + m_Selectors.clear(); + m_pSheetItemSet->ClearItem(); + m_pSheetPropInfo->Clear(); + + m_pItemSet = nullptr; + m_pPropInfo = nullptr; + + return true; +} + +void SvxCSS1Parser::ParseStyleOption( const OUString& rIn, + SfxItemSet& rItemSet, + SvxCSS1PropertyInfo& rPropInfo ) +{ + m_pItemSet = &rItemSet; + m_pPropInfo = &rPropInfo; + + CSS1Parser::ParseStyleOption( rIn ); + rItemSet.ClearItem( aItemIds.nDirection ); + + m_pItemSet = nullptr; + m_pPropInfo = nullptr; +} + +bool SvxCSS1Parser::GetEnum( const CSS1PropertyEnum *pPropTable, + std::u16string_view rValue, sal_uInt16& rEnum ) +{ + while( pPropTable->pName ) + { + if( !o3tl::equalsIgnoreAsciiCase( rValue, pPropTable->pName ) ) + pPropTable++; + else + break; + } + + if( pPropTable->pName ) + rEnum = pPropTable->nEnum; + + return (pPropTable->pName != nullptr); +} + +void SvxCSS1Parser::PixelToTwip( tools::Long &rWidth, tools::Long &rHeight ) +{ + rWidth = o3tl::convert(rWidth, o3tl::Length::px, o3tl::Length::twip); + rHeight = o3tl::convert(rHeight, o3tl::Length::px, o3tl::Length::twip); +} + +sal_uInt32 SvxCSS1Parser::GetFontHeight( sal_uInt16 nSize ) const +{ + sal_uInt16 nHeight; + + switch( nSize ) + { + case 0: nHeight = 8*20; break; + case 1: nHeight = 10*20; break; + case 2: nHeight = 11*20; break; + case 3: nHeight = 12*20; break; + case 4: nHeight = 17*20; break; + case 5: nHeight = 20*20; break; + case 6: + default: nHeight = 32*20; break; + } + + return nHeight; +} + +const FontList *SvxCSS1Parser::GetFontList() const +{ + return nullptr; +} + +void SvxCSS1Parser::InsertMapEntry( const OUString& rKey, + const SfxItemSet& rItemSet, + const SvxCSS1PropertyInfo& rProp, + CSS1Map& rMap ) +{ + auto [itr,inserted] = rMap.insert(std::make_pair(rKey, nullptr)); + if (inserted) + itr->second = std::make_unique<SvxCSS1MapEntry>(rItemSet, rProp); + else + { + SvxCSS1MapEntry *const p = itr->second.get(); + MergeStyles( rItemSet, rProp, + p->GetItemSet(), p->GetPropertyInfo(), true ); + } +} + +void SvxCSS1Parser::MergeStyles( const SfxItemSet& rSrcSet, + const SvxCSS1PropertyInfo& rSrcInfo, + SfxItemSet& rTargetSet, + SvxCSS1PropertyInfo& rTargetInfo, + bool bSmart ) +{ + if( !bSmart ) + { + rTargetSet.Put( rSrcSet ); + } + else + { + // not sure if this is really necessary? + SfxItemSet copy(rSrcSet); + if (!rSrcInfo.m_bTextIndent) + { + copy.ClearItem(RES_MARGIN_FIRSTLINE); + } + if (!rSrcInfo.m_bLeftMargin) + { + copy.ClearItem(RES_MARGIN_TEXTLEFT); + } + if (!rSrcInfo.m_bRightMargin) + { + copy.ClearItem(RES_MARGIN_RIGHT); + } + + SvxULSpaceItem aULSpace( rTargetSet.Get(aItemIds.nULSpace) ); + + rTargetSet.Put(copy); + + if( rSrcInfo.m_bTopMargin || rSrcInfo.m_bBottomMargin ) + { + const SvxULSpaceItem& rNewULSpace = rSrcSet.Get( aItemIds.nULSpace ); + + if( rSrcInfo.m_bTopMargin ) + aULSpace.SetUpper( rNewULSpace.GetUpper() ); + if( rSrcInfo.m_bBottomMargin ) + aULSpace.SetLower( rNewULSpace.GetLower() ); + + rTargetSet.Put( aULSpace ); + } + } + + rTargetInfo.Merge( rSrcInfo ); +} + +void SvxCSS1Parser::SetDfltEncoding( rtl_TextEncoding eEnc ) +{ + m_eDefaultEnc = eEnc; +} + +static void ParseCSS1_font_size( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& /*rPropInfo*/, + const SvxCSS1Parser& rParser ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + sal_uLong nHeight = 0; + sal_uInt16 nPropHeight = 100; + + switch( pExpr->GetType() ) + { + case CSS1_LENGTH: + nHeight = pExpr->GetULength(); + break; + case CSS1_PIXLENGTH: + { + double fHeight = pExpr->GetNumber(); + if (fHeight < SAL_MAX_INT32/2.0 && fHeight > SAL_MIN_INT32/2.0) + { + tools::Long nPHeight = static_cast<tools::Long>(fHeight); + tools::Long nPWidth = 0; + SvxCSS1Parser::PixelToTwip(nPWidth, nPHeight); + nHeight = static_cast<sal_uLong>(nPHeight); + } + else + { + SAL_WARN("sw.html", "out-of-size pxlength: " << fHeight); + } + } + break; + case CSS1_PERCENTAGE: + // only for drop caps! + nPropHeight = o3tl::narrowing<sal_uInt16>(pExpr->GetNumber()); + break; + case CSS1_IDENT: + { + sal_uInt16 nSize; + + if( SvxCSS1Parser::GetEnum( aFontSizeTable, pExpr->GetString(), + nSize ) ) + { + nHeight = rParser.GetFontHeight( nSize ); + } + } + break; + + default: + ; + } + + if( nHeight || nPropHeight!=100 ) + { + SvxFontHeightItem aFontHeight( nHeight, nPropHeight, + aItemIds.nFontHeight ); + rItemSet.Put( aFontHeight ); + aFontHeight.SetWhich( aItemIds.nFontHeightCJK ); + rItemSet.Put( aFontHeight ); + aFontHeight.SetWhich( aItemIds.nFontHeightCTL ); + rItemSet.Put( aFontHeight ); + } +} + +static void ParseCSS1_font_family( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& /*rPropInfo*/, + const SvxCSS1Parser& rParser ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + OUStringBuffer aName; + rtl_TextEncoding eEnc = rParser.GetDfltEncoding(); + const FontList *pFList = rParser.GetFontList(); + bool bFirst = true; + bool bFound = false; + while( pExpr && (bFirst || ','==pExpr->GetOp() || !pExpr->GetOp()) ) + { + CSS1Token eType = pExpr->GetType(); + if( CSS1_IDENT==eType || CSS1_STRING==eType ) + { + OUString aIdent( pExpr->GetString() ); + + if( CSS1_IDENT==eType ) + { + // Collect all following IDs and append them with a space + const CSS1Expression *pNext = pExpr->GetNext(); + while( pNext && !pNext->GetOp() && + CSS1_IDENT==pNext->GetType() ) + { + aIdent += " " + pNext->GetString(); + pExpr = pNext; + pNext = pExpr->GetNext(); + } + } + if( !aIdent.isEmpty() ) + { + if( !bFound && pFList ) + { + sal_Handle hFont = pFList->GetFirstFontMetric( aIdent ); + if( nullptr != hFont ) + { + const FontMetric& rFMetric = FontList::GetFontMetric( hFont ); + if( RTL_TEXTENCODING_DONTKNOW != rFMetric.GetCharSet() ) + { + bFound = true; + if( RTL_TEXTENCODING_SYMBOL == rFMetric.GetCharSet() ) + eEnc = RTL_TEXTENCODING_SYMBOL; + } + } + } + if( !bFirst ) + aName.append(";"); + aName.append(aIdent); + } + } + + pExpr = pExpr->GetNext(); + bFirst = false; + } + + if( !aName.isEmpty() && !rParser.IsIgnoreFontFamily() ) + { + SvxFontItem aFont( FAMILY_DONTKNOW, aName.makeStringAndClear(), OUString(), PITCH_DONTKNOW, + eEnc, aItemIds.nFont ); + rItemSet.Put( aFont ); + aFont.SetWhich( aItemIds.nFontCJK ); + rItemSet.Put( aFont ); + aFont.SetWhich( aItemIds.nFontCTL ); + rItemSet.Put( aFont ); + } +} + +static void ParseCSS1_font_weight( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& /*rPropInfo*/, + const SvxCSS1Parser& /*rParser*/ ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + switch( pExpr->GetType() ) + { + case CSS1_IDENT: + case CSS1_STRING: // MS-IE, what else + { + sal_uInt16 nWeight; + if( SvxCSS1Parser::GetEnum( aFontWeightTable, pExpr->GetString(), + nWeight ) ) + { + SvxWeightItem aWeight( static_cast<FontWeight>(nWeight), aItemIds.nWeight ); + rItemSet.Put( aWeight ); + aWeight.SetWhich( aItemIds.nWeightCJK ); + rItemSet.Put( aWeight ); + aWeight.SetWhich( aItemIds.nWeightCTL ); + rItemSet.Put( aWeight ); + } + } + break; + case CSS1_NUMBER: + { + sal_uInt16 nWeight = o3tl::narrowing<sal_uInt16>(pExpr->GetNumber()); + SvxWeightItem aWeight( nWeight>400 ? WEIGHT_BOLD : WEIGHT_NORMAL, + aItemIds.nWeight ); + rItemSet.Put( aWeight ); + aWeight.SetWhich( aItemIds.nWeightCJK ); + rItemSet.Put( aWeight ); + aWeight.SetWhich( aItemIds.nWeightCTL ); + rItemSet.Put( aWeight ); + } + break; + + default: + ; + } +} + +static void ParseCSS1_font_style( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& /*rPropInfo*/, + const SvxCSS1Parser& /*rParser*/ ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + bool bPosture = false; + bool bCaseMap = false; + FontItalic eItalic = ITALIC_NONE; + SvxCaseMap eCaseMap = SvxCaseMap::NotMapped; + + // normal | italic || small-caps | oblique || small-caps | small-caps + // (only normal, italic and oblique are valid) + + // the value can have two values! + for( int i=0; pExpr && i<2; ++i ) + { + // also here MS-IE parser leaves traces + if( (CSS1_IDENT==pExpr->GetType() || CSS1_STRING==pExpr->GetType()) && + !pExpr->GetOp() ) + { + const OUString& rValue = pExpr->GetString(); + // first check if the value is italic or 'normal' + sal_uInt16 nItalic; + if( SvxCSS1Parser::GetEnum( aFontStyleTable, rValue, nItalic ) ) + { + eItalic = static_cast<FontItalic>(nItalic); + if( !bCaseMap && ITALIC_NONE==eItalic ) + { + // for 'normal' we must also exclude case-map + eCaseMap = SvxCaseMap::NotMapped; + bCaseMap = true; + } + bPosture = true; + } + else if( !bCaseMap && + rValue.equalsIgnoreAsciiCase( "small-caps" ) ) + { + eCaseMap = SvxCaseMap::SmallCaps; + bCaseMap = true; + } + } + + // fetch next expression + pExpr = pExpr->GetNext(); + } + + if( bPosture ) + { + SvxPostureItem aPosture( eItalic, aItemIds.nPosture ); + rItemSet.Put( aPosture ); + aPosture.SetWhich( aItemIds.nPostureCJK ); + rItemSet.Put( aPosture ); + aPosture.SetWhich( aItemIds.nPostureCTL ); + rItemSet.Put( aPosture ); + } + + if( bCaseMap ) + rItemSet.Put( SvxCaseMapItem( eCaseMap, aItemIds.nCaseMap ) ); +} + +static void ParseCSS1_font_variant( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& /*rPropInfo*/, + const SvxCSS1Parser& /*rParser*/ ) +{ + assert(pExpr && "no expression"); + + // normal | small-caps + switch( pExpr->GetType() ) + { + case CSS1_IDENT: + { + sal_uInt16 nCaseMap; + if( SvxCSS1Parser::GetEnum( aFontVariantTable, pExpr->GetString(), + nCaseMap ) ) + { + rItemSet.Put( SvxCaseMapItem( static_cast<SvxCaseMap>(nCaseMap), + aItemIds.nCaseMap ) ); + } + break; + } + default: + break; + } +} + +static void ParseCSS1_text_transform( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& /*rPropInfo*/, + const SvxCSS1Parser& /*rParser*/ ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + // none | capitalize | uppercase | lowercase + + switch( pExpr->GetType() ) + { + case CSS1_IDENT: + { + sal_uInt16 nCaseMap; + if( SvxCSS1Parser::GetEnum( aTextTransformTable, pExpr->GetString(), + nCaseMap ) ) + { + rItemSet.Put( SvxCaseMapItem( static_cast<SvxCaseMap>(nCaseMap), + aItemIds.nCaseMap ) ); + } + break; + } + default: + break; + } +} + +static void ParseCSS1_color( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& /*rPropInfo*/, + const SvxCSS1Parser& /*rParser*/ ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + switch( pExpr->GetType() ) + { + case CSS1_IDENT: + case CSS1_RGB: + case CSS1_HEXCOLOR: + case CSS1_STRING: // because MS-IE + { + Color aColor; + if( pExpr->GetColor( aColor ) ) + rItemSet.Put( SvxColorItem( aColor, aItemIds.nColor ) ); + } + break; + default: + ; + } +} + +static void ParseCSS1_column_count( const CSS1Expression *pExpr, + SfxItemSet& /*rItemSet*/, + SvxCSS1PropertyInfo &rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + assert(pExpr && "no expression"); + + if ( pExpr->GetType() == CSS1_NUMBER ) + { + double columnCount = pExpr->GetNumber(); + if ( columnCount >= 2 ) + { + rPropInfo.m_nColumnCount = columnCount; + } + } +} + +static void ParseCSS1_direction( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& /*rPropInfo*/, + const SvxCSS1Parser& /*rParser*/ ) +{ + assert(pExpr && "no expression"); + + sal_uInt16 nDir; + switch( pExpr->GetType() ) + { + case CSS1_IDENT: + case CSS1_STRING: + if( SvxCSS1Parser::GetEnum( aDirectionTable, pExpr->GetString(), + nDir ) ) + { + rItemSet.Put( SvxFrameDirectionItem( + static_cast < SvxFrameDirection >( nDir ), + aItemIds.nDirection ) ); + } + break; + default: + ; + } +} + +static void MergeHori( SvxGraphicPosition& ePos, SvxGraphicPosition eHori ) +{ + OSL_ENSURE( GPOS_LT==eHori || GPOS_MT==eHori || GPOS_RT==eHori, + "vertical position not at the top" ); + + switch( ePos ) + { + case GPOS_LT: + case GPOS_MT: + case GPOS_RT: + ePos = eHori; + break; + + case GPOS_LM: + case GPOS_MM: + case GPOS_RM: + ePos = GPOS_LT==eHori ? GPOS_LM : (GPOS_MT==eHori ? GPOS_MM : GPOS_RM); + break; + + case GPOS_LB: + case GPOS_MB: + case GPOS_RB: + ePos = GPOS_LT==eHori ? GPOS_LB : (GPOS_MT==eHori ? GPOS_MB : GPOS_RB); + break; + + default: + ; + } +} + +static void MergeVert( SvxGraphicPosition& ePos, SvxGraphicPosition eVert ) +{ + OSL_ENSURE( GPOS_LT==eVert || GPOS_LM==eVert || GPOS_LB==eVert, + "horizontal position not on the left side" ); + + switch( ePos ) + { + case GPOS_LT: + case GPOS_LM: + case GPOS_LB: + ePos = eVert; + break; + + case GPOS_MT: + case GPOS_MM: + case GPOS_MB: + ePos = GPOS_LT==eVert ? GPOS_MT : (GPOS_LM==eVert ? GPOS_MM : GPOS_MB); + break; + + case GPOS_RT: + case GPOS_RM: + case GPOS_RB: + ePos = GPOS_LT==eVert ? GPOS_RT : (GPOS_LM==eVert ? GPOS_RM : GPOS_RB); + break; + + default: + ; + } +} + +static void ParseCSS1_background( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& /*rPropInfo*/, + const SvxCSS1Parser& rParser ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + Color aColor; + OUString aURL; + + bool bColor = false, bTransparent = false; + SvxGraphicPosition eRepeat = GPOS_TILED; + SvxGraphicPosition ePos = GPOS_LT; + bool bHori = false, bVert = false; + + while( pExpr && !pExpr->GetOp() ) + { + switch( pExpr->GetType() ) + { + case CSS1_URL: + pExpr->GetURL( aURL ); + break; + + case CSS1_RGB: + bColor = pExpr->GetColor( aColor ); + break; + + case CSS1_LENGTH: + case CSS1_PIXLENGTH: + { + // since we don't know any absolute position, we + // only distinguish between 0 and !0. Therefore pixel + // can be handled like all other units. + + bool nonZero = std::trunc(pExpr->GetNumber()) != 0.0; + if( !bHori ) + { + ePos = nonZero ? GPOS_MM : GPOS_LT; + bHori = true; + } + else if( !bVert ) + { + MergeVert( ePos, (nonZero ? GPOS_LM : GPOS_LT) ); + bVert = true; + } + } + break; + + case CSS1_PERCENTAGE: + { + // the percentage is converted to an enum + + sal_uInt16 nPerc = o3tl::narrowing<sal_uInt16>(pExpr->GetNumber()); + if( !bHori ) + { + ePos = nPerc < 25 ? GPOS_LT + : (nPerc < 75 ? GPOS_MM + : GPOS_RB); + } + else if( !bVert ) + { + SvxGraphicPosition eVert = + nPerc < 25 ? GPOS_LT: (nPerc < 75 ? GPOS_LM + : GPOS_LB); + MergeVert( ePos, eVert ); + } + } + break; + + case CSS1_IDENT: + case CSS1_HEXCOLOR: + case CSS1_STRING: // because of MS-IE + { + sal_uInt16 nEnum; + const OUString &rValue = pExpr->GetString(); + if( rValue.equalsIgnoreAsciiCase( "transparent" ) ) + { + bTransparent = true; + } + if( SvxCSS1Parser::GetEnum( aBGRepeatTable, rValue, nEnum ) ) + { + eRepeat = static_cast<SvxGraphicPosition>(nEnum); + } + else if( SvxCSS1Parser::GetEnum( aBGHoriPosTable, rValue, nEnum ) ) + { + // <position>, horizontal + MergeHori( ePos, static_cast<SvxGraphicPosition>(nEnum) ); + } + else if( SvxCSS1Parser::GetEnum( aBGVertPosTable, rValue, nEnum ) ) + { + // <position>, vertical + MergeVert( ePos, static_cast<SvxGraphicPosition>(nEnum) ); + } + else if( !bColor ) + { + // <color> + bColor = pExpr->GetColor( aColor ); + } + // <scroll> we don't know + } + break; + + default: + ; + } + + pExpr = pExpr->GetNext(); + } + + // transparent beats everything + if( bTransparent ) + { + bColor = false; + aURL.clear(); + } + + // repeat has priority over a position + if( GPOS_NONE == eRepeat ) + eRepeat = ePos; + + if( !bTransparent && !bColor && aURL.isEmpty() ) + return; + + SvxBrushItem aBrushItem( aItemIds.nBrush ); + + if( bTransparent ) + aBrushItem.SetColor( COL_TRANSPARENT); + else if( bColor ) + aBrushItem.SetColor( aColor ); + + if( !aURL.isEmpty() ) + { + aBrushItem.SetGraphicLink( URIHelper::SmartRel2Abs( INetURLObject( rParser.GetBaseURL()), aURL, Link<OUString *, bool>(), false ) ); + aBrushItem.SetGraphicPos( eRepeat ); + } + + rItemSet.Put( aBrushItem ); +} + +static void ParseCSS1_background_color( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& /*rPropInfo*/, + const SvxCSS1Parser& /*rParser*/ ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + Color aColor; + + bool bColor = false, bTransparent = false; + + switch( pExpr->GetType() ) + { + case CSS1_RGB: + bColor = pExpr->GetColor( aColor ); + break; + case CSS1_IDENT: + case CSS1_HEXCOLOR: + case CSS1_STRING: // because of MS-IE + if( pExpr->GetString().equalsIgnoreAsciiCase( "transparent" ) ) + { + bTransparent = true; + } + else + { + // <color> + bColor = pExpr->GetColor( aColor ); + } + break; + default: + ; + } + + if( bTransparent || bColor ) + { + SvxBrushItem aBrushItem( aItemIds.nBrush ); + + if( bTransparent ) + aBrushItem.SetColor( COL_TRANSPARENT ); + else if( bColor ) + aBrushItem.SetColor( aColor); + + rItemSet.Put( aBrushItem ); + } +} + +static void ParseCSS1_line_height( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& /*rPropInfo*/, + const SvxCSS1Parser& ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + sal_uInt16 nHeight = 0; + sal_uInt16 nPropHeight = 0; + + switch( pExpr->GetType() ) + { + case CSS1_LENGTH: + nHeight = o3tl::narrowing<sal_uInt16>(pExpr->GetULength()); + break; + case CSS1_PIXLENGTH: + { + double fHeight = pExpr->GetNumber(); + if (fHeight < SAL_MAX_INT32/2.0 && fHeight > SAL_MIN_INT32/2.0) + { + tools::Long nPHeight = static_cast<tools::Long>(fHeight); + tools::Long nPWidth = 0; + SvxCSS1Parser::PixelToTwip(nPWidth, nPHeight); + nHeight = o3tl::narrowing<sal_uInt16>(nPHeight); + } + } + break; + case CSS1_PERCENTAGE: + { + nPropHeight = o3tl::narrowing<sal_uInt16>(pExpr->GetNumber()); + } + break; + case CSS1_NUMBER: + { + nPropHeight = o3tl::narrowing<sal_uInt16>(pExpr->GetNumber() * 100); + } + break; + default: + ; + } + + if( nHeight ) + { + if( nHeight < SvxCSS1Parser::GetMinFixLineSpace() ) + nHeight = SvxCSS1Parser::GetMinFixLineSpace(); + SvxLineSpacingItem aLSItem( nHeight, aItemIds.nLineSpacing ); + aLSItem.SetLineHeight( nHeight ); + // interpret <line-height> attribute as minimum line height + aLSItem.SetLineSpaceRule( SvxLineSpaceRule::Min ); + aLSItem.SetInterLineSpaceRule( SvxInterLineSpaceRule::Off ); + rItemSet.Put( aLSItem ); + } + else if( nPropHeight ) + { + SvxLineSpacingItem aLSItem( nPropHeight, aItemIds.nLineSpacing ); + aLSItem.SetLineSpaceRule( SvxLineSpaceRule::Auto ); + if( 100 == nPropHeight ) + aLSItem.SetInterLineSpaceRule( SvxInterLineSpaceRule::Off ); + else + aLSItem.SetPropLineSpace( nPropHeight ); + rItemSet.Put( aLSItem ); + } + +} + +static void ParseCSS1_list_style_type( const CSS1Expression *pExpr, + SfxItemSet & /*rItemSet*/, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + if( pExpr->GetType() != CSS1_IDENT ) + return; + + const OUString& rValue = pExpr->GetString(); + + // values are context-dependent, so fill both + sal_uInt16 nEnum; + if( SvxCSS1Parser::GetEnum( aNumberStyleTable, rValue, nEnum ) ) + { + rPropInfo.m_bNumbering = true; + rPropInfo.m_nNumberingType = static_cast<SvxNumType>(nEnum); + } + if( SvxCSS1Parser::GetEnum( aBulletStyleTable, rValue, nEnum ) ) + { + rPropInfo.m_bBullet = true; + rPropInfo.m_cBulletChar = nEnum; + } +} + +static void ParseCSS1_font( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& rParser ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + FontItalic eItalic = ITALIC_NONE; + SvxCaseMap eCaseMap = SvxCaseMap::NotMapped; + FontWeight eWeight = WEIGHT_NORMAL; + + // [ <font-style> || <font-variant> || <font-weight> ] ? + while( pExpr && !pExpr->GetOp() && + (CSS1_IDENT==pExpr->GetType() || + CSS1_STRING==pExpr->GetType() || + CSS1_NUMBER==pExpr->GetType()) ) + { + if( CSS1_IDENT==pExpr->GetType() || + CSS1_STRING==pExpr->GetType() ) + { + const OUString& rValue = pExpr->GetString(); + + sal_uInt16 nEnum; + + if( SvxCSS1Parser::GetEnum( aFontStyleTable, rValue, nEnum ) ) + { + eItalic = static_cast<FontItalic>(nEnum); + } + else if( SvxCSS1Parser::GetEnum( aFontVariantTable, rValue, nEnum ) ) + { + eCaseMap = static_cast<SvxCaseMap>(nEnum); + } + else if( SvxCSS1Parser::GetEnum( aFontWeightTable, rValue, nEnum ) ) + { + eWeight = static_cast<FontWeight>(nEnum); + } + } + else + { + eWeight = o3tl::narrowing<sal_uInt16>(pExpr->GetNumber()) > 400 ? WEIGHT_BOLD + : WEIGHT_NORMAL; + } + + pExpr = pExpr->GetNext(); + } + + if( !pExpr || pExpr->GetOp() ) + return; + + // Since "font" resets all values for which nothing is specified, + // we do it here. + SvxPostureItem aPosture( eItalic, aItemIds.nPosture ); + rItemSet.Put( aPosture ); + aPosture.SetWhich( aItemIds.nPostureCJK ); + rItemSet.Put( aPosture ); + aPosture.SetWhich( aItemIds.nPostureCTL ); + rItemSet.Put( aPosture ); + + rItemSet.Put( SvxCaseMapItem( eCaseMap, aItemIds.nCaseMap ) ); + + SvxWeightItem aWeight( eWeight, aItemIds.nWeight ); + rItemSet.Put( aWeight ); + aWeight.SetWhich( aItemIds.nWeightCJK ); + rItemSet.Put( aWeight ); + aWeight.SetWhich( aItemIds.nWeightCTL ); + rItemSet.Put( aWeight ); + + // font-size + CSS1Expression aExpr( pExpr->GetType(), pExpr->GetString(), + pExpr->GetNumber() ); + ParseCSS1_font_size( &aExpr, rItemSet, rPropInfo, rParser ); + pExpr = pExpr->GetNext(); + + if( !pExpr ) + return; + + // [ '/' line-height ]? + if( '/' == pExpr->GetOp() ) + { + // '/' line-height + aExpr.Set( pExpr->GetType(), pExpr->GetString(), pExpr->GetNumber() ); + ParseCSS1_line_height( &aExpr, rItemSet, rPropInfo, rParser ); + + pExpr = pExpr->GetNext(); + } + + if( !pExpr || pExpr->GetOp() ) + return; + + // font-family + ParseCSS1_font_family( pExpr, rItemSet, rPropInfo, rParser ); +} + +static void ParseCSS1_letter_spacing( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& /*rPropInfo*/, + const SvxCSS1Parser& /*rParser*/ ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + switch( pExpr->GetType() ) + { + case CSS1_LENGTH: + rItemSet.Put( SvxKerningItem( static_cast<short>(pExpr->GetSLength()), + aItemIds.nKerning ) ); + break; + + case CSS1_PIXLENGTH: + { + double fHeight = pExpr->GetNumber(); + if (fHeight < SAL_MAX_INT32/2.0 && fHeight > SAL_MIN_INT32/2.0) + { + tools::Long nPWidth = static_cast<tools::Long>(fHeight); + tools::Long nPHeight = 0; + SvxCSS1Parser::PixelToTwip( nPWidth, nPHeight ); + rItemSet.Put( SvxKerningItem( static_cast<short>(nPWidth), aItemIds.nKerning ) ); + } + } + break; + + case CSS1_NUMBER: + if( pExpr->GetNumber() == 0 ) + { + // normally unnecessary, but we are tolerant + rItemSet.Put( SvxKerningItem( short(0), aItemIds.nKerning ) ); + } + break; + + case CSS1_IDENT: + case CSS1_STRING: // As a precaution also MS-IE + if( pExpr->GetString().equalsIgnoreAsciiCase( "normal" ) ) + { + rItemSet.Put( SvxKerningItem( short(0), aItemIds.nKerning ) ); + } + break; + default: + ; + } +} + +static void ParseCSS1_text_decoration( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& /*rPropInfo*/, + const SvxCSS1Parser& /*rParser*/ ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + bool bUnderline = false; + bool bOverline = false; + bool bCrossedOut = false; + bool bBlink = false; + bool bBlinkOn = false; + FontLineStyle eUnderline = LINESTYLE_NONE; + FontLineStyle eOverline = LINESTYLE_NONE; + FontStrikeout eCrossedOut = STRIKEOUT_NONE; + + // the value can contain two values! And MS-IE also strings + while( pExpr && (pExpr->GetType() == CSS1_IDENT || + pExpr->GetType() == CSS1_STRING) && !pExpr->GetOp() ) + { + OUString aValue = pExpr->GetString().toAsciiLowerCase(); + bool bKnown = false; + + switch( aValue[0] ) + { + case 'n': + if( aValue == "none" ) + { + bUnderline = true; + eUnderline = LINESTYLE_NONE; + + bOverline = true; + eOverline = LINESTYLE_NONE; + + bCrossedOut = true; + eCrossedOut = STRIKEOUT_NONE; + + bBlink = true; + bBlinkOn = false; + + bKnown = true; + } + break; + + case 'u': + if( aValue == "underline" ) + { + bUnderline = true; + eUnderline = LINESTYLE_SINGLE; + + bKnown = true; + } + break; + + case 'o': + if( aValue == "overline" ) + { + bOverline = true; + eOverline = LINESTYLE_SINGLE; + + bKnown = true; + } + break; + + case 'l': + if( aValue == "line-through" ) + { + bCrossedOut = true; + eCrossedOut = STRIKEOUT_SINGLE; + + bKnown = true; + } + break; + + case 'b': + if( aValue == "blink" ) + { + bBlink = true; + bBlinkOn = true; + + bKnown = true; + } + break; + } + + if( !bKnown ) + { + bUnderline = true; + eUnderline = LINESTYLE_SINGLE; + } + + pExpr = pExpr->GetNext(); + } + + if( bUnderline ) + rItemSet.Put( SvxUnderlineItem( eUnderline, aItemIds.nUnderline ) ); + + if( bOverline ) + rItemSet.Put( SvxOverlineItem( eOverline, aItemIds.nOverline ) ); + + if( bCrossedOut ) + rItemSet.Put( SvxCrossedOutItem( eCrossedOut, aItemIds.nCrossedOut ) ); + + if( bBlink ) + rItemSet.Put( SvxBlinkItem( bBlinkOn, aItemIds.nBlink ) ); +} + +static void ParseCSS1_text_align( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& /*rPropInfo*/, + const SvxCSS1Parser& /*rParser*/ ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + if( CSS1_IDENT==pExpr->GetType() || + CSS1_STRING==pExpr->GetType() ) // MS-IE, again + { + sal_uInt16 nAdjust; + if( SvxCSS1Parser::GetEnum( aTextAlignTable, pExpr->GetString(), + nAdjust ) ) + { + rItemSet.Put( SvxAdjustItem( static_cast<SvxAdjust>(nAdjust), + aItemIds.nAdjust ) ); + } + } +} + +static void ParseCSS1_text_indent( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + short nIndent = 0; + bool bSet = false; + switch( pExpr->GetType() ) + { + case CSS1_LENGTH: + { + double n = std::round(pExpr->GetNumber()); + SAL_WARN_IF( + n < std::numeric_limits<short>::min() || n > std::numeric_limits<short>::max(), + "sw.html", "clamping length " << n << " to short range"); + nIndent = static_cast<short>( + std::clamp( + n, double(std::numeric_limits<short>::min()), + double(std::numeric_limits<short>::max()))); + bSet = true; + } + break; + case CSS1_PIXLENGTH: + { + double fWidth = pExpr->GetNumber(); + if (fWidth < SAL_MAX_INT32/2.0 && fWidth > SAL_MIN_INT32/2.0) + { + tools::Long nPWidth = static_cast<tools::Long>(fWidth); + tools::Long nPHeight = 0; + SvxCSS1Parser::PixelToTwip( nPWidth, nPHeight ); + nIndent = static_cast<short>(nPWidth); + bSet = true; + } + } + break; + case CSS1_PERCENTAGE: + // we aren't able + break; + default: + ; + } + + if( !bSet ) + return; + + SvxFirstLineIndentItem const firstLine(nIndent, RES_MARGIN_FIRSTLINE); + rItemSet.Put(firstLine); + rPropInfo.m_bTextIndent = true; +} + +static void ParseCSS1_margin_left( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + tools::Long nLeft = 0; + bool bSet = false; + switch( pExpr->GetType() ) + { + case CSS1_LENGTH: + { + nLeft = pExpr->GetSLength(); + bSet = true; + } + break; + case CSS1_PIXLENGTH: + { + double fLeft = pExpr->GetNumber(); + if (fLeft < SAL_MAX_INT32/2.0 && fLeft > SAL_MIN_INT32/2.0) + { + nLeft = static_cast<tools::Long>(fLeft); + tools::Long nPHeight = 0; + SvxCSS1Parser::PixelToTwip( nLeft, nPHeight ); + bSet = true; + } + else + { + SAL_WARN("sw.html", "out-of-size pxlength: " << fLeft); + } + } + break; + case CSS1_PERCENTAGE: + // we aren't able + break; + default: + ; + } + + if (pExpr->GetString() == "auto") + { + rPropInfo.m_bLeftMargin = true; + rPropInfo.m_eLeftMarginType = SVX_CSS1_LTYPE_AUTO; + } + + if( !bSet ) + return; + + rPropInfo.m_nLeftMargin = nLeft; + if( nLeft < 0 ) + nLeft = 0; + + // TODO: other things may need a SvxLeftMarginItem ? but they currently convert it anyway so they can convert that too. + SvxTextLeftMarginItem const leftMargin(o3tl::narrowing<sal_uInt16>(nLeft), RES_MARGIN_TEXTLEFT); + rItemSet.Put(leftMargin); + rPropInfo.m_bLeftMargin = true; +} + +static void ParseCSS1_margin_right( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + tools::Long nRight = 0; + bool bSet = false; + switch( pExpr->GetType() ) + { + case CSS1_LENGTH: + { + nRight = pExpr->GetSLength(); + bSet = true; + } + break; + case CSS1_PIXLENGTH: + { + double fRight = pExpr->GetNumber(); + if (fRight < SAL_MAX_INT32/2.0 && fRight > SAL_MIN_INT32/2.0) + { + nRight = static_cast<tools::Long>(fRight); + tools::Long nPHeight = 0; + SvxCSS1Parser::PixelToTwip( nRight, nPHeight ); + bSet = true; + } + } + break; + case CSS1_PERCENTAGE: + // we aren't able + break; + default: + ; + } + + if (pExpr->GetString() == "auto") + { + rPropInfo.m_bRightMargin = true; + rPropInfo.m_eRightMarginType = SVX_CSS1_LTYPE_AUTO; + } + + if( !bSet ) + return; + + rPropInfo.m_nRightMargin = nRight; + if( nRight < 0 ) + nRight = 0; + + SvxRightMarginItem rightMargin(o3tl::narrowing<sal_uInt16>(nRight), RES_MARGIN_RIGHT); + rItemSet.Put(rightMargin); + rPropInfo.m_bRightMargin = true; +} + +static void ParseCSS1_margin_top( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + assert(pExpr && "no expression"); + + sal_uInt16 nUpper = 0; + bool bSet = false; + switch( pExpr->GetType() ) + { + case CSS1_LENGTH: + { + tools::Long nTmp = pExpr->GetSLength(); + if( nTmp < 0 ) + nTmp = 0; + nUpper = o3tl::narrowing<sal_uInt16>(nTmp); + bSet = true; + } + break; + case CSS1_PIXLENGTH: + { + double fHeight = pExpr->GetNumber(); + if (fHeight < SAL_MAX_INT32/2.0 && fHeight > SAL_MIN_INT32/2.0) + { + tools::Long nPWidth = 0; + tools::Long nPHeight = static_cast<tools::Long>(fHeight); + if( nPHeight < 0 ) + nPHeight = 0; + SvxCSS1Parser::PixelToTwip( nPWidth, nPHeight ); + nUpper = o3tl::narrowing<sal_uInt16>(nPHeight); + bSet = true; + } + } + break; + case CSS1_PERCENTAGE: + // we aren't able + break; + default: + ; + } + + if( !bSet ) + return; + + if( const SvxULSpaceItem* pItem = rItemSet.GetItemIfSet( aItemIds.nULSpace, false ) ) + { + SvxULSpaceItem aULItem( *pItem ); + aULItem.SetUpper( nUpper ); + rItemSet.Put( aULItem ); + } + else + { + SvxULSpaceItem aULItem( aItemIds.nULSpace ); + aULItem.SetUpper( nUpper ); + rItemSet.Put( aULItem ); + } + rPropInfo.m_bTopMargin = true; +} + +static void ParseCSS1_margin_bottom( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + sal_uInt16 nLower = 0; + bool bSet = false; + switch( pExpr->GetType() ) + { + case CSS1_LENGTH: + { + tools::Long nTmp = pExpr->GetSLength(); + if( nTmp < 0 ) + nTmp = 0; + nLower = o3tl::narrowing<sal_uInt16>(nTmp); + bSet = true; + } + break; + case CSS1_PIXLENGTH: + { + double fHeight = pExpr->GetNumber(); + if (fHeight < SAL_MAX_INT32/2.0 && fHeight > SAL_MIN_INT32/2.0) + { + tools::Long nPWidth = 0; + tools::Long nPHeight = static_cast<tools::Long>(fHeight); + if( nPHeight < 0 ) + nPHeight = 0; + SvxCSS1Parser::PixelToTwip( nPWidth, nPHeight ); + nLower = o3tl::narrowing<sal_uInt16>(nPHeight); + bSet = true; + } + } + break; + case CSS1_PERCENTAGE: + // we aren't able + break; + default: + ; + } + + if( !bSet ) + return; + + if( const SvxULSpaceItem* pItem = rItemSet.GetItemIfSet( aItemIds.nULSpace, false ) ) + { + SvxULSpaceItem aULItem( *pItem ); + aULItem.SetLower( nLower ); + rItemSet.Put( aULItem ); + } + else + { + SvxULSpaceItem aULItem( aItemIds.nULSpace ); + aULItem.SetLower( nLower ); + rItemSet.Put( aULItem ); + } + rPropInfo.m_bBottomMargin = true; +} + +static void ParseCSS1_margin( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + tools::Long nMargins[4] = { 0, 0, 0, 0 }; + bool bSetMargins[4] = { false, false, false, false }; + + for( int i=0; pExpr && i<4 && !pExpr->GetOp(); ++i ) + { + bool bSetThis = false; + tools::Long nMargin = 0; + + switch( pExpr->GetType() ) + { + case CSS1_LENGTH: + { + nMargin = pExpr->GetSLength(); + bSetThis = true; + } + break; + case CSS1_PIXLENGTH: + { + double fMargin = pExpr->GetNumber(); + if (fMargin < SAL_MAX_INT32/2.0 && fMargin > SAL_MIN_INT32/2.0) + { + nMargin = static_cast<tools::Long>(fMargin); + tools::Long nPWidth = 0; + SvxCSS1Parser::PixelToTwip( nPWidth, nMargin ); + bSetThis = true; + } + else + { + SAL_WARN("sw.html", "out-of-size pxlength: " << fMargin); + } + } + break; + case CSS1_PERCENTAGE: + // we aren't able + break; + default: + ; + } + + if( bSetThis ) + { + // 0 = top + // 1 = right + // 2 = bottom + // 3 = left + switch( i ) + { + case 0: + nMargins[0] = nMargins[1] =nMargins[2] = nMargins[3] = nMargin; + bSetMargins[0] = bSetMargins[1] = + bSetMargins[2] = bSetMargins[3] = true; + break; + case 1: + nMargins[1] = nMargins[3] = nMargin; // right + left + bSetMargins[1] = bSetMargins[3] = true; + break; + case 2: + nMargins[2] = nMargin; // bottom + bSetMargins[2] = true; + break; + case 3: + nMargins[3] = nMargin; // left + bSetMargins[3] = true; + break; + } + } + pExpr = pExpr->GetNext(); + } + + if( bSetMargins[3] || bSetMargins[1] ) + { + if( bSetMargins[3] ) + { + rPropInfo.m_bLeftMargin = true; + rPropInfo.m_nLeftMargin = nMargins[3]; + if( nMargins[3] < 0 ) + nMargins[3] = 0; + } + if( bSetMargins[1] ) + { + rPropInfo.m_bRightMargin = true; + rPropInfo.m_nRightMargin = nMargins[1]; + if( nMargins[1] < 0 ) + nMargins[1] = 0; + } + + if (bSetMargins[3]) + { + SvxTextLeftMarginItem const leftMargin(o3tl::narrowing<sal_uInt16>(nMargins[3]), RES_MARGIN_TEXTLEFT); + rItemSet.Put(leftMargin); + } + if (bSetMargins[1]) + { + SvxRightMarginItem const rightMargin(o3tl::narrowing<sal_uInt16>(nMargins[1]), RES_MARGIN_RIGHT); + rItemSet.Put(rightMargin); + } + } + + if( !(bSetMargins[0] || bSetMargins[2]) ) + return; + + if( nMargins[0] < 0 ) + nMargins[0] = 0; + if( nMargins[2] < 0 ) + nMargins[2] = 0; + + if( const SvxULSpaceItem* pItem = rItemSet.GetItemIfSet( aItemIds.nULSpace, false ) ) + { + SvxULSpaceItem aULItem( *pItem ); + if( bSetMargins[0] ) + aULItem.SetUpper( o3tl::narrowing<sal_uInt16>(nMargins[0]) ); + if( bSetMargins[2] ) + aULItem.SetLower( o3tl::narrowing<sal_uInt16>(nMargins[2]) ); + rItemSet.Put( aULItem ); + } + else + { + SvxULSpaceItem aULItem( aItemIds.nULSpace ); + if( bSetMargins[0] ) + aULItem.SetUpper( o3tl::narrowing<sal_uInt16>(nMargins[0]) ); + if( bSetMargins[2] ) + aULItem.SetLower( o3tl::narrowing<sal_uInt16>(nMargins[2]) ); + rItemSet.Put( aULItem ); + } + + rPropInfo.m_bTopMargin |= bSetMargins[0]; + rPropInfo.m_bBottomMargin |= bSetMargins[2]; +} + +static bool ParseCSS1_padding_xxx( const CSS1Expression *pExpr, + SvxCSS1PropertyInfo& rPropInfo, + SvxBoxItemLine nWhichLine ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + bool bSet = false; + sal_uInt16 nDist = 0; + + switch( pExpr->GetType() ) + { + case CSS1_LENGTH: + { + tools::Long nTmp = pExpr->GetSLength(); + if( nTmp < 0 ) + nTmp = 0; + else if( nTmp > SvxCSS1PropertyInfo::UNSET_BORDER_DISTANCE-1 ) + nTmp = SvxCSS1PropertyInfo::UNSET_BORDER_DISTANCE-1; + nDist = o3tl::narrowing<sal_uInt16>(nTmp); + bSet = true; + } + break; + case CSS1_PIXLENGTH: + { + double fWidth = pExpr->GetNumber(); + if (fWidth < SAL_MAX_INT32/2.0 && fWidth > SAL_MIN_INT32/2.0) + { + tools::Long nPWidth = static_cast<tools::Long>(fWidth); + tools::Long nPHeight = 0; + if( nPWidth < 0 ) + nPWidth = 0; + SvxCSS1Parser::PixelToTwip( nPWidth, nPHeight ); + if( nPWidth > SvxCSS1PropertyInfo::UNSET_BORDER_DISTANCE-1 ) + nPWidth = SvxCSS1PropertyInfo::UNSET_BORDER_DISTANCE-1; + nDist = o3tl::narrowing<sal_uInt16>(nPWidth); + bSet = true; + } + } + break; + case CSS1_PERCENTAGE: + // we aren't able + break; + default: + ; + } + + if( bSet ) + { + switch( nWhichLine ) + { + case SvxBoxItemLine::TOP: rPropInfo.m_nTopBorderDistance = nDist; break; + case SvxBoxItemLine::BOTTOM: rPropInfo.m_nBottomBorderDistance = nDist;break; + case SvxBoxItemLine::LEFT: rPropInfo.m_nLeftBorderDistance = nDist; break; + case SvxBoxItemLine::RIGHT: rPropInfo.m_nRightBorderDistance = nDist; break; + } + } + + return bSet; +} + +static void ParseCSS1_padding_top( const CSS1Expression *pExpr, + SfxItemSet & /*rItemSet*/, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + ParseCSS1_padding_xxx( pExpr, rPropInfo, SvxBoxItemLine::TOP ); +} + +static void ParseCSS1_padding_bottom( const CSS1Expression *pExpr, + SfxItemSet & /*rItemSet*/, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + ParseCSS1_padding_xxx( pExpr, rPropInfo, SvxBoxItemLine::BOTTOM ); +} + +static void ParseCSS1_padding_left( const CSS1Expression *pExpr, + SfxItemSet & /*rItemSet*/, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + ParseCSS1_padding_xxx( pExpr, rPropInfo, SvxBoxItemLine::LEFT ); +} + +static void ParseCSS1_padding_right( const CSS1Expression *pExpr, + SfxItemSet & /*rItemSet*/, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + ParseCSS1_padding_xxx( pExpr, rPropInfo, SvxBoxItemLine::RIGHT ); +} + +static void ParseCSS1_padding( const CSS1Expression *pExpr, + SfxItemSet & /*rItemSet*/, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + int n=0; + while( n<4 && pExpr && !pExpr->GetOp() ) + { + SvxBoxItemLine nLine = n==0 || n==2 ? SvxBoxItemLine::BOTTOM : SvxBoxItemLine::LEFT; + if( ParseCSS1_padding_xxx( pExpr, rPropInfo, nLine ) ) + { + if( n==0 ) + { + rPropInfo.m_nTopBorderDistance = rPropInfo.m_nBottomBorderDistance; + rPropInfo.m_nLeftBorderDistance = rPropInfo.m_nTopBorderDistance; + } + if( n <= 1 ) + rPropInfo.m_nRightBorderDistance = rPropInfo.m_nLeftBorderDistance; + } + + pExpr = pExpr->GetNext(); + n++; + } +} + +static void ParseCSS1_border_xxx( const CSS1Expression *pExpr, + SfxItemSet & /*rItemSet*/, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/, + SvxBoxItemLine nWhichLine, bool bAll ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + sal_uInt16 nWidth = USHRT_MAX; // line thickness + sal_uInt16 nNWidth = 1; // named line thickness (and default) + CSS1BorderStyle eStyle = CSS1_BS_NONE; // line style + Color aColor; + bool bColor = false; + + while( pExpr && !pExpr->GetOp() ) + { + switch( pExpr->GetType() ) + { + case CSS1_RGB: + case CSS1_HEXCOLOR: + if( pExpr->GetColor( aColor ) ) + bColor = true; + break; + + case CSS1_IDENT: + { + const OUString& rValue = pExpr->GetString(); + sal_uInt16 nValue; + if( SvxCSS1Parser::GetEnum( aBorderWidthTable, rValue, nValue ) ) + { + nNWidth = nValue; + } + else if( SvxCSS1Parser::GetEnum( aBorderStyleTable, rValue, nValue ) ) + { + eStyle = static_cast<CSS1BorderStyle>(nValue); + } + else if( pExpr->GetColor( aColor ) ) + { + bColor = true; + } + } + break; + + case CSS1_LENGTH: + nWidth = o3tl::narrowing<sal_uInt16>(pExpr->GetULength()); + break; + + case CSS1_PIXLENGTH: + { + // One Pixel becomes a hairline (is prettier) + double fWidth = pExpr->GetNumber(); + if (fWidth > 1.0 && fWidth < SAL_MAX_INT32/2.0) + { + bool bHori = nWhichLine == SvxBoxItemLine::TOP || + nWhichLine == SvxBoxItemLine::BOTTOM; + + tools::Long nPWidth = bHori ? 0 : fWidth; + tools::Long nPHeight = bHori ? fWidth : 0; + SvxCSS1Parser::PixelToTwip( nPWidth, nPHeight ); + nWidth = o3tl::narrowing<sal_uInt16>(bHori ? nPHeight : nPWidth); + } + else + nWidth = 1; + } + break; + + default: + ; + } + + pExpr = pExpr->GetNext(); + } + + for( int i=0; i<4; ++i ) + { + SvxBoxItemLine nLine = SvxBoxItemLine::TOP; + switch( i ) + { + case 0: nLine = SvxBoxItemLine::TOP; break; + case 1: nLine = SvxBoxItemLine::BOTTOM; break; + case 2: nLine = SvxBoxItemLine::LEFT; break; + case 3: nLine = SvxBoxItemLine::RIGHT; break; + } + + if( bAll || nLine == nWhichLine ) + { + SvxCSS1BorderInfo *pInfo = rPropInfo.GetBorderInfo( nLine ); + pInfo->eStyle = eStyle; + pInfo->nAbsWidth = nWidth; + pInfo->nNamedWidth = nNWidth; + if( bColor ) + pInfo->aColor = aColor; + } + } +} + +static void ParseCSS1_border_xxx_width( const CSS1Expression *pExpr, + SfxItemSet & /*rItemSet*/, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/, + SvxBoxItemLine nWhichLine ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + sal_uInt16 nWidth = USHRT_MAX; // line thickness + sal_uInt16 nNWidth = 1; // named line thickness (and default) + + switch( pExpr->GetType() ) + { + case CSS1_IDENT: + { + sal_uInt16 nValue; + if( SvxCSS1Parser::GetEnum( aBorderWidthTable, pExpr->GetString(), nValue ) ) + { + nNWidth = nValue; + } + } + break; + + case CSS1_LENGTH: + nWidth = o3tl::narrowing<sal_uInt16>(pExpr->GetULength()); + break; + + case CSS1_PIXLENGTH: + { + double fLength = pExpr->GetNumber(); + if (fLength < SAL_MAX_INT32/2.0 && fLength > SAL_MIN_INT32/2.0) + { + tools::Long nWidthL = static_cast<tools::Long>(fLength); + + bool bHori = nWhichLine == SvxBoxItemLine::TOP || + nWhichLine == SvxBoxItemLine::BOTTOM; + + tools::Long nPWidth = bHori ? 0 : nWidthL; + tools::Long nPHeight = bHori ? nWidthL : 0; + SvxCSS1Parser::PixelToTwip( nPWidth, nPHeight ); + nWidth = o3tl::narrowing<sal_uInt16>(bHori ? nPHeight : nPWidth); + } + } + break; + + default: + ; + } + + SvxCSS1BorderInfo *pInfo = rPropInfo.GetBorderInfo( nWhichLine ); + pInfo->nAbsWidth = nWidth; + pInfo->nNamedWidth = nNWidth; +} + +static void ParseCSS1_border_top_width( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& rParser ) +{ + ParseCSS1_border_xxx_width( pExpr, rItemSet, rPropInfo, rParser, SvxBoxItemLine::TOP ); +} + +static void ParseCSS1_border_right_width( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& rParser ) +{ + ParseCSS1_border_xxx_width( pExpr, rItemSet, rPropInfo, rParser, SvxBoxItemLine::RIGHT ); +} + +static void ParseCSS1_border_bottom_width( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& rParser ) +{ + ParseCSS1_border_xxx_width( pExpr, rItemSet, rPropInfo, rParser, SvxBoxItemLine::BOTTOM ); +} + +static void ParseCSS1_border_left_width( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& rParser ) +{ + ParseCSS1_border_xxx_width( pExpr, rItemSet, rPropInfo, rParser, SvxBoxItemLine::LEFT ); +} + +static void ParseCSS1_border_width( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& rParser ) +{ + sal_uInt16 n=0; + while( n<4 && pExpr && !pExpr->GetOp() ) + { + SvxBoxItemLine nLine = n==0 || n==2 ? SvxBoxItemLine::BOTTOM : SvxBoxItemLine::LEFT; + ParseCSS1_border_xxx_width( pExpr, rItemSet, rPropInfo, rParser, nLine ); + rPropInfo.CopyBorderInfo( n, SVX_CSS1_BORDERINFO_WIDTH ); + + pExpr = pExpr->GetNext(); + n++; + } +} + +static void ParseCSS1_border_color( const CSS1Expression *pExpr, + SfxItemSet & /*rItemSet*/, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + sal_uInt16 n=0; + while( n<4 && pExpr && !pExpr->GetOp() ) + { + SvxBoxItemLine nLine = n==0 || n==2 ? SvxBoxItemLine::BOTTOM : SvxBoxItemLine::LEFT; + Color aColor; + switch( pExpr->GetType() ) + { + case CSS1_RGB: + case CSS1_HEXCOLOR: + case CSS1_IDENT: + if( pExpr->GetColor( aColor ) ) + rPropInfo.GetBorderInfo( nLine )->aColor = aColor; + break; + default: + ; + } + rPropInfo.CopyBorderInfo( n, SVX_CSS1_BORDERINFO_COLOR ); + + pExpr = pExpr->GetNext(); + n++; + } +} + +static void ParseCSS1_border_style( const CSS1Expression *pExpr, + SfxItemSet & /*rItemSet*/, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + sal_uInt16 n=0; + while( n<4 && pExpr && !pExpr->GetOp() ) + { + SvxBoxItemLine nLine = n==0 || n==2 ? SvxBoxItemLine::BOTTOM : SvxBoxItemLine::LEFT; + sal_uInt16 nValue = 0; + if( CSS1_IDENT==pExpr->GetType() && + SvxCSS1Parser::GetEnum( aBorderStyleTable, pExpr->GetString(), + nValue ) ) + { + rPropInfo.GetBorderInfo( nLine )->eStyle = static_cast<CSS1BorderStyle>(nValue); + } + rPropInfo.CopyBorderInfo( n, SVX_CSS1_BORDERINFO_STYLE ); + + pExpr = pExpr->GetNext(); + n++; + } +} + +static void ParseCSS1_border_top( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& rParser ) +{ + ParseCSS1_border_xxx( pExpr, rItemSet, rPropInfo, rParser, SvxBoxItemLine::TOP, false ); +} + +static void ParseCSS1_border_right( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& rParser ) +{ + ParseCSS1_border_xxx( pExpr, rItemSet, rPropInfo, rParser, SvxBoxItemLine::RIGHT, false ); +} + +static void ParseCSS1_border_bottom( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& rParser ) +{ + ParseCSS1_border_xxx( pExpr, rItemSet, rPropInfo, rParser, SvxBoxItemLine::BOTTOM, false ); +} + +static void ParseCSS1_border_left( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& rParser ) +{ + ParseCSS1_border_xxx( pExpr, rItemSet, rPropInfo, rParser, SvxBoxItemLine::LEFT, false ); +} + +static void ParseCSS1_border( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& rParser ) +{ + ParseCSS1_border_xxx( pExpr, rItemSet, rPropInfo, rParser, SvxBoxItemLine::TOP, true ); +} + +static void ParseCSS1_float( const CSS1Expression *pExpr, + SfxItemSet & /*rItemSet*/, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + if( CSS1_IDENT==pExpr->GetType() ) + { + sal_uInt16 nFloat; + if( SvxCSS1Parser::GetEnum( aFloatTable, pExpr->GetString(), nFloat ) ) + rPropInfo.m_eFloat = static_cast<SvxAdjust>(nFloat); + } +} + +static void ParseCSS1_position( const CSS1Expression *pExpr, + SfxItemSet & /*rItemSet*/, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + OSL_ENSURE( pExpr, "no expression" ); + + if( CSS1_IDENT==pExpr->GetType() ) + { + sal_uInt16 nPos; + if( SvxCSS1Parser::GetEnum( aPositionTable, pExpr->GetString(), nPos ) ) + rPropInfo.m_ePosition = static_cast<SvxCSS1Position>(nPos); + } +} + +static void ParseCSS1_length( const CSS1Expression *pExpr, + tools::Long& rLength, + SvxCSS1LengthType& rLengthType, + bool bHori ) +{ + switch( pExpr->GetType() ) + { + case CSS1_IDENT: + if( pExpr->GetString().equalsIgnoreAsciiCase( "auto" ) ) + { + rLength = 0; + rLengthType = SVX_CSS1_LTYPE_AUTO; + } + break; + + case CSS1_LENGTH: + rLength = pExpr->GetSLength(); + rLengthType = SVX_CSS1_LTYPE_TWIP; + break; + + case CSS1_PIXLENGTH: + case CSS1_NUMBER: // because of Netscape and IE + { + double fLength = pExpr->GetNumber(); + if (fLength < SAL_MAX_INT32/2.0 && fLength > SAL_MIN_INT32/2.0) + { + tools::Long nWidthL = static_cast<tools::Long>(fLength); + tools::Long nPWidth = bHori ? 0 : nWidthL; + tools::Long nPHeight = bHori ? nWidthL : 0; + SvxCSS1Parser::PixelToTwip( nPWidth, nPHeight ); + rLength = (bHori ? nPHeight : nPWidth); + rLengthType = SVX_CSS1_LTYPE_TWIP; + } + } + break; + + case CSS1_PERCENTAGE: + rLength = static_cast<tools::Long>(std::min(pExpr->GetNumber(), 100.0)); + rLengthType = SVX_CSS1_LTYPE_PERCENTAGE; + break; + + default: + ; + } +} + +static void ParseCSS1_width( const CSS1Expression *pExpr, + SfxItemSet & /*rItemSet*/, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + ParseCSS1_length( pExpr, rPropInfo.m_nWidth, rPropInfo.m_eWidthType, true ); +} + +static void ParseCSS1_height( const CSS1Expression *pExpr, + SfxItemSet & /*rItemSet*/, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + ParseCSS1_length( pExpr, rPropInfo.m_nHeight, rPropInfo.m_eHeightType, false ); +} + +static void ParseCSS1_left( const CSS1Expression *pExpr, + SfxItemSet & /*rItemSet*/, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + ParseCSS1_length( pExpr, rPropInfo.m_nLeft, rPropInfo.m_eLeftType, true ); +} + +static void ParseCSS1_top( const CSS1Expression *pExpr, + SfxItemSet & /*rItemSet*/, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + ParseCSS1_length( pExpr, rPropInfo.m_nTop, rPropInfo.m_eTopType, false ); +} + +// Feature: PrintExt +static void ParseCSS1_size( const CSS1Expression *pExpr, + SfxItemSet & /*rItemSet*/, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + int n=0; + while( n<2 && pExpr && !pExpr->GetOp() ) + { + switch( pExpr->GetType() ) + { + case CSS1_IDENT: + { + sal_uInt16 nValue; + if( SvxCSS1Parser::GetEnum( aSizeTable, pExpr->GetString(), + nValue ) ) + { + rPropInfo.m_eSizeType = static_cast<SvxCSS1SizeType>(nValue); + } + } + break; + + case CSS1_LENGTH: + rPropInfo.m_nHeight = pExpr->GetSLength(); + if( n==0 ) + rPropInfo.m_nWidth = rPropInfo.m_nHeight; + rPropInfo.m_eSizeType = SVX_CSS1_STYPE_TWIP; + break; + + case CSS1_PIXLENGTH: + { + double fHeight = pExpr->GetNumber(); + if (fHeight < SAL_MAX_INT32/2.0 && fHeight > SAL_MIN_INT32/2.0) + { + tools::Long nPHeight = static_cast<tools::Long>(fHeight); + tools::Long nPWidth = n==0 ? nPHeight : 0; + SvxCSS1Parser::PixelToTwip( nPWidth, nPHeight ); + rPropInfo.m_nHeight = nPHeight; + if( n==0 ) + rPropInfo.m_nWidth = nPWidth; + rPropInfo.m_eSizeType = SVX_CSS1_STYPE_TWIP; + } + break; + } + default: + ; + } + + pExpr = pExpr->GetNext(); + n++; + } +} + +static void ParseCSS1_page_break_xxx( const CSS1Expression *pExpr, + SvxCSS1PageBreak& rPBreak ) +{ + if( CSS1_IDENT == pExpr->GetType() ) + { + sal_uInt16 nValue; + if( SvxCSS1Parser::GetEnum( aPageBreakTable, pExpr->GetString(), + nValue ) ) + { + rPBreak = static_cast<SvxCSS1PageBreak>(nValue); + } + } +} + +static void ParseCSS1_page_break_before( const CSS1Expression *pExpr, + SfxItemSet & /*rItemSet*/, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + ParseCSS1_page_break_xxx( pExpr, rPropInfo.m_ePageBreakBefore ); +} + +static void ParseCSS1_page_break_after( const CSS1Expression *pExpr, + SfxItemSet & /*rItemSet*/, + SvxCSS1PropertyInfo& rPropInfo, + const SvxCSS1Parser& /*rParser*/ ) +{ + ParseCSS1_page_break_xxx( pExpr, rPropInfo.m_ePageBreakAfter ); +} + +static void ParseCSS1_page_break_inside( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& /*rPropInfo*/, + const SvxCSS1Parser& /*rParser*/ ) +{ + SvxCSS1PageBreak eBreak(SVX_CSS1_PBREAK_NONE); + ParseCSS1_page_break_xxx( pExpr, eBreak ); + + bool bSetSplit = false, bSplit = true; + switch( eBreak ) + { + case SVX_CSS1_PBREAK_AUTO: + bSetSplit = true; + break; + case SVX_CSS1_PBREAK_AVOID: + bSplit = false; + bSetSplit = true; + break; + default: + ; + } + + if( bSetSplit ) + rItemSet.Put( SvxFormatSplitItem( bSplit, aItemIds.nFormatSplit ) ); +} + +static void ParseCSS1_widows( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& /*rPropInfo*/, + const SvxCSS1Parser& /*rParser*/ ) +{ + if( CSS1_NUMBER == pExpr->GetType() ) + { + sal_uInt8 nVal = pExpr->GetNumber() <= 255 + ? static_cast<sal_uInt8>(pExpr->GetNumber()) + : 255; + SvxWidowsItem aWidowsItem( nVal, aItemIds.nWidows ); + rItemSet.Put( aWidowsItem ); + } +} + +static void ParseCSS1_orphans( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& /*rPropInfo*/, + const SvxCSS1Parser& /*rParser*/ ) +{ + if( CSS1_NUMBER == pExpr->GetType() ) + { + sal_uInt8 nVal = pExpr->GetNumber() <= 255 + ? static_cast<sal_uInt8>(pExpr->GetNumber()) + : 255; + SvxOrphansItem aOrphansItem( nVal, aItemIds.nOrphans ); + rItemSet.Put( aOrphansItem ); + } +} + +static void ParseCSS1_so_language( const CSS1Expression *pExpr, + SfxItemSet &rItemSet, + SvxCSS1PropertyInfo& /*rPropInfo*/, + const SvxCSS1Parser& /*rParser*/ ) +{ + if( CSS1_IDENT != pExpr->GetType() && CSS1_STRING != pExpr->GetType() ) + return; + + LanguageType eLang = LanguageTag::convertToLanguageTypeWithFallback( pExpr->GetString() ); + if( LANGUAGE_DONTKNOW != eLang ) + { + SvxLanguageItem aLang( eLang, aItemIds.nLanguage ); + rItemSet.Put( aLang ); + aLang.SetWhich( aItemIds.nLanguageCJK ); + rItemSet.Put( aLang ); + aLang.SetWhich( aItemIds.nLanguageCTL ); + rItemSet.Put( aLang ); + } +} + +static void ParseCSS1_visibility(const CSS1Expression* pExpr, SfxItemSet& /*rItemSet*/, + SvxCSS1PropertyInfo& rPropInfo, const SvxCSS1Parser& /*rParser*/) +{ + if (pExpr->GetType() != CSS1_IDENT) + return; + + rPropInfo.m_bVisible = pExpr->GetString() != "hidden"; +} + +static void ParseCSS1_white_space(const CSS1Expression* pExpr, SfxItemSet& /*rItemSet*/, + SvxCSS1PropertyInfo& rPropInfo, const SvxCSS1Parser& /*rParser*/) +{ + if (pExpr->GetType() == CSS1_IDENT) + { + if (pExpr->GetString().equalsIgnoreAsciiCase("pre") + || pExpr->GetString().equalsIgnoreAsciiCase("pre-wrap")) + { + rPropInfo.m_bPreserveSpace = true; + } + } +} + +namespace { + +// the assignment of property to parsing function +struct CSS1PropEntry +{ + std::string_view pName; + FnParseCSS1Prop pFunc; +}; + +} + +// the table with assignments +CSS1PropEntry constexpr aCSS1PropFnTab[] = +{ + { sCSS1_P_background, ParseCSS1_background }, + { sCSS1_P_background_color, ParseCSS1_background_color }, + { sCSS1_P_border, ParseCSS1_border }, + { sCSS1_P_border_bottom, ParseCSS1_border_bottom }, + { sCSS1_P_border_bottom_width, ParseCSS1_border_bottom_width }, + { sCSS1_P_border_color, ParseCSS1_border_color }, + { sCSS1_P_border_left, ParseCSS1_border_left }, + { sCSS1_P_border_left_width, ParseCSS1_border_left_width }, + { sCSS1_P_border_right, ParseCSS1_border_right }, + { sCSS1_P_border_right_width, ParseCSS1_border_right_width }, + { sCSS1_P_border_style, ParseCSS1_border_style }, + { sCSS1_P_border_top, ParseCSS1_border_top }, + { sCSS1_P_border_top_width, ParseCSS1_border_top_width }, + { sCSS1_P_border_width, ParseCSS1_border_width }, + { sCSS1_P_color, ParseCSS1_color }, + { sCSS1_P_column_count, ParseCSS1_column_count }, + { sCSS1_P_direction, ParseCSS1_direction }, + { sCSS1_P_float, ParseCSS1_float }, + { sCSS1_P_font, ParseCSS1_font }, + { sCSS1_P_font_family, ParseCSS1_font_family }, + { sCSS1_P_font_size, ParseCSS1_font_size }, + { sCSS1_P_font_style, ParseCSS1_font_style }, + { sCSS1_P_font_variant, ParseCSS1_font_variant }, + { sCSS1_P_font_weight, ParseCSS1_font_weight }, + { sCSS1_P_height, ParseCSS1_height }, + { sCSS1_P_left, ParseCSS1_left }, + { sCSS1_P_letter_spacing, ParseCSS1_letter_spacing }, + { sCSS1_P_line_height, ParseCSS1_line_height }, + { sCSS1_P_list_style_type, ParseCSS1_list_style_type }, + { sCSS1_P_margin, ParseCSS1_margin }, + { sCSS1_P_margin_bottom, ParseCSS1_margin_bottom }, + { sCSS1_P_margin_left, ParseCSS1_margin_left }, + { sCSS1_P_margin_right, ParseCSS1_margin_right }, + { sCSS1_P_margin_top, ParseCSS1_margin_top }, + { sCSS1_P_orphans, ParseCSS1_orphans }, + { sCSS1_P_padding, ParseCSS1_padding }, + { sCSS1_P_padding_bottom, ParseCSS1_padding_bottom }, + { sCSS1_P_padding_left, ParseCSS1_padding_left }, + { sCSS1_P_padding_right, ParseCSS1_padding_right }, + { sCSS1_P_padding_top, ParseCSS1_padding_top }, + { sCSS1_P_page_break_after, ParseCSS1_page_break_after }, + { sCSS1_P_page_break_before, ParseCSS1_page_break_before }, + { sCSS1_P_page_break_inside, ParseCSS1_page_break_inside }, + { sCSS1_P_position, ParseCSS1_position }, + { sCSS1_P_size, ParseCSS1_size }, + { sCSS1_P_so_language, ParseCSS1_so_language }, + { sCSS1_P_text_align, ParseCSS1_text_align }, + { sCSS1_P_text_decoration, ParseCSS1_text_decoration }, + { sCSS1_P_text_indent, ParseCSS1_text_indent }, + { sCSS1_P_text_transform, ParseCSS1_text_transform }, + { sCSS1_P_top, ParseCSS1_top }, + { sCSS1_P_visibility, ParseCSS1_visibility }, + { sCSS1_white_space, ParseCSS1_white_space }, + { sCSS1_P_widows, ParseCSS1_widows }, + { sCSS1_P_width, ParseCSS1_width }, +}; + +static_assert(std::is_sorted(std::begin(aCSS1PropFnTab), std::end(aCSS1PropFnTab), + [](const auto& lhs, const auto& rhs) constexpr + { return lhs.pName < rhs.pName; })); + +static bool CSS1PropEntryFindCompare(CSS1PropEntry const & lhs, OUString const & s) +{ + return s.compareToIgnoreAsciiCaseAscii(lhs.pName) > 0; +} + +void SvxCSS1Parser::DeclarationParsed( const OUString& rProperty, + std::unique_ptr<CSS1Expression> pExpr ) +{ + OSL_ENSURE( m_pItemSet, "DeclarationParsed() without ItemSet" ); + + auto it = std::lower_bound( std::begin(aCSS1PropFnTab), std::end(aCSS1PropFnTab), rProperty, + CSS1PropEntryFindCompare ); + if( it != std::end(aCSS1PropFnTab) && !CSS1PropEntryFindCompare(*it,rProperty) ) + { + it->pFunc( pExpr.get(), *m_pItemSet, *m_pPropInfo, *this ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/svxcss1.hxx b/sw/source/filter/html/svxcss1.hxx new file mode 100644 index 0000000000..669ed92a5b --- /dev/null +++ b/sw/source/filter/html/svxcss1.hxx @@ -0,0 +1,314 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_FILTER_HTML_SVXCSS1_HXX +#define INCLUDED_SW_SOURCE_FILTER_HTML_SVXCSS1_HXX + +#include <svl/itemset.hxx> +#include <editeng/svxenum.hxx> +#include <rtl/textenc.h> +#include "parcss1.hxx" +#include <o3tl/typed_flags_set.hxx> +#include <o3tl/unit_conversion.hxx> + +#include <array> +#include <map> +#include <memory> +#include <vector> + +class SfxItemPool; +class SvxBoxItem; +class FontList; +enum class SvxBoxItemLine; + +enum SvxCSS1Position +{ + SVX_CSS1_POS_NONE, // nothing specified + SVX_CSS1_POS_STATIC, // normal + SVX_CSS1_POS_ABSOLUTE, // absolute + SVX_CSS1_POS_RELATIVE, // relative +}; + +enum SvxCSS1LengthType +{ + SVX_CSS1_LTYPE_NONE, // nothing specified + SVX_CSS1_LTYPE_AUTO, // automatic + SVX_CSS1_LTYPE_TWIP, // twip + SVX_CSS1_LTYPE_PERCENTAGE, // percentage value +}; + +// Feature: PrintExt +enum SvxCSS1SizeType +{ + SVX_CSS1_STYPE_NONE, // nothing specified + SVX_CSS1_STYPE_AUTO, // automatic + SVX_CSS1_STYPE_TWIP, // twip + SVX_CSS1_STYPE_LANDSCAPE, // landscape + SVX_CSS1_STYPE_PORTRAIT, // portrait +}; + +enum SvxCSS1PageBreak +{ + SVX_CSS1_PBREAK_NONE, // nothing specified + SVX_CSS1_PBREAK_AUTO, // automatic + SVX_CSS1_PBREAK_ALWAYS, // always + SVX_CSS1_PBREAK_AVOID, // never + SVX_CSS1_PBREAK_LEFT, // next page is a left one + SVX_CSS1_PBREAK_RIGHT, // next page is a right one +}; + + +enum class Css1ScriptFlags { + Western = 0x01, + CJK = 0x02, + CTL = 0x04, + AllMask = Western | CJK | CTL, +}; +namespace o3tl { + template<> struct typed_flags<Css1ScriptFlags> : is_typed_flags<Css1ScriptFlags, 0x07> {}; +} + +struct CSS1PropertyEnum +{ + const char *pName; // property value + sal_uInt16 nEnum; // and the corresponding value of enum +}; + +namespace editeng { class SvxBorderLine; } + +#define SVX_CSS1_BORDERINFO_WIDTH 1 +#define SVX_CSS1_BORDERINFO_COLOR 2 +#define SVX_CSS1_BORDERINFO_STYLE 4 + +struct SvxCSS1BorderInfo; +class SvxCSS1PropertyInfo +{ + std::array<std::unique_ptr<SvxCSS1BorderInfo>,4> m_aBorderInfos; + + void DestroyBorderInfos(); + +public: + static constexpr sal_uInt16 UNSET_BORDER_DISTANCE = SAL_MAX_UINT16; + + OUString m_aId; // ID for bookmarks, frame, and so + + bool m_bTopMargin : 1; + bool m_bBottomMargin : 1; + + bool m_bLeftMargin : 1; + bool m_bRightMargin : 1; + bool m_bTextIndent : 1; + bool m_bNumbering : 1; + bool m_bBullet : 1; + bool m_bPreserveSpace : 1 = false; + + SvxAdjust m_eFloat; + + SvxCSS1Position m_ePosition; + + sal_uInt16 m_nTopBorderDistance; + sal_uInt16 m_nBottomBorderDistance; + sal_uInt16 m_nLeftBorderDistance; + sal_uInt16 m_nRightBorderDistance; + + SvxNumType m_nNumberingType; + sal_Unicode m_cBulletChar; + + sal_uInt16 m_nColumnCount; + + tools::Long m_nLeft, m_nTop; + tools::Long m_nWidth, m_nHeight; + tools::Long m_nLeftMargin, m_nRightMargin; + + SvxCSS1LengthType m_eLeftType, m_eTopType; + SvxCSS1LengthType m_eWidthType, m_eHeightType; + SvxCSS1LengthType m_eLeftMarginType; + SvxCSS1LengthType m_eRightMarginType; + + SvxCSS1SizeType m_eSizeType; + + SvxCSS1PageBreak m_ePageBreakBefore; + SvxCSS1PageBreak m_ePageBreakAfter; + + bool m_bVisible = true; + + SvxCSS1PropertyInfo(); + SvxCSS1PropertyInfo( const SvxCSS1PropertyInfo& rProp ); + ~SvxCSS1PropertyInfo(); + + void Merge( const SvxCSS1PropertyInfo& rProp ); + + void Clear(); + + SvxCSS1BorderInfo *GetBorderInfo( SvxBoxItemLine nLine, bool bCreate=true ); + void CopyBorderInfo( SvxBoxItemLine nSrcLine, SvxBoxItemLine nDstLine, sal_uInt16 nWhat ); + void CopyBorderInfo( sal_uInt16 nCount, sal_uInt16 nWhat ); + + void SetBoxItem( SfxItemSet& rItemSet, sal_uInt16 nMinBorderDist, + const SvxBoxItem* pDflt=nullptr ); + +}; + +class SvxCSS1MapEntry +{ + SfxItemSet m_aItemSet; + SvxCSS1PropertyInfo m_aPropInfo; + +public: + SvxCSS1MapEntry( SfxItemSet aItemSet, + const SvxCSS1PropertyInfo& rProp ); + + const SfxItemSet& GetItemSet() const { return m_aItemSet; } + SfxItemSet& GetItemSet() { return m_aItemSet; } + + const SvxCSS1PropertyInfo& GetPropertyInfo() const { return m_aPropInfo; } + SvxCSS1PropertyInfo& GetPropertyInfo() { return m_aPropInfo; } +}; + +// Class is processing the CSS1-Parser output by converting the CSS1 properties +// into SvxItem(Set). Also the selectors together with associated ItemSet are +// saved. +// A derived parser can suppress this for certain selectors by overriding +// the method StyleParsed. + +class SvxCSS1Parser : public CSS1Parser +{ + typedef std::vector<std::unique_ptr<CSS1Selector>> CSS1Selectors; + typedef std::map<OUString, std::unique_ptr<SvxCSS1MapEntry>> CSS1Map; + CSS1Selectors m_Selectors; // List of "open" Selectors + + CSS1Map m_Ids; + CSS1Map m_Classes; + CSS1Map m_Pages; + CSS1Map m_Tags; + + OUString m_sBaseURL; + + std::unique_ptr<SfxItemSet> m_pSheetItemSet; // item set of Style-Sheet + SfxItemSet *m_pItemSet; // current item set + + std::unique_ptr<SvxCSS1PropertyInfo> m_pSheetPropInfo; + SvxCSS1PropertyInfo *m_pPropInfo; + + // minimum spacing for fixed line spacing + static constexpr sal_uInt16 gnMinFixLineSpace = o3tl::toTwips(25, o3tl::Length::mm10); + + rtl_TextEncoding m_eDefaultEnc; + bool m_bIgnoreFontFamily; + WhichRangesContainer m_aWhichMap; // Which-Map of Parser + + using CSS1Parser::ParseStyleOption; + +protected: + + using CSS1Parser::ParseStyleSheet; + + // This method is called for every selector with according item set. + // For a selector multiple calls are possible. + // If true is returned then the item set resp. the selector isn't saved anymore! + // The ItemSet may be modified accordingly! + // The implementation returns false. + virtual void StyleParsed( const CSS1Selector *pSelector, + SfxItemSet& rItemSet, + SvxCSS1PropertyInfo& rPropInfo ); + + /// Will be called when a Selector is parsed. If bFirst is true, + /// the content of the aItemSet will be copied into all recently + /// created Styles. + /// Derived classes should not override this method! + virtual void SelectorParsed( std::unique_ptr<CSS1Selector> pSelector, bool bFirst ) override; + + /// Will be called for every parsed Property. Adds the item to the + /// pItemSet. + /// Derived classes should not override this method! + virtual void DeclarationParsed( const OUString& rProperty, + std::unique_ptr<CSS1Expression> pExpr ) override; + +public: + + SvxCSS1Parser( SfxItemPool& rPool, + OUString aBaseURL, + sal_uInt16 const *pWhichIds, sal_uInt16 nWhichIds ); + virtual ~SvxCSS1Parser() override; + + bool IsIgnoreFontFamily() const { return m_bIgnoreFontFamily; } + void SetIgnoreFontFamily( bool bSet ) { m_bIgnoreFontFamily = bSet; } + + // Parse a style sheet. For every found selector a StyleParsed with + // according item set is called. + virtual bool ParseStyleSheet( const OUString& rIn ); + + // Parse style option. Here only the item set is filled. + void ParseStyleOption( const OUString& rIn, SfxItemSet& rItemSet, + SvxCSS1PropertyInfo& rPropInfo ); + + // convert a string to enum value + static bool GetEnum( const CSS1PropertyEnum *pPropTable, + std::u16string_view rValue, sal_uInt16 &rEnum ); + + static void PixelToTwip( tools::Long &nWidth, tools::Long &nHeight ); + + // determine the font height of a certain font size (0-6) + virtual sal_uInt32 GetFontHeight( sal_uInt16 nSize ) const; + + virtual const FontList *GetFontList() const; + + const WhichRangesContainer& GetWhichMap() const { return m_aWhichMap; } + + static void InsertMapEntry( const OUString& rKey, const SfxItemSet& rItemSet, + const SvxCSS1PropertyInfo& rProp, CSS1Map& rMap ); + + void InsertId( const OUString& rId, const SfxItemSet& rItemSet, + const SvxCSS1PropertyInfo& rProp ); + + const SvxCSS1MapEntry* GetId( const OUString& rId ) const; + + void InsertClass( const OUString& rClass, const SfxItemSet& rItemSet, + const SvxCSS1PropertyInfo& rProp ); + + const SvxCSS1MapEntry* GetClass( const OUString& rClass ) const; + + void InsertPage( const OUString& rPage, bool bPseudo, + const SfxItemSet& rItemSet, + const SvxCSS1PropertyInfo& rProp ); + + SvxCSS1MapEntry* GetPage( const OUString& rPage, bool bPseudo ); + + void InsertTag( const OUString& rTag, const SfxItemSet& rItemSet, + const SvxCSS1PropertyInfo& rProp ); + + SvxCSS1MapEntry* GetTag( const OUString& rTag ); + + static void MergeStyles( const SfxItemSet& rSrcSet, + const SvxCSS1PropertyInfo& rSrcInfo, + SfxItemSet& rTargetSet, + SvxCSS1PropertyInfo& rTargetInfo, + bool bSmart ); + + static sal_uInt16 GetMinFixLineSpace() { return gnMinFixLineSpace; } + + virtual void SetDfltEncoding( rtl_TextEncoding eEnc ); + rtl_TextEncoding GetDfltEncoding() const { return m_eDefaultEnc; } + + const OUString& GetBaseURL() const { return m_sBaseURL;} + +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/swcss1.hxx b/sw/source/filter/html/swcss1.hxx new file mode 100644 index 0000000000..9d930f6bfc --- /dev/null +++ b/sw/source/filter/html/swcss1.hxx @@ -0,0 +1,210 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_FILTER_HTML_SWCSS1_HXX +#define INCLUDED_SW_SOURCE_FILTER_HTML_SWCSS1_HXX + +#include <sal/config.h> + +#include <string_view> + +#include <svtools/htmltokn.h> +#include <tools/solar.h> + +#include <poolfmt.hxx> + +#include "svxcss1.hxx" + +class SwDoc; +class SwCharFormat; +class SwTextFormatColl; +class SvxBrushItem; +class SwFormatDrop; +class SwPageDesc; +class SwHTMLParser; + +// This header looks harmless, but includes still quite +// inconspicuous one or the other! On the other hand this class +// is rarely needed. Therefore its own header. + +class SwCSS1Parser : public SvxCSS1Parser +{ + SwDoc *m_pDoc; + SwHTMLParser const& m_rHTMLParser; + + sal_uLong m_aFontHeights[7]; + + sal_uInt16 m_nDropCapCnt; + + bool m_bIsNewDoc : 1; + + bool m_bBodyBGColorSet : 1; + bool m_bBodyBackgroundSet : 1; + bool m_bBodyTextSet : 1; + bool m_bBodyLinkSet : 1; + bool m_bBodyVLinkSet : 1; + + bool m_bSetFirstPageDesc : 1; + bool m_bSetRightPageDesc : 1; + + bool m_bTableHeaderTextCollSet : 1; + bool m_bTableTextCollSet : 1; + + bool m_bLinkCharFormatsSet : 1; + + const SwPageDesc* GetPageDesc( sal_uInt16 nPoolId, bool bCreate ); + + void SetTableTextColl( bool bHeader ); + void SetLinkCharFormats(); + +protected: + virtual void StyleParsed( const CSS1Selector *pSelector, + SfxItemSet& rItemSet, + SvxCSS1PropertyInfo& rPropInfo ) override; + + using CSS1Parser::ParseStyleSheet; + +public: + SwCSS1Parser( SwDoc *pDoc, SwHTMLParser const& rParser, + sal_uInt32 const aFHeight[7], const OUString& rBaseURL, bool bNewDoc); + virtual ~SwCSS1Parser() override; + + virtual bool ParseStyleSheet( const OUString& rIn ) override; + + // determine font height for a certain font size (0-6) + virtual sal_uInt32 GetFontHeight( sal_uInt16 nSize ) const override; + + // fetch current font list (also zero is allowed) + virtual const FontList *GetFontList() const override; + + // determine the character format of a token and a maybe empty class + SwCharFormat* GetChrFormat( HtmlTokenId nToken, const OUString& rClass ) const; + + // determine a TextFormatColl of a Pool-Id + SwTextFormatColl *GetTextFormatColl( sal_uInt16 nTextColl, const OUString& rClass ); + + // This methods do the same as the one of SwDoc, but change the + // encoding if required. + SwTextFormatColl *GetTextCollFromPool( sal_uInt16 nPoolId ) const; + SwCharFormat *GetCharFormatFromPool( sal_uInt16 nPoolId ) const; + + // Fetch the left or right page style. In documents with only + // one style there is only a right page. + // Otherwise the right page is the HTML pool style and the left + // page a user style which is created on-demand if bCreate is set. + SwPageDesc* GetMasterPageDesc(); + inline const SwPageDesc* GetFirstPageDesc( bool bCreate=false ); + inline const SwPageDesc* GetRightPageDesc( bool bCreate=false ); + inline const SwPageDesc* GetLeftPageDesc( bool bCreate=false ); + + // Set attributes on the HTML page style (set attributes are + // deleted from the Item-Set). Is called for the BODY tag. + void SetPageDescAttrs( const SvxBrushItem *pBrush, + SfxItemSet *pItemSet=nullptr ); + + void ChgPageDesc( const SwPageDesc *pPageDesc, + const SwPageDesc& rNewPageDesc ); + + // Is called for @page + void SetPageDescAttrs( const SwPageDesc *pPageDesc, SfxItemSet& rItemSet, + const SvxCSS1PropertyInfo& rPropInfo ); + + // Fill a DropCap attribute + void FillDropCap( SwFormatDrop& rDrop, SfxItemSet& rItemSet, + const OUString *pName=nullptr ); + + bool SetFormatBreak( SfxItemSet& rItemSet, + const SvxCSS1PropertyInfo& rPropInfo ); + + static void AddClassName( OUString& rFormatName, std::u16string_view rClass ); + + static bool MayBePositioned( const SvxCSS1PropertyInfo& rPropInfo, + bool bAutoWidth=false ); + + static Css1ScriptFlags GetScriptFromClass( OUString& rClass, + bool bSubClassOnly = true ); + + bool IsBodyBGColorSet() const { return m_bBodyBGColorSet; } + bool IsBodyBackgroundSet() const { return m_bBodyBackgroundSet; } + bool IsBodyTextSet() const { return m_bBodyTextSet; } + bool IsBodyLinkSet() const { return m_bBodyLinkSet; } + bool IsBodyVLinkSet() const { return m_bBodyVLinkSet; } + + bool IsSetFirstPageDesc() const { return m_bSetFirstPageDesc; } + bool IsSetRightPageDesc() const { return m_bSetRightPageDesc; } + + void SetBodyBGColorSet() { m_bBodyBGColorSet = true; } + void SetBodyBackgroundSet() { m_bBodyBackgroundSet = true; } + void SetBodyTextSet() { m_bBodyTextSet = true; } + void SetBodyLinkSet() { m_bBodyLinkSet = true; } + void SetBodyVLinkSet() { m_bBodyVLinkSet = true; } + + std::unique_ptr<SvxBrushItem> makePageDescBackground() const; + + inline void SetTHTagStyles(); + inline void SetTDTagStyles(); + inline void SetATagStyles(); + inline void SetDelayedStyles(); + + virtual void SetDfltEncoding( rtl_TextEncoding eEnc ) override; +}; + +inline const SwPageDesc* SwCSS1Parser::GetFirstPageDesc( bool bCreate ) +{ + return GetPageDesc( RES_POOLPAGE_FIRST, bCreate ); +} + +inline const SwPageDesc* SwCSS1Parser::GetRightPageDesc( bool bCreate ) +{ + return GetPageDesc( RES_POOLPAGE_RIGHT, bCreate ); +} + +inline const SwPageDesc* SwCSS1Parser::GetLeftPageDesc( bool bCreate ) +{ + return GetPageDesc( RES_POOLPAGE_LEFT, bCreate ); +} + +inline void SwCSS1Parser::SetTHTagStyles() +{ + if( !m_bTableHeaderTextCollSet ) + SetTableTextColl( true ); +} + +inline void SwCSS1Parser::SetTDTagStyles() +{ + if( !m_bTableTextCollSet ) + SetTableTextColl( false ); +} + +inline void SwCSS1Parser::SetATagStyles() +{ + if( !m_bLinkCharFormatsSet ) + SetLinkCharFormats(); +} + +inline void SwCSS1Parser::SetDelayedStyles() +{ + SetTHTagStyles(); + SetTDTagStyles(); + SetATagStyles(); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/swhtml.cxx b/sw/source/filter/html/swhtml.cxx new file mode 100644 index 0000000000..072d6a2b23 --- /dev/null +++ b/sw/source/filter/html/swhtml.cxx @@ -0,0 +1,5648 @@ +/* -*- 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 <algorithm> +#include <memory> +#include <config_java.h> + +#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> +#include <com/sun/star/document/XDocumentProperties.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <comphelper/string.hxx> +#include <o3tl/safeint.hxx> +#include <rtl/ustrbuf.hxx> +#include <svx/svxids.hrc> +#include <svx/svdotext.hxx> +#if OSL_DEBUG_LEVEL > 0 +#include <stdlib.h> +#endif +#include <hintids.hxx> + +#include <utility> +#include <vcl/errinf.hxx> +#include <svl/stritem.hxx> +#include <vcl/imap.hxx> +#include <svtools/htmltokn.h> +#include <svtools/htmlkywd.hxx> +#include <svtools/ctrltool.hxx> +#include <unotools/pathoptions.hxx> +#include <vcl/svapp.hxx> +#include <sfx2/event.hxx> +#include <sfx2/docfile.hxx> + +#include <sfx2/linkmgr.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/formatbreakitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/blinkitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/protitem.hxx> +#include <editeng/flstitem.hxx> +#include <svx/unobrushitemhelper.hxx> + +#include <frmatr.hxx> +#include <charatr.hxx> +#include <fmtfld.hxx> +#include <fmtpdsc.hxx> +#include <fmtanchr.hxx> +#include <fmtsrnd.hxx> +#include <fmtfsize.hxx> +#include <fmtclds.hxx> +#include <fchrfmt.hxx> +#include <fmtinfmt.hxx> +#include <fmtfollowtextflow.hxx> +#include <fmtornt.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentLinksAdministration.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <IDocumentStatistics.hxx> +#include <IDocumentState.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <mdiexp.hxx> +#include <poolfmt.hxx> +#include <pagedesc.hxx> +#include <IMark.hxx> +#include <docsh.hxx> +#include <editsh.hxx> +#include <docufld.hxx> +#include "swcss1.hxx" +#include <fltini.hxx> +#include <htmltbl.hxx> +#include "htmlnum.hxx" +#include "swhtml.hxx" +#include "wrthtml.hxx" +#include <linkenum.hxx> +#include <breakit.hxx> +#include <SwAppletImpl.hxx> +#include <swdll.hxx> +#include <txatbase.hxx> + +#include <sfx2/viewfrm.hxx> +#include <svx/svdobj.hxx> +#include <officecfg/Office/Writer.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <comphelper/sequence.hxx> +#include <officecfg/Office/Common.hxx> + +#include <swerror.h> +#include <ndole.hxx> +#include <unoframe.hxx> +#include "css1atr.hxx" +#include <frameformats.hxx> + +#define FONTSIZE_MASK 7 + +#define HTML_ESC_PROP 80 +#define HTML_ESC_SUPER DFLT_ESC_SUPER +#define HTML_ESC_SUB DFLT_ESC_SUB + +#define HTML_SPTYPE_BLOCK 1 +#define HTML_SPTYPE_HORI 2 +#define HTML_SPTYPE_VERT 3 + +using editeng::SvxBorderLine; +using namespace ::com::sun::star; + +// <P ALIGN=xxx>, <Hn ALIGN=xxx>, <TD ALIGN=xxx> etc. +HTMLOptionEnum<SvxAdjust> const aHTMLPAlignTable[] = +{ + { OOO_STRING_SVTOOLS_HTML_AL_left, SvxAdjust::Left }, + { OOO_STRING_SVTOOLS_HTML_AL_center, SvxAdjust::Center }, + { OOO_STRING_SVTOOLS_HTML_AL_middle, SvxAdjust::Center }, // Netscape + { OOO_STRING_SVTOOLS_HTML_AL_right, SvxAdjust::Right }, + { OOO_STRING_SVTOOLS_HTML_AL_justify, SvxAdjust::Block }, + { OOO_STRING_SVTOOLS_HTML_AL_char, SvxAdjust::Left }, + { nullptr, SvxAdjust(0) } +}; + +// <SPACER TYPE=...> +HTMLOptionEnum<sal_uInt16> const aHTMLSpacerTypeTable[] = +{ + { OOO_STRING_SVTOOLS_HTML_SPTYPE_block, HTML_SPTYPE_BLOCK }, + { OOO_STRING_SVTOOLS_HTML_SPTYPE_horizontal, HTML_SPTYPE_HORI }, + { OOO_STRING_SVTOOLS_HTML_SPTYPE_vertical, HTML_SPTYPE_VERT }, + { nullptr, 0 } +}; + +HTMLReader::HTMLReader() +{ + m_bTemplateBrowseMode = true; +} + +OUString HTMLReader::GetTemplateName(SwDoc& rDoc) const +{ + if (!rDoc.getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE)) + // HTML import into Writer, avoid loading the Writer/Web template. + return OUString(); + + static constexpr OUString sTemplateWithoutExt(u"internal/html"_ustr); + SvtPathOptions aPathOpt; + + // first search for OpenDocument Writer/Web template + // OpenDocument Writer/Web template (extension .oth) + OUString sTemplate( sTemplateWithoutExt + ".oth" ); + if (aPathOpt.SearchFile( sTemplate, SvtPathOptions::Paths::Template )) + return sTemplate; + + // no OpenDocument Writer/Web template found. + // search for OpenOffice.org Writer/Web template + sTemplate = sTemplateWithoutExt + ".stw"; + if (aPathOpt.SearchFile( sTemplate, SvtPathOptions::Paths::Template )) + return sTemplate; + + OSL_ENSURE( false, "The default HTML template cannot be found in the defined template directories!"); + + return OUString(); +} + +bool HTMLReader::SetStrmStgPtr() +{ + OSL_ENSURE( m_pMedium, "Where is the medium??" ); + + if( m_pMedium->IsRemote() || !m_pMedium->IsStorage() ) + { + m_pStream = m_pMedium->GetInStream(); + return true; + } + return false; + +} + +// Call for the general Reader-Interface +ErrCodeMsg HTMLReader::Read( SwDoc &rDoc, const OUString& rBaseURL, SwPaM &rPam, const OUString & rName ) +{ + SetupFilterOptions(); + + if( !m_pStream ) + { + OSL_ENSURE( m_pStream, "HTML-Read without stream" ); + return ERR_SWG_READ_ERROR; + } + + if( !m_bInsertMode ) + { + Reader::ResetFrameFormats( rDoc ); + + // Set the HTML page style, when it isn't a HTML document, + // otherwise it's already set. + if( !rDoc.getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE) && m_aNamespace != "reqif-xhtml" ) + { + rDoc.getIDocumentContentOperations().InsertPoolItem( rPam, SwFormatPageDesc( + rDoc.getIDocumentStylePoolAccess().GetPageDescFromPool( RES_POOLPAGE_HTML, false )) ); + } + } + + // so nobody steals the document! + rtl::Reference<SwDoc> xHoldAlive(&rDoc); + ErrCodeMsg nRet = ERRCODE_NONE; + tools::SvRef<SwHTMLParser> xParser = new SwHTMLParser( &rDoc, rPam, *m_pStream, + rName, rBaseURL, !m_bInsertMode, m_pMedium, + IsReadUTF8(), + m_bIgnoreHTMLComments, m_aNamespace ); + + SvParserState eState = xParser->CallParser(); + + if( SvParserState::Pending == eState ) + m_pStream->ResetError(); + else if( SvParserState::Accepted != eState ) + { + const OUString sErr(OUString::number(static_cast<sal_Int32>(xParser->GetLineNr())) + + "," + OUString::number(static_cast<sal_Int32>(xParser->GetLinePos()))); + + // use the stream as transport for error number + nRet = ErrCodeMsg( ERR_FORMAT_ROWCOL, sErr, + DialogMask::ButtonsOk | DialogMask::MessageError ); + } + + return nRet; +} + +SwHTMLParser::SwHTMLParser( SwDoc* pD, SwPaM& rCursor, SvStream& rIn, + OUString aPath, + OUString aBaseURL, + bool bReadNewDoc, + SfxMedium* pMed, bool bReadUTF8, + bool bNoHTMLComments, + const OUString& rNamespace ) + : SfxHTMLParser( rIn, bReadNewDoc, pMed ), + m_aPathToFile(std::move( aPath )), + m_sBaseURL(std::move( aBaseURL )), + m_xAttrTab(std::make_shared<HTMLAttrTable>()), + m_pNumRuleInfo( new SwHTMLNumRuleInfo ), + m_xDoc( pD ), + m_pActionViewShell( nullptr ), + m_pSttNdIdx( nullptr ), + m_pFormImpl( nullptr ), + m_pImageMap( nullptr ), + m_nBaseFontStMin( 0 ), + m_nFontStMin( 0 ), + m_nDefListDeep( 0 ), + m_nFontStHeadStart( 0 ), + m_nSBModuleCnt( 0 ), + m_nMissingImgMaps( 0 ), + m_nParaCnt( 5 ), + // #i83625# + m_nContextStMin( 0 ), + m_nContextStAttrMin( 0 ), + m_nSelectEntryCnt( 0 ), + m_nOpenParaToken( HtmlTokenId::NONE ), + m_eJumpTo( JumpToMarks::NONE ), +#ifdef DBG_UTIL + m_nContinue( 0 ), +#endif + m_eParaAdjust( SvxAdjust::End ), + m_bDocInitialized( false ), + m_bSetModEnabled( false ), + m_bInFloatingFrame( false ), + m_bInField( false ), + m_bKeepUnknown( false ), + m_bCallNextToken( false ), + m_bIgnoreRawData( false ), + m_bLBEntrySelected ( false ), + m_bTAIgnoreNewPara ( false ), + m_bFixMarqueeWidth ( false ), + m_bNoParSpace( false ), + m_bInNoEmbed( false ), + m_bInTitle( false ), + m_bUpdateDocStat( false ), + m_bFixSelectWidth( false ), + m_bTextArea( false ), + m_bSelect( false ), + m_bInFootEndNoteAnchor( false ), + m_bInFootEndNoteSymbol( false ), + m_bIgnoreHTMLComments( bNoHTMLComments ), + m_bRemoveHidden( false ), + m_bBodySeen( false ), + m_bReadingHeaderOrFooter( false ), + m_bNotifyMacroEventRead( false ), + m_isInTableStructure(false), + m_nTableDepth( 0 ), + m_nFloatingFrames( 0 ), + m_nListItems( 0 ), + m_pTempViewFrame(nullptr) +{ + // If requested explicitly, then force ignoring of comments (don't create postits for them). + if (!bFuzzing) + { + if (officecfg::Office::Writer::Filter::Import::HTML::IgnoreComments::get()) + m_bIgnoreHTMLComments = true; + m_bKeepUnknown = officecfg::Office::Common::Filter::HTML::Import::UnknownTag::get(); + } + + m_nEventId = nullptr; + m_bUpperSpace = m_bViewCreated = m_bChkJumpMark = false; + + m_eScriptLang = HTMLScriptLanguage::Unknown; + + rCursor.DeleteMark(); + m_pPam = &rCursor; // re-use existing cursor: avoids spurious ~SwContentIndexReg assert + memset(m_xAttrTab.get(), 0, sizeof(HTMLAttrTable)); + + // Read the font sizes 1-7 from the INI file + if (!bFuzzing) + { + m_aFontHeights[0] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_1::get() * 20; + m_aFontHeights[1] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_2::get() * 20; + m_aFontHeights[2] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_3::get() * 20; + m_aFontHeights[3] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_4::get() * 20; + m_aFontHeights[4] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_5::get() * 20; + m_aFontHeights[5] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_6::get() * 20; + m_aFontHeights[6] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_7::get() * 20; + } + else + { + m_aFontHeights[0] = m_aFontHeights[1] = m_aFontHeights[2] = m_aFontHeights[3] = + m_aFontHeights[4] = m_aFontHeights[5] = m_aFontHeights[6] = 12 * 20; + } + + if(bReadNewDoc) + { + //CJK has different defaults, so a different object should be used for this + //RES_CHARTR_CJK_FONTSIZE is a valid value + SvxFontHeightItem aFontHeight(m_aFontHeights[2], 100, RES_CHRATR_FONTSIZE); + m_xDoc->SetDefault( aFontHeight ); + SvxFontHeightItem aFontHeightCJK(m_aFontHeights[2], 100, RES_CHRATR_CJK_FONTSIZE); + m_xDoc->SetDefault( aFontHeightCJK ); + SvxFontHeightItem aFontHeightCTL(m_aFontHeights[2], 100, RES_CHRATR_CTL_FONTSIZE); + m_xDoc->SetDefault( aFontHeightCTL ); + + // #i18732# - adjust default of option 'FollowTextFlow' + // TODO: not sure what the appropriate default for HTML should be? + m_xDoc->SetDefault( SwFormatFollowTextFlow(true) ); + } + + // Change to HTML mode during the import, so that the right styles are created + m_bOldIsHTMLMode = m_xDoc->getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE); + m_xDoc->getIDocumentSettingAccess().set(DocumentSettingId::HTML_MODE, true); + + m_pCSS1Parser.reset(new SwCSS1Parser(m_xDoc.get(), *this, m_aFontHeights, m_sBaseURL, IsNewDoc())); + if (!bFuzzing) + m_pCSS1Parser->SetIgnoreFontFamily( officecfg::Office::Common::Filter::HTML::Import::FontSetting::get() ); + + if( bReadUTF8 ) + { + SetSrcEncoding( RTL_TEXTENCODING_UTF8 ); + } + else + { + SwDocShell *pDocSh = m_xDoc->GetDocShell(); + SvKeyValueIterator *pHeaderAttrs = + pDocSh->GetHeaderAttributes(); + if( pHeaderAttrs ) + SetEncodingByHTTPHeader( pHeaderAttrs ); + } + m_pCSS1Parser->SetDfltEncoding( osl_getThreadTextEncoding() ); + + SwDocShell* pDocSh = m_xDoc->GetDocShell(); + if( pDocSh ) + { + m_bViewCreated = true; // not, load synchronous + + // a jump mark is present + + if( pMed ) + { + m_sJmpMark = pMed->GetURLObject().GetMark(); + if( !m_sJmpMark.isEmpty() ) + { + m_eJumpTo = JumpToMarks::Mark; + sal_Int32 nLastPos = m_sJmpMark.lastIndexOf( cMarkSeparator ); + sal_Int32 nPos = nLastPos != -1 ? nLastPos : 0; + + OUString sCmp; + if (nPos) + { + sCmp = m_sJmpMark.copy(nPos + 1).replaceAll(" ", ""); + } + + if( !sCmp.isEmpty() ) + { + sCmp = sCmp.toAsciiLowerCase(); + if( sCmp == "region" ) + m_eJumpTo = JumpToMarks::Region; + else if( sCmp == "table" ) + m_eJumpTo = JumpToMarks::Table; + else if( sCmp == "graphic" ) + m_eJumpTo = JumpToMarks::Graphic; + else if( sCmp == "outline" || + sCmp == "text" || + sCmp == "frame" ) + m_eJumpTo = JumpToMarks::NONE; // this is nothing valid! + else + // otherwise this is a normal (book)mark + nPos = -1; + } + else + nPos = -1; + + if( nPos != -1 ) + m_sJmpMark = m_sJmpMark.copy( 0, nPos ); + if( m_sJmpMark.isEmpty() ) + m_eJumpTo = JumpToMarks::NONE; + } + } + } + + if (!rNamespace.isEmpty()) + { + SetNamespace(rNamespace); + m_bXHTML = true; + if (rNamespace == "reqif-xhtml") + m_bReqIF = true; + } + + // Extract load parameters which are specific to this filter. + if (!pMed) + { + return; + } + + comphelper::SequenceAsHashMap aLoadMap(pMed->GetArgs()); + auto it = aLoadMap.find("AllowedRTFOLEMimeTypes"); + if (it == aLoadMap.end()) + { + return; + } + + uno::Sequence<OUString> aTypes; + it->second >>= aTypes; + m_aAllowedRTFOLEMimeTypes = comphelper::sequenceToContainer<std::set<OUString>>(aTypes); +} + +SwHTMLParser::~SwHTMLParser() +{ +#ifdef DBG_UTIL + OSL_ENSURE( !m_nContinue, "DTOR in continue!" ); +#endif + + OSL_ENSURE(m_aContexts.empty(), "There are still contexts on the stack"); + OSL_ENSURE(!m_nContextStMin, "There are protected contexts"); + m_nContextStMin = 0; + while (!m_aContexts.empty()) + { + std::unique_ptr<HTMLAttrContext> xCntxt(PopContext()); + ClearContext(xCntxt.get()); + } + + bool bAsync = m_xDoc->IsInLoadAsynchron(); + m_xDoc->SetInLoadAsynchron( false ); + m_xDoc->getIDocumentSettingAccess().set(DocumentSettingId::HTML_MODE, m_bOldIsHTMLMode); + + if( m_xDoc->GetDocShell() && m_nEventId ) + Application::RemoveUserEvent( m_nEventId ); + + // the DocumentDetected maybe can delete the DocShells, therefore fetch again + if( m_xDoc->GetDocShell() ) + { + // update linked sections + sal_uInt16 nLinkMode = m_xDoc->getIDocumentSettingAccess().getLinkUpdateMode( true ); + if( nLinkMode != NEVER && bAsync && + SfxObjectCreateMode::INTERNAL!=m_xDoc->GetDocShell()->GetCreateMode() ) + m_xDoc->getIDocumentLinksAdministration().GetLinkManager().UpdateAllLinks( nLinkMode == MANUAL, false, nullptr ); + + if ( m_xDoc->GetDocShell()->IsLoading() ) + { + // #i59688# + m_xDoc->GetDocShell()->LoadingFinished(); + } + } + + delete m_pSttNdIdx; + + if( !m_aSetAttrTab.empty() ) + { + OSL_ENSURE( m_aSetAttrTab.empty(),"There are still attributes on the stack" ); + for ( const auto& rpAttr : m_aSetAttrTab ) + delete rpAttr; + m_aSetAttrTab.clear(); + } + + m_pCSS1Parser.reset(); + m_pNumRuleInfo.reset(); + DeleteFormImpl(); + m_pFootEndNoteImpl.reset(); + + OSL_ENSURE(!m_xTable, "It exists still an open table"); + m_pImageMaps.reset(); + + OSL_ENSURE( m_vPendingStack.empty(), + "SwHTMLParser::~SwHTMLParser: Here should not be Pending-Stack anymore" ); + m_vPendingStack.clear(); + + m_xDoc.clear(); + + if ( m_pTempViewFrame ) + { + m_pTempViewFrame->DoClose(); + + // the temporary view frame is hidden, so the hidden flag might need to be removed + if ( m_bRemoveHidden && m_xDoc.is() && m_xDoc->GetDocShell() && m_xDoc->GetDocShell()->GetMedium() ) + m_xDoc->GetDocShell()->GetMedium()->GetItemSet().ClearItem( SID_HIDDEN ); + } +} + +IMPL_LINK_NOARG( SwHTMLParser, AsyncCallback, void*, void ) +{ + m_nEventId=nullptr; + + // #i47907# - If the document has already been destructed, + // the parser should be aware of this: + if( ( m_xDoc->GetDocShell() && m_xDoc->GetDocShell()->IsAbortingImport() ) + || 1 == m_xDoc->getReferenceCount() ) + { + // was the import aborted by SFX? + eState = SvParserState::Error; + } + + GetAsynchCallLink().Call(nullptr); +} + +SvParserState SwHTMLParser::CallParser() +{ + // create temporary index on position 0, so it won't be moved! + m_pSttNdIdx = new SwNodeIndex( m_xDoc->GetNodes() ); + if( !IsNewDoc() ) // insert into existing document ? + { + const SwPosition* pPos = m_pPam->GetPoint(); + + m_xDoc->getIDocumentContentOperations().SplitNode( *pPos, false ); + + *m_pSttNdIdx = pPos->GetNodeIndex()-1; + m_xDoc->getIDocumentContentOperations().SplitNode( *pPos, false ); + + SwPaM aInsertionRangePam( *pPos ); + + m_pPam->Move( fnMoveBackward ); + + // split any redline over the insertion point + aInsertionRangePam.SetMark(); + *aInsertionRangePam.GetPoint() = *m_pPam->GetPoint(); + aInsertionRangePam.Move( fnMoveBackward ); + m_xDoc->getIDocumentRedlineAccess().SplitRedline( aInsertionRangePam ); + + m_xDoc->SetTextFormatColl( *m_pPam, + m_pCSS1Parser->GetTextCollFromPool( RES_POOLCOLL_STANDARD )); + } + + if( GetMedium() ) + { + if( !m_bViewCreated ) + { + m_nEventId = Application::PostUserEvent( LINK( this, SwHTMLParser, AsyncCallback ) ); + } + else + { + m_bViewCreated = true; + m_nEventId = nullptr; + } + } + else // show progress bar + { + rInput.Seek(STREAM_SEEK_TO_END); + rInput.ResetError(); + + m_xProgress.reset(new ImportProgress(m_xDoc->GetDocShell(), 0, rInput.Tell())); + + rInput.Seek(STREAM_SEEK_TO_BEGIN); + rInput.ResetError(); + } + + StartListening(m_xDoc->GetPageDesc( 0 ).GetNotifier()); + + SvParserState eRet = HTMLParser::CallParser(); + return eRet; +} + +bool SwHTMLParser::CanRemoveNode(SwNodeOffset nNodeIdx) const +{ + const SwNode *pPrev = m_xDoc->GetNodes()[nNodeIdx - 1]; + return pPrev->IsContentNode() || (pPrev->IsEndNode() && pPrev->StartOfSectionNode()->IsSectionNode()); +} + +void SwHTMLParser::Continue( HtmlTokenId nToken ) +{ +#ifdef DBG_UTIL + OSL_ENSURE(!m_nContinue, "Continue in Continue - not supposed to happen"); + m_nContinue++; +#endif + + // When the import (of SFX) is aborted, an error will be set but + // we still continue, so that we clean up properly. + OSL_ENSURE( SvParserState::Error!=eState, + "SwHTMLParser::Continue: already set an error" ); + if( m_xDoc->GetDocShell() && m_xDoc->GetDocShell()->IsAbortingImport() ) + eState = SvParserState::Error; + + // Fetch SwViewShell from document, save it and set as current. + SwViewShell *pInitVSh = CallStartAction(); + + if( SvParserState::Error != eState && GetMedium() && !m_bViewCreated ) + { + // At first call first return, show document and wait for callback + // time. + // At this point in CallParser only one digit was read and + // a SaveState(0) was called. + eState = SvParserState::Pending; + m_bViewCreated = true; + m_xDoc->SetInLoadAsynchron( true ); + +#ifdef DBG_UTIL + m_nContinue--; +#endif + + return; + } + + m_bSetModEnabled = false; + if( m_xDoc->GetDocShell() ) + { + m_bSetModEnabled = m_xDoc->GetDocShell()->IsEnableSetModified(); + if( m_bSetModEnabled ) + { + m_xDoc->GetDocShell()->EnableSetModified( false ); + } + } + + // during import don't call OLE-Modified + Link<bool,void> aOLELink( m_xDoc->GetOle2Link() ); + m_xDoc->SetOle2Link( Link<bool,void>() ); + + bool bModified = m_xDoc->getIDocumentState().IsModified(); + bool const bWasUndo = m_xDoc->GetIDocumentUndoRedo().DoesUndo(); + m_xDoc->GetIDocumentUndoRedo().DoUndo(false); + + // When the import will be aborted, don't call Continue anymore. + // If a Pending-Stack exists make sure the stack is ended with a call + // of NextToken. + if( SvParserState::Error == eState ) + { + OSL_ENSURE( m_vPendingStack.empty() || m_vPendingStack.back().nToken != HtmlTokenId::NONE, + "SwHTMLParser::Continue: Pending-Stack without Token" ); + if( !m_vPendingStack.empty() && m_vPendingStack.back().nToken != HtmlTokenId::NONE ) + NextToken( m_vPendingStack.back().nToken ); + OSL_ENSURE( m_vPendingStack.empty(), + "SwHTMLParser::Continue: There is again a Pending-Stack" ); + } + else + { + HTMLParser::Continue( !m_vPendingStack.empty() ? m_vPendingStack.back().nToken : nToken ); + } + + // disable progress bar again + m_xProgress.reset(); + + bool bLFStripped = false; + if( SvParserState::Pending != GetStatus() ) + { + // set the last attributes yet + { + if( !m_aScriptSource.isEmpty() ) + { + SwScriptFieldType *pType = + static_cast<SwScriptFieldType*>(m_xDoc->getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::Script )); + + SwScriptField aField( pType, m_aScriptType, m_aScriptSource, + false ); + InsertAttr( SwFormatField( aField ), false ); + } + + if( m_pAppletImpl ) + { + if( m_pAppletImpl->GetApplet().is() ) + EndApplet(); + else + EndObject(); + } + + // maybe remove an existing LF after the last paragraph + if( IsNewDoc() ) + bLFStripped = StripTrailingLF() > 0; + + // close still open numbering + while( GetNumInfo().GetNumRule() ) + EndNumberBulletList(); + + OSL_ENSURE( !m_nContextStMin, "There are protected contexts" ); + // try this twice, first normally to let m_nContextStMin decrease + // naturally and get contexts popped in desired order, and if that + // fails force it + for (int i = 0; i < 2; ++i) + { + while (m_aContexts.size() > m_nContextStMin) + { + std::unique_ptr<HTMLAttrContext> xCntxt(PopContext()); + if (xCntxt) + EndContext(xCntxt.get()); + } + if (!m_nContextStMin) + break; + OSL_ENSURE(!m_nContextStMin, "There are still protected contexts"); + m_nContextStMin = 0; + } + + m_aParaAttrs.clear(); + + SetAttr( false ); + + // set the first delayed styles + m_pCSS1Parser->SetDelayedStyles(); + } + + // again correct the start + if( !IsNewDoc() && m_pSttNdIdx->GetIndex() ) + { + SwTextNode* pTextNode = m_pSttNdIdx->GetNode().GetTextNode(); + SwNodeIndex aNxtIdx( *m_pSttNdIdx ); + if( pTextNode && pTextNode->CanJoinNext( &aNxtIdx )) + { + const sal_Int32 nStt = pTextNode->GetText().getLength(); + // when the cursor is still in the node, then set him at the end + if( m_pPam->GetPoint()->GetNode() == aNxtIdx.GetNode() ) + { + m_pPam->GetPoint()->Assign( *pTextNode, nStt ); + } + +#if OSL_DEBUG_LEVEL > 0 +// !!! shouldn't be possible, or ?? + OSL_ENSURE( m_pSttNdIdx->GetIndex()+1 != m_pPam->GetBound().GetNodeIndex(), + "Pam.Bound1 is still in the node" ); + OSL_ENSURE( m_pSttNdIdx->GetIndex()+1 != m_pPam->GetBound( false ).GetNodeIndex(), + "Pam.Bound2 is still in the node" ); + + if( m_pSttNdIdx->GetIndex()+1 == m_pPam->GetBound().GetNodeIndex() ) + { + const sal_Int32 nCntPos = m_pPam->GetBound().GetContentIndex(); + m_pPam->GetBound().SetContent( + pTextNode->GetText().getLength() + nCntPos ); + } + if( m_pSttNdIdx->GetIndex()+1 == m_pPam->GetBound( false ).GetNodeIndex() ) + { + const sal_Int32 nCntPos = m_pPam->GetBound( false ).GetContentIndex(); + m_pPam->GetBound( false ).SetContent( + pTextNode->GetText().getLength() + nCntPos ); + } +#endif + // Keep character attribute! + SwTextNode* pDelNd = aNxtIdx.GetNode().GetTextNode(); + if (pTextNode->GetText().getLength()) + pDelNd->FormatToTextAttr( pTextNode ); + else + pTextNode->ChgFormatColl( pDelNd->GetTextColl() ); + pTextNode->JoinNext(); + } + } + } + + if( SvParserState::Accepted == eState ) + { + if( m_nMissingImgMaps ) + { + // Some Image-Map relations are still missing. + // Maybe now the Image-Maps are there? + ConnectImageMaps(); + } + + // now remove the last useless paragraph + SwPosition* pPos = m_pPam->GetPoint(); + if( !pPos->GetContentIndex() && !bLFStripped ) + { + SwTextNode* pCurrentNd; + SwNodeOffset nNodeIdx = pPos->GetNodeIndex(); + + bool bHasFlysOrMarks = + HasCurrentParaFlys() || HasCurrentParaBookmarks( true ); + + if( IsNewDoc() ) + { + if (!m_pPam->GetPoint()->GetContentIndex() && CanRemoveNode(nNodeIdx)) + { + SwContentNode* pCNd = m_pPam->GetPointContentNode(); + if( pCNd && pCNd->StartOfSectionIndex()+2 < + pCNd->EndOfSectionIndex() && !bHasFlysOrMarks ) + { + SwViewShell *pVSh = CheckActionViewShell(); + SwCursorShell *pCursorSh = dynamic_cast<SwCursorShell *>( pVSh ); + if( pCursorSh && + pCursorSh->GetCursor()->GetPoint() + ->GetNodeIndex() == nNodeIdx ) + { + pCursorSh->MovePara(GoPrevPara, fnParaEnd ); + pCursorSh->SetMark(); + pCursorSh->ClearMark(); + } + SwNode& rDelNode = m_pPam->GetPoint()->GetNode(); + // move so we don't have a dangling SwContentIndex to the deleted node + m_pPam->GetPoint()->Adjust(SwNodeOffset(1)); + if (m_pPam->HasMark()) + m_pPam->GetMark()->Adjust(SwNodeOffset(1)); + m_xDoc->GetNodes().Delete( rDelNode ); + } + } + } + else if( nullptr != ( pCurrentNd = m_xDoc->GetNodes()[ nNodeIdx ]->GetTextNode()) && !bHasFlysOrMarks ) + { + if( pCurrentNd->CanJoinNext( pPos )) + { + SwTextNode* pNextNd = pPos->GetNode().GetTextNode(); + m_pPam->SetMark(); m_pPam->DeleteMark(); + pNextNd->JoinPrev(); + } + else if (pCurrentNd->GetText().isEmpty()) + { + m_pPam->SetMark(); m_pPam->DeleteMark(); + SwNode& rDelNode = pPos->GetNode(); + // move so we don't have a dangling SwContentIndex to the deleted node + m_pPam->GetPoint()->Adjust(SwNodeOffset(+1)); + m_xDoc->GetNodes().Delete( rDelNode ); + m_pPam->Move( fnMoveBackward ); + } + } + } + + // annul the SplitNode from the beginning + else if( !IsNewDoc() ) + { + if( pPos->GetContentIndex() ) // then there was no <p> at the end + m_pPam->Move( fnMoveForward, GoInNode ); // therefore to the next + SwTextNode* pTextNode = pPos->GetNode().GetTextNode(); + SwNodeIndex aPrvIdx( pPos->GetNode() ); + if( pTextNode && pTextNode->CanJoinPrev( &aPrvIdx ) && + *m_pSttNdIdx <= aPrvIdx ) + { + // Normally here should take place a JoinNext, but all cursors and + // so are registered in pTextNode, so that it MUST remain. + + // Convert paragraph to character attribute, from Prev adopt + // the paragraph attribute and the template! + SwTextNode* pPrev = aPrvIdx.GetNode().GetTextNode(); + pTextNode->ChgFormatColl( pPrev->GetTextColl() ); + pTextNode->FormatToTextAttr( pPrev ); + pTextNode->ResetAllAttr(); + + if( pPrev->HasSwAttrSet() ) + pTextNode->SetAttr( *pPrev->GetpSwAttrSet() ); + + if( &m_pPam->GetBound().GetNode() == pPrev ) + m_pPam->GetBound().nContent.Assign( pTextNode, 0 ); + if( &m_pPam->GetBound(false).GetNode() == pPrev ) + m_pPam->GetBound(false).nContent.Assign( pTextNode, 0 ); + + pTextNode->JoinPrev(); + } + } + + // adjust AutoLoad in DocumentProperties + if (!bFuzzing && IsNewDoc()) + { + SwDocShell *pDocShell(m_xDoc->GetDocShell()); + OSL_ENSURE(pDocShell, "no SwDocShell"); + if (pDocShell) { + uno::Reference<document::XDocumentPropertiesSupplier> xDPS( + pDocShell->GetModel(), uno::UNO_QUERY_THROW); + uno::Reference<document::XDocumentProperties> xDocProps( + xDPS->getDocumentProperties()); + OSL_ENSURE(xDocProps.is(), "DocumentProperties is null"); + if ( xDocProps.is() && (xDocProps->getAutoloadSecs() > 0) && + (xDocProps->getAutoloadURL().isEmpty()) ) + { + xDocProps->setAutoloadURL(m_aPathToFile); + } + } + } + + if( m_bUpdateDocStat ) + { + m_xDoc->getIDocumentStatistics().UpdateDocStat( false, true ); + } + } + + if( SvParserState::Pending != GetStatus() ) + { + delete m_pSttNdIdx; + m_pSttNdIdx = nullptr; + } + + // should the parser be the last one who hold the document, then nothing + // has to be done anymore, document will be destroyed shortly! + if( 1 < m_xDoc->getReferenceCount() ) + { + if( bWasUndo ) + { + m_xDoc->GetIDocumentUndoRedo().DelAllUndoObj(); + m_xDoc->GetIDocumentUndoRedo().DoUndo(true); + } + else if( !pInitVSh ) + { + // When at the beginning of Continue no Shell was available, + // it's possible in the meantime one was created. + // In that case the bWasUndo flag is wrong and we must + // enable Undo. + SwViewShell *pTmpVSh = CheckActionViewShell(); + if( pTmpVSh ) + { + m_xDoc->GetIDocumentUndoRedo().DoUndo(true); + } + } + + m_xDoc->SetOle2Link( aOLELink ); + if( !bModified ) + m_xDoc->getIDocumentState().ResetModified(); + if( m_bSetModEnabled && m_xDoc->GetDocShell() ) + { + m_xDoc->GetDocShell()->EnableSetModified(); + m_bSetModEnabled = false; // this is unnecessary here + } + } + + // When the Document-SwVievShell still exists and an Action is open + // (doesn't have to be by abort), end the Action, disconnect from Shell + // and finally reconstruct the old Shell. + CallEndAction( true ); + +#ifdef DBG_UTIL + m_nContinue--; +#endif +} + +void SwHTMLParser::Notify(const SfxHint& rHint) +{ + if(rHint.GetId() == SfxHintId::Dying) + { + EndListeningAll(); + ReleaseRef(); + } +} + +void SwHTMLParser::DocumentDetected() +{ + OSL_ENSURE( !m_bDocInitialized, "DocumentDetected called multiple times" ); + m_bDocInitialized = true; + if( IsNewDoc() ) + { + if( IsInHeader() ) + FinishHeader(); + + CallEndAction( true ); + + m_xDoc->GetIDocumentUndoRedo().DoUndo(false); + // For DocumentDetected in general a SwViewShell is created. + // But it also can be created later, in case the UI is captured. + CallStartAction(); + } +} + +// is called for every token that is recognised in CallParser +void SwHTMLParser::NextToken( HtmlTokenId nToken ) +{ + if( ( m_xDoc->GetDocShell() && m_xDoc->GetDocShell()->IsAbortingImport() ) + || 1 == m_xDoc->getReferenceCount() ) + { + // Was the import cancelled by SFX? If a pending stack + // exists, clean it. + eState = SvParserState::Error; + OSL_ENSURE( m_vPendingStack.empty() || m_vPendingStack.back().nToken != HtmlTokenId::NONE, + "SwHTMLParser::NextToken: Pending-Stack without token" ); + if( 1 == m_xDoc->getReferenceCount() || m_vPendingStack.empty() ) + return ; + } + +#if OSL_DEBUG_LEVEL > 0 + if( !m_vPendingStack.empty() ) + { + switch( nToken ) + { + // tables are read by recursive method calls + case HtmlTokenId::TABLE_ON: + // For CSS declarations we might have to wait + // for a file download to finish + case HtmlTokenId::LINK: + // For controls we might have to set the size. + case HtmlTokenId::INPUT: + case HtmlTokenId::TEXTAREA_ON: + case HtmlTokenId::SELECT_ON: + case HtmlTokenId::SELECT_OFF: + break; + default: + OSL_ENSURE( m_vPendingStack.empty(), "Unknown token for Pending-Stack" ); + break; + } + } +#endif + + // The following special cases have to be treated before the + // filter detection, because Netscape doesn't reference the content + // of the title for filter detection either. + if( m_vPendingStack.empty() ) + { + if( m_bInTitle ) + { + switch( nToken ) + { + case HtmlTokenId::TITLE_OFF: + { + OUString sTitle = m_sTitle.makeStringAndClear(); + if( IsNewDoc() && !sTitle.isEmpty() ) + { + if( m_xDoc->GetDocShell() ) { + uno::Reference<document::XDocumentPropertiesSupplier> + xDPS(m_xDoc->GetDocShell()->GetModel(), + uno::UNO_QUERY_THROW); + uno::Reference<document::XDocumentProperties> xDocProps( + xDPS->getDocumentProperties()); + OSL_ENSURE(xDocProps.is(), "no DocumentProperties"); + if (xDocProps.is()) { + xDocProps->setTitle(sTitle); + } + + m_xDoc->GetDocShell()->SetTitle(sTitle); + } + } + m_bInTitle = false; + break; + } + + case HtmlTokenId::NONBREAKSPACE: + m_sTitle.append(" "); + break; + + case HtmlTokenId::SOFTHYPH: + m_sTitle.append("-"); + break; + + case HtmlTokenId::TEXTTOKEN: + m_sTitle.append(aToken); + break; + + default: + m_sTitle.append("<"); + if( (nToken >= HtmlTokenId::ONOFF_START) && isOffToken(nToken) ) + m_sTitle.append("/"); + m_sTitle.append(sSaveToken); + if( !aToken.isEmpty() ) + { + m_sTitle.append(" "); + m_sTitle.append(aToken); + } + m_sTitle.append(">"); + break; + } + + return; + } + } + + // Find out what type of document it is if we don't know already. + // For Controls this has to be finished before the control is inserted + // because for inserting a View is needed. + if( !m_bDocInitialized ) + DocumentDetected(); + + bool bGetIDOption = false, bInsertUnknown = false; + bool bUpperSpaceSave = m_bUpperSpace; + m_bUpperSpace = false; + + // The following special cases may or have to be treated after the + // filter detection + if( m_vPendingStack.empty() ) + { + if( m_bInFloatingFrame ) + { + // <SCRIPT> is ignored here (from us), because it is ignored in + // Applets as well + if( HtmlTokenId::IFRAME_OFF == nToken ) + { + m_bCallNextToken = false; + m_bInFloatingFrame = false; + } + + return; + } + else if( m_bInNoEmbed ) + { + switch( nToken ) + { + case HtmlTokenId::NOEMBED_OFF: + m_aContents = convertLineEnd(m_aContents, GetSystemLineEnd()); + InsertComment( m_aContents, OOO_STRING_SVTOOLS_HTML_noembed ); + m_aContents.clear(); + m_bCallNextToken = false; + m_bInNoEmbed = false; + break; + + case HtmlTokenId::RAWDATA: + InsertCommentText( OOO_STRING_SVTOOLS_HTML_noembed ); + break; + + default: + OSL_ENSURE( false, "SwHTMLParser::NextToken: invalid tag" ); + break; + } + + return; + } + else if( m_pAppletImpl ) + { + // in an applet only <PARAM> tags and the </APPLET> tag + // are of interest for us (for the moment) + // <SCRIPT> is ignored here (from Netscape)! + + switch( nToken ) + { + case HtmlTokenId::APPLET_OFF: + m_bCallNextToken = false; + EndApplet(); + break; + case HtmlTokenId::OBJECT_OFF: + m_bCallNextToken = false; + EndObject(); + break; + case HtmlTokenId::PARAM: + InsertParam(); + break; + default: break; + } + + return; + } + else if( m_bTextArea ) + { + // in a TextArea everything up to </TEXTAREA> is inserted as text. + // <SCRIPT> is ignored here (from Netscape)! + + switch( nToken ) + { + case HtmlTokenId::TEXTAREA_OFF: + m_bCallNextToken = false; + EndTextArea(); + break; + + default: + InsertTextAreaText( nToken ); + break; + } + + return; + } + else if( m_bSelect ) + { + // HAS to be treated after bNoScript! + switch( nToken ) + { + case HtmlTokenId::SELECT_OFF: + m_bCallNextToken = false; + EndSelect(); + return; + + case HtmlTokenId::OPTION: + InsertSelectOption(); + return; + + case HtmlTokenId::TEXTTOKEN: + InsertSelectText(); + return; + + case HtmlTokenId::INPUT: + case HtmlTokenId::SCRIPT_ON: + case HtmlTokenId::SCRIPT_OFF: + case HtmlTokenId::NOSCRIPT_ON: + case HtmlTokenId::NOSCRIPT_OFF: + case HtmlTokenId::RAWDATA: + // treat in normal switch + break; + + default: + // ignore + return; + } + } + else if( m_pMarquee ) + { + // in a TextArea everything up to </TEXTAREA> is inserted as text. + // The <SCRIPT> tags are ignored from MS-IE, we ignore the whole + // script. + switch( nToken ) + { + case HtmlTokenId::MARQUEE_OFF: + m_bCallNextToken = false; + EndMarquee(); + break; + + case HtmlTokenId::TEXTTOKEN: + InsertMarqueeText(); + break; + default: break; + } + + return; + } + else if( m_bInField ) + { + switch( nToken ) + { + case HtmlTokenId::SDFIELD_OFF: + m_bCallNextToken = false; + EndField(); + break; + + case HtmlTokenId::TEXTTOKEN: + InsertFieldText(); + break; + default: break; + } + + return; + } + else if( m_bInFootEndNoteAnchor || m_bInFootEndNoteSymbol ) + { + switch( nToken ) + { + case HtmlTokenId::ANCHOR_OFF: + EndAnchor(); + m_bCallNextToken = false; + break; + + case HtmlTokenId::TEXTTOKEN: + InsertFootEndNoteText(); + break; + default: break; + } + return; + } + else if( !m_aUnknownToken.isEmpty() ) + { + // Paste content of unknown tags. + // (but surely if we are not in the header section) fdo#36080 fdo#34666 + if (!aToken.isEmpty() && !IsInHeader() ) + { + if( !m_bDocInitialized ) + DocumentDetected(); + m_xDoc->getIDocumentContentOperations().InsertString( *m_pPam, aToken.toString()); + + // if there are temporary paragraph attributes and the + // paragraph isn't empty then the paragraph attributes + // are final. + m_aParaAttrs.clear(); + + SetAttr(); + } + + // Unknown token in the header are only closed by a matching + // end-token, </HEAD> or <BODY>. Text inside is ignored. + switch( nToken ) + { + case HtmlTokenId::UNKNOWNCONTROL_OFF: + if( m_aUnknownToken != sSaveToken ) + return; + [[fallthrough]]; + case HtmlTokenId::FRAMESET_ON: + case HtmlTokenId::HEAD_OFF: + case HtmlTokenId::BODY_ON: + case HtmlTokenId::IMAGE: // Don't know why Netscape acts this way. + m_aUnknownToken.clear(); + break; + case HtmlTokenId::TEXTTOKEN: + return; + default: + m_aUnknownToken.clear(); + break; + } + } + } + + switch( nToken ) + { + case HtmlTokenId::BODY_ON: + if (!m_bBodySeen) + { + m_bBodySeen = true; + if( !m_aStyleSource.isEmpty() ) + { + m_pCSS1Parser->ParseStyleSheet( m_aStyleSource ); + m_aStyleSource.clear(); + } + if( IsNewDoc() ) + { + InsertBodyOptions(); + // If there is a template for the first or the right page, + // it is set here. + const SwPageDesc *pPageDesc = nullptr; + if( m_pCSS1Parser->IsSetFirstPageDesc() ) + pPageDesc = m_pCSS1Parser->GetFirstPageDesc(); + else if( m_pCSS1Parser->IsSetRightPageDesc() ) + pPageDesc = m_pCSS1Parser->GetRightPageDesc(); + + if( pPageDesc ) + { + m_xDoc->getIDocumentContentOperations().InsertPoolItem( *m_pPam, SwFormatPageDesc( pPageDesc ) ); + } + } + } + break; + + case HtmlTokenId::LINK: + InsertLink(); + break; + + case HtmlTokenId::BASE: + { + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::HREF: + m_sBaseURL = rOption.GetString(); + break; + case HtmlOptionId::TARGET: + if( IsNewDoc() ) + { + SwDocShell *pDocShell(m_xDoc->GetDocShell()); + OSL_ENSURE(pDocShell, "no SwDocShell"); + if (pDocShell) { + uno::Reference<document::XDocumentPropertiesSupplier> xDPS( + pDocShell->GetModel(), uno::UNO_QUERY_THROW); + uno::Reference<document::XDocumentProperties> + xDocProps(xDPS->getDocumentProperties()); + OSL_ENSURE(xDocProps.is(),"no DocumentProperties"); + if (xDocProps.is()) { + xDocProps->setDefaultTarget( + rOption.GetString()); + } + } + } + break; + default: break; + } + } + } + break; + + case HtmlTokenId::META: + { + SvKeyValueIterator *pHTTPHeader = nullptr; + if( IsNewDoc() ) + { + SwDocShell *pDocSh = m_xDoc->GetDocShell(); + if( pDocSh ) + pHTTPHeader = pDocSh->GetHeaderAttributes(); + } + SwDocShell *pDocShell(m_xDoc->GetDocShell()); + OSL_ENSURE(pDocShell, "no SwDocShell"); + if (pDocShell) + { + uno::Reference<document::XDocumentProperties> xDocProps; + if (IsNewDoc()) + { + const uno::Reference<document::XDocumentPropertiesSupplier> + xDPS( pDocShell->GetModel(), uno::UNO_QUERY_THROW ); + xDocProps = xDPS->getDocumentProperties(); + OSL_ENSURE(xDocProps.is(), "DocumentProperties is null"); + } + ParseMetaOptions( xDocProps, pHTTPHeader ); + } + } + break; + + case HtmlTokenId::TITLE_ON: + m_bInTitle = true; + break; + + case HtmlTokenId::SCRIPT_ON: + NewScript(); + break; + + case HtmlTokenId::SCRIPT_OFF: + EndScript(); + break; + + case HtmlTokenId::NOSCRIPT_ON: + case HtmlTokenId::NOSCRIPT_OFF: + bInsertUnknown = true; + break; + + case HtmlTokenId::STYLE_ON: + NewStyle(); + break; + + case HtmlTokenId::STYLE_OFF: + EndStyle(); + break; + + case HtmlTokenId::RAWDATA: + if( !m_bIgnoreRawData ) + { + if( IsReadScript() ) + { + AddScriptSource(); + } + else if( IsReadStyle() ) + { + if( !m_aStyleSource.isEmpty() ) + m_aStyleSource += "\n"; + m_aStyleSource += aToken; + } + } + break; + + case HtmlTokenId::OBJECT_ON: + if (m_bXHTML) + { + if (!InsertEmbed()) + InsertImage(); + break; + } +#if HAVE_FEATURE_JAVA + NewObject(); + m_bCallNextToken = m_pAppletImpl!=nullptr && m_xTable; +#endif + break; + + case HtmlTokenId::OBJECT_OFF: + if (!m_aEmbeds.empty()) + m_aEmbeds.pop(); + break; + + case HtmlTokenId::APPLET_ON: +#if HAVE_FEATURE_JAVA + InsertApplet(); + m_bCallNextToken = m_pAppletImpl!=nullptr && m_xTable; +#endif + break; + + case HtmlTokenId::IFRAME_ON: + if (bFuzzing && m_nFloatingFrames > 64) + SAL_WARN("sw.html", "Not importing any more FloatingFrames for fuzzing performance"); + else + { + InsertFloatingFrame(); + m_bCallNextToken = m_bInFloatingFrame && m_xTable; + } + break; + + case HtmlTokenId::LINEBREAK: + if( !IsReadPRE() ) + { + InsertLineBreak(); + break; + } + else + bGetIDOption = true; + // <BR>s in <PRE> resemble true LFs, hence no break + [[fallthrough]]; + + case HtmlTokenId::NEWPARA: + // CR in PRE/LISTING/XMP + { + if( HtmlTokenId::NEWPARA==nToken || + m_pPam->GetPoint()->GetContentIndex() ) + { + AppendTextNode(); // there is no LF at this place + // therefore it will cause no problems + SetTextCollAttrs(); + } + // progress bar + if (m_xProgress) + m_xProgress->Update(rInput.Tell()); + } + break; + + case HtmlTokenId::NONBREAKSPACE: + m_xDoc->getIDocumentContentOperations().InsertString( *m_pPam, OUString(CHAR_HARDBLANK) ); + break; + + case HtmlTokenId::SOFTHYPH: + m_xDoc->getIDocumentContentOperations().InsertString( *m_pPam, OUString(CHAR_SOFTHYPHEN) ); + break; + + case HtmlTokenId::LINEFEEDCHAR: + if( m_pPam->GetPoint()->GetContentIndex() ) + AppendTextNode(); + if (!m_xTable && !m_xDoc->IsInHeaderFooter(m_pPam->GetPoint()->GetNode())) + { + NewAttr(m_xAttrTab, &m_xAttrTab->pBreak, SvxFormatBreakItem(SvxBreak::PageBefore, RES_BREAK)); + EndAttr( m_xAttrTab->pBreak, false ); + } + break; + + case HtmlTokenId::TEXTTOKEN: + case HtmlTokenId::CDATA: + // insert string without spanning attributes at the end. + if (!aToken.isEmpty() && ' ' == aToken[0] && !IsReadPRE() && !GetPreserveSpaces()) + { + sal_Int32 nPos = m_pPam->GetPoint()->GetContentIndex(); + const SwTextNode* pTextNode = nPos ? m_pPam->GetPoint()->GetNode().GetTextNode() : nullptr; + if (pTextNode) + { + const OUString& rText = pTextNode->GetText(); + sal_Unicode cLast = rText[--nPos]; + if( ' ' == cLast || '\x0a' == cLast) + aToken.remove(0, 1); + } + else + aToken.remove(0, 1); + + if( aToken.isEmpty() ) + { + m_bUpperSpace = bUpperSpaceSave; + break; + } + } + + if( !aToken.isEmpty() ) + { + if( !m_bDocInitialized ) + DocumentDetected(); + + if (!m_aEmbeds.empty()) + { + // The text token is inside an OLE object, which means + // alternate text. + SwOLENode* pOLENode = m_aEmbeds.top(); + if (!pOLENode) + { + // <object> is mapped to an image -> ignore. + break; + } + + if (SwFlyFrameFormat* pFormat + = dynamic_cast<SwFlyFrameFormat*>(pOLENode->GetFlyFormat())) + { + if (SdrObject* pObject = SwXFrame::GetOrCreateSdrObject(*pFormat)) + { + pObject->SetTitle(pObject->GetTitle() + aToken); + break; + } + } + } + + m_xDoc->getIDocumentContentOperations().InsertString( *m_pPam, aToken.toString()); + + // if there are temporary paragraph attributes and the + // paragraph isn't empty then the paragraph attributes + // are final. + m_aParaAttrs.clear(); + + SetAttr(); + } + break; + + case HtmlTokenId::HORZRULE: + InsertHorzRule(); + break; + + case HtmlTokenId::IMAGE: + InsertImage(); + // if only the parser references the doc, we can break and set + // an error code + if( 1 == m_xDoc->getReferenceCount() ) + { + eState = SvParserState::Error; + } + break; + + case HtmlTokenId::SPACER: + InsertSpacer(); + break; + + case HtmlTokenId::EMBED: + InsertEmbed(); + break; + + case HtmlTokenId::NOEMBED_ON: + m_bInNoEmbed = true; + m_bCallNextToken = bool(m_xTable); + ReadRawData( OOO_STRING_SVTOOLS_HTML_noembed ); + break; + + case HtmlTokenId::DEFLIST_ON: + if( m_nOpenParaToken != HtmlTokenId::NONE ) + EndPara(); + NewDefList(); + break; + case HtmlTokenId::DEFLIST_OFF: + if( m_nOpenParaToken != HtmlTokenId::NONE ) + EndPara(); + EndDefListItem( HtmlTokenId::NONE ); + EndDefList(); + break; + + case HtmlTokenId::DD_ON: + case HtmlTokenId::DT_ON: + if( m_nOpenParaToken != HtmlTokenId::NONE ) + EndPara(); + EndDefListItem();// close <DD>/<DT> and set no template + NewDefListItem( nToken ); + break; + + case HtmlTokenId::DD_OFF: + case HtmlTokenId::DT_OFF: + // c.f. HtmlTokenId::LI_OFF + // Actually we should close a DD/DT now. + // But neither Netscape nor Microsoft do this and so don't we. + EndDefListItem( nToken ); + break; + + // divisions + case HtmlTokenId::DIVISION_ON: + case HtmlTokenId::CENTER_ON: + if (!m_isInTableStructure) + { + if (m_nOpenParaToken != HtmlTokenId::NONE) + { + if (IsReadPRE()) + m_nOpenParaToken = HtmlTokenId::NONE; + else + EndPara(); + } + NewDivision( nToken ); + } + break; + + case HtmlTokenId::DIVISION_OFF: + case HtmlTokenId::CENTER_OFF: + if (!m_isInTableStructure) + { + if (m_nOpenParaToken != HtmlTokenId::NONE) + { + if (IsReadPRE()) + m_nOpenParaToken = HtmlTokenId::NONE; + else + EndPara(); + } + EndDivision(); + } + break; + + case HtmlTokenId::MULTICOL_ON: + if( m_nOpenParaToken != HtmlTokenId::NONE ) + EndPara(); + NewMultiCol(); + break; + + case HtmlTokenId::MULTICOL_OFF: + if( m_nOpenParaToken != HtmlTokenId::NONE ) + EndPara(); + EndTag( HtmlTokenId::MULTICOL_ON ); + break; + + case HtmlTokenId::MARQUEE_ON: + NewMarquee(); + m_bCallNextToken = m_pMarquee!=nullptr && m_xTable; + break; + + case HtmlTokenId::FORM_ON: + NewForm(); + break; + case HtmlTokenId::FORM_OFF: + EndForm(); + break; + + // templates + case HtmlTokenId::PARABREAK_ON: + if( m_nOpenParaToken != HtmlTokenId::NONE ) + EndPara( true ); + NewPara(); + break; + + case HtmlTokenId::PARABREAK_OFF: + EndPara( true ); + break; + + case HtmlTokenId::ADDRESS_ON: + if( m_nOpenParaToken != HtmlTokenId::NONE ) + EndPara(); + NewTextFormatColl(HtmlTokenId::ADDRESS_ON, RES_POOLCOLL_SEND_ADDRESS); + break; + + case HtmlTokenId::ADDRESS_OFF: + if( m_nOpenParaToken != HtmlTokenId::NONE ) + EndPara(); + EndTextFormatColl( HtmlTokenId::ADDRESS_OFF ); + break; + + case HtmlTokenId::BLOCKQUOTE_ON: + case HtmlTokenId::BLOCKQUOTE30_ON: + if( m_nOpenParaToken != HtmlTokenId::NONE ) + EndPara(); + NewTextFormatColl( HtmlTokenId::BLOCKQUOTE_ON, RES_POOLCOLL_HTML_BLOCKQUOTE ); + break; + + case HtmlTokenId::BLOCKQUOTE_OFF: + case HtmlTokenId::BLOCKQUOTE30_OFF: + if( m_nOpenParaToken != HtmlTokenId::NONE ) + EndPara(); + EndTextFormatColl( HtmlTokenId::BLOCKQUOTE_ON ); + break; + + case HtmlTokenId::PREFORMTXT_ON: + case HtmlTokenId::LISTING_ON: + case HtmlTokenId::XMP_ON: + if( m_nOpenParaToken != HtmlTokenId::NONE ) + EndPara(); + NewTextFormatColl( nToken, RES_POOLCOLL_HTML_PRE ); + break; + + case HtmlTokenId::PREFORMTXT_OFF: + m_bNoParSpace = true; // the last PRE-paragraph gets a spacing + EndTextFormatColl( HtmlTokenId::PREFORMTXT_OFF ); + break; + + case HtmlTokenId::LISTING_OFF: + case HtmlTokenId::XMP_OFF: + EndTextFormatColl( nToken ); + break; + + case HtmlTokenId::HEAD1_ON: + case HtmlTokenId::HEAD2_ON: + case HtmlTokenId::HEAD3_ON: + case HtmlTokenId::HEAD4_ON: + case HtmlTokenId::HEAD5_ON: + case HtmlTokenId::HEAD6_ON: + if( m_nOpenParaToken != HtmlTokenId::NONE ) + { + if( IsReadPRE() ) + m_nOpenParaToken = HtmlTokenId::NONE; + else + EndPara(); + } + NewHeading( nToken ); + break; + + case HtmlTokenId::HEAD1_OFF: + case HtmlTokenId::HEAD2_OFF: + case HtmlTokenId::HEAD3_OFF: + case HtmlTokenId::HEAD4_OFF: + case HtmlTokenId::HEAD5_OFF: + case HtmlTokenId::HEAD6_OFF: + EndHeading(); + break; + + case HtmlTokenId::TABLE_ON: + if( !m_vPendingStack.empty() ) + BuildTable( SvxAdjust::End ); + else + { + if( m_nOpenParaToken != HtmlTokenId::NONE ) + EndPara(); + OSL_ENSURE(!m_xTable, "table in table not allowed here"); + if( !m_xTable && (IsNewDoc() || !m_pPam->GetPointNode().FindTableNode()) && + (m_pPam->GetPoint()->GetNodeIndex() > + m_xDoc->GetNodes().GetEndOfExtras().GetIndex() || + !m_pPam->GetPointNode().FindFootnoteStartNode() ) ) + { + if ( m_nParaCnt < 5 ) + Show(); // show what we have up to here + + SvxAdjust eAdjust = m_xAttrTab->pAdjust + ? static_cast<const SvxAdjustItem&>(m_xAttrTab->pAdjust->GetItem()). + GetAdjust() + : SvxAdjust::End; + BuildTable( eAdjust ); + } + else + bInsertUnknown = m_bKeepUnknown; + } + break; + + // lists + case HtmlTokenId::DIRLIST_ON: + case HtmlTokenId::MENULIST_ON: + case HtmlTokenId::ORDERLIST_ON: + case HtmlTokenId::UNORDERLIST_ON: + if( m_nOpenParaToken != HtmlTokenId::NONE ) + EndPara(); + NewNumberBulletList( nToken ); + break; + + case HtmlTokenId::DIRLIST_OFF: + case HtmlTokenId::MENULIST_OFF: + case HtmlTokenId::ORDERLIST_OFF: + case HtmlTokenId::UNORDERLIST_OFF: + if( m_nOpenParaToken != HtmlTokenId::NONE ) + EndPara(); + EndNumberBulletListItem( HtmlTokenId::NONE, true ); + EndNumberBulletList( nToken ); + break; + + case HtmlTokenId::LI_ON: + case HtmlTokenId::LISTHEADER_ON: + if( m_nOpenParaToken != HtmlTokenId::NONE && + (m_pPam->GetPoint()->GetContentIndex() + || HtmlTokenId::PARABREAK_ON==m_nOpenParaToken) ) + { + // only finish paragraph for <P><LI>, not for <DD><LI> + EndPara(); + } + + if (bFuzzing && m_nListItems > 1024) + { + SAL_WARN("sw.html", "skipping remaining bullet import for performance during fuzzing"); + } + else + { + EndNumberBulletListItem( HtmlTokenId::NONE, false );// close <LI>/<LH> and don't set a template + NewNumberBulletListItem( nToken ); + } + + ++m_nListItems; + + break; + case HtmlTokenId::LI_OFF: + case HtmlTokenId::LISTHEADER_OFF: + EndNumberBulletListItem( nToken, false ); + break; + + // Attribute : + case HtmlTokenId::ITALIC_ON: + { + SvxPostureItem aPosture( ITALIC_NORMAL, RES_CHRATR_POSTURE ); + SvxPostureItem aPostureCJK( ITALIC_NORMAL, RES_CHRATR_CJK_POSTURE ); + SvxPostureItem aPostureCTL( ITALIC_NORMAL, RES_CHRATR_CTL_POSTURE ); + NewStdAttr( HtmlTokenId::ITALIC_ON, + &m_xAttrTab->pItalic, aPosture, + &m_xAttrTab->pItalicCJK, &aPostureCJK, + &m_xAttrTab->pItalicCTL, &aPostureCTL ); + } + break; + + case HtmlTokenId::BOLD_ON: + { + SvxWeightItem aWeight( WEIGHT_BOLD, RES_CHRATR_WEIGHT ); + SvxWeightItem aWeightCJK( WEIGHT_BOLD, RES_CHRATR_CJK_WEIGHT ); + SvxWeightItem aWeightCTL( WEIGHT_BOLD, RES_CHRATR_CTL_WEIGHT ); + NewStdAttr( HtmlTokenId::BOLD_ON, + &m_xAttrTab->pBold, aWeight, + &m_xAttrTab->pBoldCJK, &aWeightCJK, + &m_xAttrTab->pBoldCTL, &aWeightCTL ); + } + break; + + case HtmlTokenId::STRIKE_ON: + case HtmlTokenId::STRIKETHROUGH_ON: + { + NewStdAttr( HtmlTokenId::STRIKE_ON, &m_xAttrTab->pStrike, + SvxCrossedOutItem(STRIKEOUT_SINGLE, RES_CHRATR_CROSSEDOUT) ); + } + break; + + case HtmlTokenId::UNDERLINE_ON: + { + NewStdAttr( HtmlTokenId::UNDERLINE_ON, &m_xAttrTab->pUnderline, + SvxUnderlineItem(LINESTYLE_SINGLE, RES_CHRATR_UNDERLINE) ); + } + break; + + case HtmlTokenId::SUPERSCRIPT_ON: + { + NewStdAttr( HtmlTokenId::SUPERSCRIPT_ON, &m_xAttrTab->pEscapement, + SvxEscapementItem(HTML_ESC_SUPER,HTML_ESC_PROP, RES_CHRATR_ESCAPEMENT) ); + } + break; + + case HtmlTokenId::SUBSCRIPT_ON: + { + NewStdAttr( HtmlTokenId::SUBSCRIPT_ON, &m_xAttrTab->pEscapement, + SvxEscapementItem(HTML_ESC_SUB,HTML_ESC_PROP, RES_CHRATR_ESCAPEMENT) ); + } + break; + + case HtmlTokenId::BLINK_ON: + { + NewStdAttr( HtmlTokenId::BLINK_ON, &m_xAttrTab->pBlink, + SvxBlinkItem( true, RES_CHRATR_BLINK ) ); + } + break; + + case HtmlTokenId::SPAN_ON: + NewStdAttr( HtmlTokenId::SPAN_ON ); + break; + + case HtmlTokenId::ITALIC_OFF: + case HtmlTokenId::BOLD_OFF: + case HtmlTokenId::STRIKE_OFF: + case HtmlTokenId::UNDERLINE_OFF: + case HtmlTokenId::SUPERSCRIPT_OFF: + case HtmlTokenId::SUBSCRIPT_OFF: + case HtmlTokenId::BLINK_OFF: + case HtmlTokenId::SPAN_OFF: + EndTag( nToken ); + break; + + case HtmlTokenId::STRIKETHROUGH_OFF: + EndTag( HtmlTokenId::STRIKE_OFF ); + break; + + case HtmlTokenId::BASEFONT_ON: + NewBasefontAttr(); + break; + case HtmlTokenId::BASEFONT_OFF: + EndBasefontAttr(); + break; + case HtmlTokenId::FONT_ON: + case HtmlTokenId::BIGPRINT_ON: + case HtmlTokenId::SMALLPRINT_ON: + NewFontAttr( nToken ); + break; + case HtmlTokenId::FONT_OFF: + case HtmlTokenId::BIGPRINT_OFF: + case HtmlTokenId::SMALLPRINT_OFF: + EndFontAttr( nToken ); + break; + + case HtmlTokenId::EMPHASIS_ON: + case HtmlTokenId::CITATION_ON: + case HtmlTokenId::STRONG_ON: + case HtmlTokenId::CODE_ON: + case HtmlTokenId::SAMPLE_ON: + case HtmlTokenId::KEYBOARD_ON: + case HtmlTokenId::VARIABLE_ON: + case HtmlTokenId::DEFINSTANCE_ON: + case HtmlTokenId::SHORTQUOTE_ON: + case HtmlTokenId::LANGUAGE_ON: + case HtmlTokenId::AUTHOR_ON: + case HtmlTokenId::PERSON_ON: + case HtmlTokenId::ACRONYM_ON: + case HtmlTokenId::ABBREVIATION_ON: + case HtmlTokenId::INSERTEDTEXT_ON: + case HtmlTokenId::DELETEDTEXT_ON: + + case HtmlTokenId::TELETYPE_ON: + NewCharFormat( nToken ); + break; + + case HtmlTokenId::SDFIELD_ON: + NewField(); + m_bCallNextToken = m_bInField && m_xTable; + break; + + case HtmlTokenId::EMPHASIS_OFF: + case HtmlTokenId::CITATION_OFF: + case HtmlTokenId::STRONG_OFF: + case HtmlTokenId::CODE_OFF: + case HtmlTokenId::SAMPLE_OFF: + case HtmlTokenId::KEYBOARD_OFF: + case HtmlTokenId::VARIABLE_OFF: + case HtmlTokenId::DEFINSTANCE_OFF: + case HtmlTokenId::SHORTQUOTE_OFF: + case HtmlTokenId::LANGUAGE_OFF: + case HtmlTokenId::AUTHOR_OFF: + case HtmlTokenId::PERSON_OFF: + case HtmlTokenId::ACRONYM_OFF: + case HtmlTokenId::ABBREVIATION_OFF: + case HtmlTokenId::INSERTEDTEXT_OFF: + case HtmlTokenId::DELETEDTEXT_OFF: + + case HtmlTokenId::TELETYPE_OFF: + EndTag( nToken ); + break; + + case HtmlTokenId::HEAD_OFF: + if( !m_aStyleSource.isEmpty() ) + { + m_pCSS1Parser->ParseStyleSheet( m_aStyleSource ); + m_aStyleSource.clear(); + } + break; + + case HtmlTokenId::DOCTYPE: + case HtmlTokenId::BODY_OFF: + case HtmlTokenId::HTML_OFF: + case HtmlTokenId::HEAD_ON: + case HtmlTokenId::TITLE_OFF: + break; // don't evaluate further??? + case HtmlTokenId::HTML_ON: + { + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + if( HtmlOptionId::DIR == rOption.GetToken() ) + { + const OUString& rDir = rOption.GetString(); + SfxItemSet aItemSet( m_xDoc->GetAttrPool(), + m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + OUString aDummy; + ParseStyleOptions( aDummy, aDummy, aDummy, aItemSet, + aPropInfo, nullptr, &rDir ); + + m_pCSS1Parser->SetPageDescAttrs( nullptr, &aItemSet ); + break; + } + } + } + break; + + case HtmlTokenId::INPUT: + InsertInput(); + break; + + case HtmlTokenId::TEXTAREA_ON: + NewTextArea(); + m_bCallNextToken = m_bTextArea && m_xTable; + break; + + case HtmlTokenId::SELECT_ON: + NewSelect(); + m_bCallNextToken = m_bSelect && m_xTable; + break; + + case HtmlTokenId::ANCHOR_ON: + NewAnchor(); + break; + + case HtmlTokenId::ANCHOR_OFF: + EndAnchor(); + break; + + case HtmlTokenId::COMMENT: + if( ( aToken.getLength() > 5 ) && ( ! m_bIgnoreHTMLComments ) ) + { + // insert as Post-It + // If there are no space characters right behind + // the <!-- and on front of the -->, leave the comment untouched. + if( ' ' == aToken[ 3 ] && + ' ' == aToken[ aToken.getLength()-3 ] ) + { + std::u16string_view aComment( aToken.subView( 3, aToken.getLength()-5 ) ); + InsertComment(OUString(comphelper::string::strip(aComment, ' '))); + } + else + { + OUString aComment = "<" + aToken + ">"; + InsertComment( aComment ); + } + } + break; + + case HtmlTokenId::MAP_ON: + // Image Maps are read asynchronously: At first only an image map is created + // Areas are processed later. Nevertheless the + // ImageMap is inserted into the IMap-Array, because it might be used + // already. + m_pImageMap = new ImageMap; + if( ParseMapOptions( m_pImageMap) ) + { + if (!m_pImageMaps) + m_pImageMaps.reset( new ImageMaps ); + m_pImageMaps->push_back(std::unique_ptr<ImageMap>(m_pImageMap)); + } + else + { + delete m_pImageMap; + m_pImageMap = nullptr; + } + break; + + case HtmlTokenId::MAP_OFF: + // there is no ImageMap anymore (don't delete IMap, because it's + // already contained in the array!) + m_pImageMap = nullptr; + break; + + case HtmlTokenId::AREA: + if( m_pImageMap ) + ParseAreaOptions( m_pImageMap, m_sBaseURL, SvMacroItemId::OnMouseOver, + SvMacroItemId::OnMouseOut ); + break; + + case HtmlTokenId::FRAMESET_ON: + bInsertUnknown = m_bKeepUnknown; + break; + + case HtmlTokenId::NOFRAMES_ON: + if( IsInHeader() ) + FinishHeader(); + bInsertUnknown = m_bKeepUnknown; + break; + + case HtmlTokenId::UNKNOWNCONTROL_ON: + // Ignore content of unknown token in the header, if the token + // does not start with a '!'. + // (but judging from the code, also if does not start with a '%') + // (and also if we're not somewhere we consider PRE) + if( IsInHeader() && !IsReadPRE() && m_aUnknownToken.isEmpty() && + !sSaveToken.isEmpty() && '!' != sSaveToken[0] && + '%' != sSaveToken[0] ) + m_aUnknownToken = sSaveToken; + [[fallthrough]]; + + default: + bInsertUnknown = m_bKeepUnknown; + break; + } + + if( bGetIDOption ) + InsertIDOption(); + + if( bInsertUnknown ) + { + OUStringBuffer aComment("HTML: <"); + if( (nToken >= HtmlTokenId::ONOFF_START) && isOffToken(nToken) ) + aComment.append("/"); + aComment.append(sSaveToken); + if( !aToken.isEmpty() ) + { + UnescapeToken(); + aComment.append(" " + aToken); + } + aComment.append(">"); + InsertComment( aComment.makeStringAndClear() ); + } + + // if there are temporary paragraph attributes and the + // paragraph isn't empty then the paragraph attributes are final. + if( !m_aParaAttrs.empty() && m_pPam->GetPoint()->GetContentIndex() ) + m_aParaAttrs.clear(); +} + +static void lcl_swhtml_getItemInfo( const HTMLAttr& rAttr, + bool& rScriptDependent, + sal_uInt16& rScriptType ) +{ + switch( rAttr.GetItem().Which() ) + { + case RES_CHRATR_FONT: + case RES_CHRATR_FONTSIZE: + case RES_CHRATR_LANGUAGE: + case RES_CHRATR_POSTURE: + case RES_CHRATR_WEIGHT: + rScriptType = i18n::ScriptType::LATIN; + rScriptDependent = true; + break; + case RES_CHRATR_CJK_FONT: + case RES_CHRATR_CJK_FONTSIZE: + case RES_CHRATR_CJK_LANGUAGE: + case RES_CHRATR_CJK_POSTURE: + case RES_CHRATR_CJK_WEIGHT: + rScriptType = i18n::ScriptType::ASIAN; + rScriptDependent = true; + break; + case RES_CHRATR_CTL_FONT: + case RES_CHRATR_CTL_FONTSIZE: + case RES_CHRATR_CTL_LANGUAGE: + case RES_CHRATR_CTL_POSTURE: + case RES_CHRATR_CTL_WEIGHT: + rScriptType = i18n::ScriptType::COMPLEX; + rScriptDependent = true; + break; + default: + rScriptDependent = false; + break; + } +} + +bool SwHTMLParser::AppendTextNode( SwHTMLAppendMode eMode, bool bUpdateNum ) +{ + // A hard line break at the end always must be removed. + // A second one we replace with paragraph spacing. + sal_Int32 nLFStripped = StripTrailingLF(); + if( (AM_NOSPACE==eMode || AM_SOFTNOSPACE==eMode) && nLFStripped > 1 ) + eMode = AM_SPACE; + + // the hard attributes of this paragraph will never be invalid again + m_aParaAttrs.clear(); + + SwTextNode *pTextNode = (AM_SPACE==eMode || AM_NOSPACE==eMode) ? + m_pPam->GetPoint()->GetNode().GetTextNode() : nullptr; + + if (pTextNode) + { + const SvxULSpaceItem& rULSpace = + pTextNode->SwContentNode::GetAttr( RES_UL_SPACE ); + + bool bChange = AM_NOSPACE==eMode ? rULSpace.GetLower() > 0 + : rULSpace.GetLower() == 0; + + if( bChange ) + { + const SvxULSpaceItem& rCollULSpace = + pTextNode->GetAnyFormatColl().GetULSpace(); + + bool bMayReset = AM_NOSPACE==eMode ? rCollULSpace.GetLower() == 0 + : rCollULSpace.GetLower() > 0; + + if( bMayReset && + rCollULSpace.GetUpper() == rULSpace.GetUpper() ) + { + pTextNode->ResetAttr( RES_UL_SPACE ); + } + else + { + pTextNode->SetAttr( + SvxULSpaceItem( rULSpace.GetUpper(), + AM_NOSPACE==eMode ? 0 : HTML_PARSPACE, RES_UL_SPACE ) ); + } + } + } + m_bNoParSpace = AM_NOSPACE==eMode || AM_SOFTNOSPACE==eMode; + + SwPosition aOldPos( *m_pPam->GetPoint() ); + + bool bRet = m_xDoc->getIDocumentContentOperations().AppendTextNode( *m_pPam->GetPoint() ); + + // split character attributes and maybe set none, + // which are set for the whole paragraph + const sal_Int32 nEndCnt = aOldPos.GetContentIndex(); + const SwPosition& rPos = *m_pPam->GetPoint(); + + HTMLAttr** pHTMLAttributes = reinterpret_cast<HTMLAttr**>(m_xAttrTab.get()); + for (auto nCnt = sizeof(HTMLAttrTable) / sizeof(HTMLAttr*); nCnt--; ++pHTMLAttributes) + { + HTMLAttr *pAttr = *pHTMLAttributes; + if( pAttr && pAttr->GetItem().Which() < RES_PARATR_BEGIN ) + { + bool bWholePara = false; + + while( pAttr ) + { + HTMLAttr *pNext = pAttr->GetNext(); + if( pAttr->GetStartParagraphIdx() < aOldPos.GetNodeIndex() || + (!bWholePara && + pAttr->GetStartParagraph() == aOldPos.GetNode() && + pAttr->GetStartContent() != nEndCnt) ) + { + bWholePara = + pAttr->GetStartParagraph() == aOldPos.GetNode() && + pAttr->GetStartContent() == 0; + + sal_Int32 nStt = pAttr->m_nStartContent; + bool bScript = false; + sal_uInt16 nScriptItem; + bool bInsert = true; + lcl_swhtml_getItemInfo( *pAttr, bScript, + nScriptItem ); + // set previous part + if( bScript ) + { + const SwTextNode *pTextNd = + pAttr->GetStartParagraph().GetNode().GetTextNode(); + OSL_ENSURE( pTextNd, "No text node" ); + if( pTextNd ) + { + const OUString& rText = pTextNd->GetText(); + sal_uInt16 nScriptText = + g_pBreakIt->GetBreakIter()->getScriptType( + rText, pAttr->GetStartContent() ); + sal_Int32 nScriptEnd = g_pBreakIt->GetBreakIter() + ->endOfScript( rText, nStt, nScriptText ); + while (nScriptEnd < nEndCnt && nScriptEnd != -1) + { + if( nScriptItem == nScriptText ) + { + HTMLAttr *pSetAttr = + pAttr->Clone( aOldPos.GetNode(), nScriptEnd ); + pSetAttr->m_nStartContent = nStt; + pSetAttr->ClearPrev(); + if( !pNext || bWholePara ) + { + if (pSetAttr->m_bInsAtStart) + m_aSetAttrTab.push_front( pSetAttr ); + else + m_aSetAttrTab.push_back( pSetAttr ); + } + else + pNext->InsertPrev( pSetAttr ); + } + nStt = nScriptEnd; + nScriptText = g_pBreakIt->GetBreakIter()->getScriptType( + rText, nStt ); + nScriptEnd = g_pBreakIt->GetBreakIter() + ->endOfScript( rText, nStt, nScriptText ); + } + bInsert = nScriptItem == nScriptText; + } + } + if( bInsert ) + { + HTMLAttr *pSetAttr = + pAttr->Clone( aOldPos.GetNode(), nEndCnt ); + pSetAttr->m_nStartContent = nStt; + + // When the attribute is for the whole paragraph, the outer + // attributes aren't effective anymore. Hence it may not be inserted + // in the Prev-List of an outer attribute, because that won't be + // set. That leads to shifting when fields are used. + if( !pNext || bWholePara ) + { + if (pSetAttr->m_bInsAtStart) + m_aSetAttrTab.push_front( pSetAttr ); + else + m_aSetAttrTab.push_back( pSetAttr ); + } + else + pNext->InsertPrev( pSetAttr ); + } + else + { + HTMLAttr *pPrev = pAttr->GetPrev(); + if( pPrev ) + { + // the previous attributes must be set anyway + if( !pNext || bWholePara ) + { + if (pPrev->m_bInsAtStart) + m_aSetAttrTab.push_front( pPrev ); + else + m_aSetAttrTab.push_back( pPrev ); + } + else + pNext->InsertPrev( pPrev ); + } + } + pAttr->ClearPrev(); + } + + pAttr->SetStart( rPos ); + pAttr = pNext; + } + } + } + + if( bUpdateNum ) + { + if( GetNumInfo().GetDepth() ) + { + sal_uInt8 nLvl = GetNumInfo().GetLevel(); + SetNodeNum( nLvl ); + } + else + m_pPam->GetPointNode().GetTextNode()->ResetAttr( RES_PARATR_NUMRULE ); + } + + // We must set the attribute of the paragraph before now (because of JavaScript) + SetAttr(); + + // Now it is time to get rid of all script dependent hints that are + // equal to the settings in the style + SwTextNode *pTextNd = aOldPos.GetNode().GetTextNode(); + OSL_ENSURE( pTextNd, "There is the txt node" ); + size_t nCntAttr = (pTextNd && pTextNd->GetpSwpHints()) + ? pTextNd->GetSwpHints().Count() : 0; + if( nCntAttr ) + { + // These are the end position of all script dependent hints. + // If we find a hint that starts before the current end position, + // we have to set it. If we find a hint that start behind or at + // that position, we have to take the hint value into account. + // If it is equal to the style, or in fact the paragraph value + // for that hint, the hint is removed. Otherwise its end position + // is remembered. + sal_Int32 aEndPos[15] = + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + SwpHints& rHints = pTextNd->GetSwpHints(); + for( size_t i=0; i < nCntAttr; i++ ) + { + SwTextAttr *pHt = rHints.Get( i ); + sal_uInt16 nWhich = pHt->Which(); + sal_Int16 nIdx = 0; + bool bFont = false; + switch( nWhich ) + { + case RES_CHRATR_FONT: + nIdx = 0; + bFont = true; + break; + case RES_CHRATR_FONTSIZE: + nIdx = 1; + break; + case RES_CHRATR_LANGUAGE: + nIdx = 2; + break; + case RES_CHRATR_POSTURE: + nIdx = 3; + break; + case RES_CHRATR_WEIGHT: + nIdx = 4; + break; + case RES_CHRATR_CJK_FONT: + nIdx = 5; + bFont = true; + break; + case RES_CHRATR_CJK_FONTSIZE: + nIdx = 6; + break; + case RES_CHRATR_CJK_LANGUAGE: + nIdx = 7; + break; + case RES_CHRATR_CJK_POSTURE: + nIdx = 8; + break; + case RES_CHRATR_CJK_WEIGHT: + nIdx = 9; + break; + case RES_CHRATR_CTL_FONT: + nIdx = 10; + bFont = true; + break; + case RES_CHRATR_CTL_FONTSIZE: + nIdx = 11; + break; + case RES_CHRATR_CTL_LANGUAGE: + nIdx = 12; + break; + case RES_CHRATR_CTL_POSTURE: + nIdx = 13; + break; + case RES_CHRATR_CTL_WEIGHT: + nIdx = 14; + break; + default: + // Skip to next attribute + continue; + } + const sal_Int32 nStt = pHt->GetStart(); + if( nStt >= aEndPos[nIdx] ) + { + const SfxPoolItem& rItem = + static_cast<const SwContentNode *>(pTextNd)->GetAttr( nWhich ); + if( bFont ? swhtml_css1atr_equalFontItems(rItem,pHt->GetAttr()) + : rItem == pHt->GetAttr() ) + { + // The hint is the same as set in the paragraph and + // therefore, it can be deleted + // CAUTION!!! This WILL delete the hint and it MAY + // also delete the SwpHints!!! To avoid any trouble + // we leave the loop immediately if this is the last + // hint. + pTextNd->DeleteAttribute( pHt ); + if( 1 == nCntAttr ) + break; + i--; + nCntAttr--; + } + else + { + // The hint is different. Therefore all hints within that + // hint have to be ignored. + aEndPos[nIdx] = pHt->GetEnd() ? *pHt->GetEnd() : nStt; + } + } + else + { + // The hint starts before another one ends. + // The hint in this case is not deleted + OSL_ENSURE( pHt->GetEnd() && *pHt->GetEnd() <= aEndPos[nIdx], + "hints aren't nested properly!" ); + } + } + } + + if (!m_xTable && !--m_nParaCnt) + Show(); + + return bRet; +} + +void SwHTMLParser::AddParSpace() +{ + //If it already has ParSpace, return + if( !m_bNoParSpace ) + return; + + m_bNoParSpace = false; + + SwNodeOffset nNdIdx = m_pPam->GetPoint()->GetNodeIndex() - 1; + + SwTextNode *pTextNode = m_xDoc->GetNodes()[nNdIdx]->GetTextNode(); + if( !pTextNode ) + return; + + SvxULSpaceItem rULSpace = + pTextNode->SwContentNode::GetAttr( RES_UL_SPACE ); + if( rULSpace.GetLower() ) + return; + + const SvxULSpaceItem& rCollULSpace = + pTextNode->GetAnyFormatColl().GetULSpace(); + if( rCollULSpace.GetLower() && + rCollULSpace.GetUpper() == rULSpace.GetUpper() ) + { + pTextNode->ResetAttr( RES_UL_SPACE ); + } + else + { + //What I do here, is that I examine the attributes, and if + //I find out, that it's CJK/CTL, then I set the paragraph space + //to the value set in HTML_CJK_PARSPACE/HTML_CTL_PARSPACE. + + bool bIsCJK = false; + bool bIsCTL = false; + + const size_t nCntAttr = pTextNode->GetpSwpHints() + ? pTextNode->GetSwpHints().Count() : 0; + + for(size_t i = 0; i < nCntAttr; ++i) + { + SwTextAttr *const pHt = pTextNode->GetSwpHints().Get(i); + sal_uInt16 const nWhich = pHt->Which(); + if (RES_CHRATR_CJK_FONT == nWhich || + RES_CHRATR_CJK_FONTSIZE == nWhich || + RES_CHRATR_CJK_LANGUAGE == nWhich || + RES_CHRATR_CJK_POSTURE == nWhich || + RES_CHRATR_CJK_WEIGHT == nWhich) + { + bIsCJK = true; + break; + } + if (RES_CHRATR_CTL_FONT == nWhich || + RES_CHRATR_CTL_FONTSIZE == nWhich || + RES_CHRATR_CTL_LANGUAGE == nWhich || + RES_CHRATR_CTL_POSTURE == nWhich || + RES_CHRATR_CTL_WEIGHT == nWhich) + { + bIsCTL = true; + break; + } + } + + if( bIsCTL ) + { + pTextNode->SetAttr( + SvxULSpaceItem( rULSpace.GetUpper(), HTML_CTL_PARSPACE, RES_UL_SPACE ) ); + } + else if( bIsCJK ) + { + pTextNode->SetAttr( + SvxULSpaceItem( rULSpace.GetUpper(), HTML_CJK_PARSPACE, RES_UL_SPACE ) ); + } else { + pTextNode->SetAttr( + SvxULSpaceItem( rULSpace.GetUpper(), HTML_PARSPACE, RES_UL_SPACE ) ); + } + } +} + +void SwHTMLParser::Show() +{ + // Here + // - a EndAction is called, so the document is formatted + // - a Reschedule is called, + // - the own View-Shell is set again + // - and a StartAction is called + + OSL_ENSURE( SvParserState::Working==eState, "Show not in working state - That can go wrong" ); + SwViewShell *pOldVSh = CallEndAction(); + + Application::Reschedule(); + + if( ( m_xDoc->GetDocShell() && m_xDoc->GetDocShell()->IsAbortingImport() ) + || 1 == m_xDoc->getReferenceCount() ) + { + // was the import aborted by SFX? + eState = SvParserState::Error; + } + + // Fetch the SwViewShell again, as it could be destroyed in Reschedule. + SwViewShell *pVSh = CallStartAction( pOldVSh ); + + // is the current node not visible anymore, then we use a bigger increment + if( pVSh ) + { + m_nParaCnt = (m_pPam->GetPoint()->GetNode().IsInVisibleArea(pVSh)) + ? 5 : 50; + } +} + +void SwHTMLParser::ShowStatline() +{ + // Here + // - a Reschedule is called, so it can be scrolled + // - the own View-Shell is set again + // - a StartAction/EndAction is called, when there was scrolling. + + OSL_ENSURE( SvParserState::Working==eState, "ShowStatLine not in working state - That can go wrong" ); + + // scroll bar + if (m_xProgress) + { + m_xProgress->Update(rInput.Tell()); + CheckActionViewShell(); + } + else + { + Application::Reschedule(); + + if( ( m_xDoc->GetDocShell() && m_xDoc->GetDocShell()->IsAbortingImport() ) + || 1 == m_xDoc->getReferenceCount() ) + // was the import aborted by SFX? + eState = SvParserState::Error; + + SwViewShell *pVSh = CheckActionViewShell(); + if( pVSh && pVSh->HasInvalidRect() ) + { + CallEndAction( false, false ); + CallStartAction( pVSh, false ); + } + } +} + +SwViewShell *SwHTMLParser::CallStartAction( SwViewShell *pVSh, bool bChkPtr ) +{ + OSL_ENSURE( !m_pActionViewShell, "CallStartAction: SwViewShell already set" ); + + if( !pVSh || bChkPtr ) + { +#if OSL_DEBUG_LEVEL > 0 + SwViewShell *pOldVSh = pVSh; +#endif + pVSh = m_xDoc->getIDocumentLayoutAccess().GetCurrentViewShell(); +#if OSL_DEBUG_LEVEL > 0 + OSL_ENSURE( !pVSh || !pOldVSh || pOldVSh == pVSh, "CallStartAction: Who swapped the SwViewShell?" ); + if( pOldVSh && !pVSh ) + pVSh = nullptr; +#endif + } + m_pActionViewShell = pVSh; + + if( m_pActionViewShell ) + { + if( auto pEditShell = dynamic_cast< SwEditShell *>( m_pActionViewShell ) ) + pEditShell->StartAction(); + else + m_pActionViewShell->StartAction(); + } + + return m_pActionViewShell; +} + +SwViewShell *SwHTMLParser::CallEndAction( bool bChkAction, bool bChkPtr ) +{ + if( bChkPtr ) + { + SwViewShell *pVSh = m_xDoc->getIDocumentLayoutAccess().GetCurrentViewShell(); + OSL_ENSURE( !pVSh || m_pActionViewShell == pVSh, + "CallEndAction: Who swapped the SwViewShell?" ); +#if OSL_DEBUG_LEVEL > 0 + if( m_pActionViewShell && !pVSh ) + pVSh = nullptr; +#endif + if( pVSh != m_pActionViewShell ) + m_pActionViewShell = nullptr; + } + + if( !m_pActionViewShell || (bChkAction && !m_pActionViewShell->ActionPend()) ) + return m_pActionViewShell; + + if (SwEditShell* pEditShell = dynamic_cast<SwEditShell*>(m_pActionViewShell)) + { + // Already scrolled?, then make sure that the view doesn't move! + const bool bOldLock = m_pActionViewShell->IsViewLocked(); + m_pActionViewShell->LockView( true ); + pEditShell->EndAction(); + m_pActionViewShell->LockView( bOldLock ); + + // bChkJumpMark is only set when the object was also found + if( m_bChkJumpMark ) + { + const Point aVisSttPos( DOCUMENTBORDER, DOCUMENTBORDER ); + if( GetMedium() && aVisSttPos == m_pActionViewShell->VisArea().Pos() ) + ::JumpToSwMark( m_pActionViewShell, + GetMedium()->GetURLObject().GetMark() ); + m_bChkJumpMark = false; + } + } + else + m_pActionViewShell->EndAction(); + + // if the parser holds the last reference to the document, then we can + // abort here and set an error. + if( 1 == m_xDoc->getReferenceCount() ) + { + eState = SvParserState::Error; + } + + SwViewShell *pVSh = m_pActionViewShell; + m_pActionViewShell = nullptr; + + return pVSh; +} + +SwViewShell *SwHTMLParser::CheckActionViewShell() +{ + SwViewShell *pVSh = m_xDoc->getIDocumentLayoutAccess().GetCurrentViewShell(); + OSL_ENSURE( !pVSh || m_pActionViewShell == pVSh, + "CheckActionViewShell: Who has swapped SwViewShell?" ); +#if OSL_DEBUG_LEVEL > 0 + if( m_pActionViewShell && !pVSh ) + pVSh = nullptr; +#endif + if( pVSh != m_pActionViewShell ) + m_pActionViewShell = nullptr; + + return m_pActionViewShell; +} + +SwHTMLFrameFormatListener::SwHTMLFrameFormatListener(SwFrameFormat* pFrameFormat) + : m_pFrameFormat(pFrameFormat) +{ + StartListening(m_pFrameFormat->GetNotifier()); +} + +void SwHTMLFrameFormatListener::Notify(const SfxHint& rHint) +{ + if (rHint.GetId() == SfxHintId::Dying) + m_pFrameFormat = nullptr; +} + +void SwHTMLParser::SetAttr_( bool bChkEnd, bool bBeforeTable, + std::deque<std::unique_ptr<HTMLAttr>> *pPostIts ) +{ + SwPaM aAttrPam( *m_pPam->GetPoint() ); + const SwPosition& rEndPos = *m_pPam->GetPoint(); + const sal_Int32 nEndCnt = m_pPam->GetPoint()->GetContentIndex(); + HTMLAttr* pAttr; + SwContentNode* pCNd; + + std::vector<std::unique_ptr<HTMLAttr>> aFields; + + for( auto n = m_aSetAttrTab.size(); n; ) + { + pAttr = m_aSetAttrTab[ --n ]; + sal_uInt16 nWhich = pAttr->m_pItem->Which(); + + SwNodeOffset nEndParaIdx = pAttr->GetEndParagraphIdx(); + bool bSetAttr; + if( bChkEnd ) + { + // Set character attribute with end early on, so set them still in + // the current paragraph (because of JavaScript and various "chats"(?)). + // This shouldn't be done for attributes which are used for + // the whole paragraph, because they could be from a paragraph style + // which can't be set. Because the attributes are inserted with + // SETATTR_DONTREPLACE, they should be able to be set later. + bSetAttr = ( nEndParaIdx < rEndPos.GetNodeIndex() && + ((RES_MARGIN_FIRSTLINE != nWhich && RES_MARGIN_TEXTLEFT != nWhich) || !GetNumInfo().GetNumRule()) ) || + ( !pAttr->IsLikePara() && + nEndParaIdx == rEndPos.GetNodeIndex() && + pAttr->GetEndContent() < nEndCnt && + (isCHRATR(nWhich) || isTXTATR_WITHEND(nWhich)) ) || + ( bBeforeTable && + nEndParaIdx == rEndPos.GetNodeIndex() && + !pAttr->GetEndContent() ); + } + else + { + // Attributes in body nodes array section shouldn't be set if we are in a + // special nodes array section, but vice versa it's possible. + SwNodeOffset nEndOfIcons = m_xDoc->GetNodes().GetEndOfExtras().GetIndex(); + bSetAttr = nEndParaIdx < rEndPos.GetNodeIndex() || + rEndPos.GetNodeIndex() > nEndOfIcons || + nEndParaIdx <= nEndOfIcons; + } + + if( bSetAttr ) + { + // The attribute shouldn't be in the list of temporary paragraph + // attributes, because then it would be deleted. + while( !m_aParaAttrs.empty() ) + { + OSL_ENSURE( pAttr != m_aParaAttrs.back(), + "SetAttr: Attribute must not yet be set" ); + m_aParaAttrs.pop_back(); + } + + // then set it + m_aSetAttrTab.erase( m_aSetAttrTab.begin() + n ); + + while( pAttr ) + { + HTMLAttr *pPrev = pAttr->GetPrev(); + if( !pAttr->m_bValid ) + { + // invalid attributes can be deleted + delete pAttr; + pAttr = pPrev; + continue; + } + + pCNd = pAttr->m_nStartPara.GetNode().GetContentNode(); + if( !pCNd ) + { + // because of the awful deleting of nodes an index can also + // point to an end node :-( + if ( (pAttr->GetStartParagraph() == pAttr->GetEndParagraph()) && + !isTXTATR_NOEND(nWhich) ) + { + // when the end index also points to the node, we don't + // need to set attributes anymore, except if it's a text attribute. + delete pAttr; + pAttr = pPrev; + continue; + } + pCNd = m_xDoc->GetNodes().GoNext( &(pAttr->m_nStartPara) ); + if( pCNd ) + pAttr->m_nStartContent = 0; + else + { + OSL_ENSURE( false, "SetAttr: GoNext() failed!" ); + delete pAttr; + pAttr = pPrev; + continue; + } + } + + // because of the deleting of BRs the start index can also + // point behind the end the text + if( pAttr->m_nStartContent > pCNd->Len() ) + pAttr->m_nStartContent = pCNd->Len(); + aAttrPam.GetPoint()->Assign( *pCNd, pAttr->m_nStartContent ); + + aAttrPam.SetMark(); + if ( (pAttr->GetStartParagraph() != pAttr->GetEndParagraph()) && + !isTXTATR_NOEND(nWhich) ) + { + pCNd = pAttr->m_nEndPara.GetNode().GetContentNode(); + if( !pCNd ) + { + pCNd = SwNodes::GoPrevious( &(pAttr->m_nEndPara) ); + if( pCNd ) + pAttr->m_nEndContent = pCNd->Len(); + else + { + OSL_ENSURE( false, "SetAttr: GoPrevious() failed!" ); + aAttrPam.DeleteMark(); + delete pAttr; + pAttr = pPrev; + continue; + } + } + } + else if( pAttr->IsLikePara() ) + { + pAttr->m_nEndContent = pCNd->Len(); + } + + // because of the deleting of BRs the start index can also + // point behind the end the text + if( pAttr->m_nEndContent > pCNd->Len() ) + pAttr->m_nEndContent = pCNd->Len(); + + aAttrPam.GetPoint()->Assign( *pCNd, pAttr->m_nEndContent ); + if( bBeforeTable && + aAttrPam.GetPoint()->GetNodeIndex() == + rEndPos.GetNodeIndex() ) + { + // If we're before inserting a table and the attribute ends + // in the current node, then we must end it in the previous + // node or discard it, if it starts in that node. + if( nWhich != RES_BREAK && nWhich != RES_PAGEDESC && + !isTXTATR_NOEND(nWhich) ) + { + if( aAttrPam.GetMark()->GetNodeIndex() != + rEndPos.GetNodeIndex() ) + { + OSL_ENSURE( !aAttrPam.GetPoint()->GetContentIndex(), + "Content-Position before table not 0???" ); + aAttrPam.Move( fnMoveBackward ); + } + else + { + aAttrPam.DeleteMark(); + delete pAttr; + pAttr = pPrev; + continue; + } + } + } + + switch( nWhich ) + { + case RES_FLTR_BOOKMARK: // insert bookmark + { + const OUString sName( static_cast<SfxStringItem*>(pAttr->m_pItem.get())->GetValue() ); + IDocumentMarkAccess* const pMarkAccess = m_xDoc->getIDocumentMarkAccess(); + IDocumentMarkAccess::const_iterator_t ppBkmk = pMarkAccess->findMark( sName ); + if( ppBkmk != pMarkAccess->getAllMarksEnd() && + (*ppBkmk)->GetMarkStart() == *aAttrPam.GetPoint() ) + break; // do not generate duplicates on this position + aAttrPam.DeleteMark(); + const ::sw::mark::IMark* const pNewMark = pMarkAccess->makeMark( + aAttrPam, + sName, + IDocumentMarkAccess::MarkType::BOOKMARK, + ::sw::mark::InsertMode::New); + + // jump to bookmark + if( JumpToMarks::Mark == m_eJumpTo && pNewMark->GetName() == m_sJmpMark ) + { + m_bChkJumpMark = true; + m_eJumpTo = JumpToMarks::NONE; + } + } + break; + case RES_TXTATR_FIELD: + case RES_TXTATR_ANNOTATION: + case RES_TXTATR_INPUTFIELD: + { + SwFieldIds nFieldWhich = + pPostIts + ? static_cast<const SwFormatField *>(pAttr->m_pItem.get())->GetField()->GetTyp()->Which() + : SwFieldIds::Database; + if( pPostIts && (SwFieldIds::Postit == nFieldWhich || + SwFieldIds::Script == nFieldWhich) ) + { + pPostIts->emplace_front( pAttr ); + } + else + { + aFields.emplace_back( pAttr); + } + } + aAttrPam.DeleteMark(); + pAttr = pPrev; + continue; + + // tdf#94088 expand RES_BACKGROUND to the new fill attribute + // definitions in the range [XATTR_FILL_FIRST .. XATTR_FILL_LAST]. + // This is the right place in the future if the adapted fill attributes + // may be handled more directly in HTML import to handle them. + case RES_BACKGROUND: + { + const SvxBrushItem& rBrush = static_cast< SvxBrushItem& >(*pAttr->m_pItem); + SfxItemSetFixed<XATTR_FILL_FIRST, XATTR_FILL_LAST> aNewSet(m_xDoc->GetAttrPool()); + + setSvxBrushItemAsFillAttributesToTargetSet(rBrush, aNewSet); + m_xDoc->getIDocumentContentOperations().InsertItemSet(aAttrPam, aNewSet, SetAttrMode::DONTREPLACE); + break; + } + + case RES_LR_SPACE: + assert(false); + break; + + case RES_MARGIN_FIRSTLINE: + case RES_MARGIN_TEXTLEFT: + case RES_MARGIN_RIGHT: + if( aAttrPam.GetPoint()->GetNodeIndex() == + aAttrPam.GetMark()->GetNodeIndex()) + { + // because of numbering set this attribute directly at node + pCNd->SetAttr( *pAttr->m_pItem ); + break; + } + OSL_ENSURE( false, + "LRSpace set over multiple paragraphs!" ); + [[fallthrough]]; // (shouldn't reach this point anyway) + default: + + // maybe jump to a bookmark + if( RES_TXTATR_INETFMT == nWhich && + JumpToMarks::Mark == m_eJumpTo && + m_sJmpMark == static_cast<SwFormatINetFormat*>(pAttr->m_pItem.get())->GetName() ) + { + m_bChkJumpMark = true; + m_eJumpTo = JumpToMarks::NONE; + } + + m_xDoc->getIDocumentContentOperations().InsertPoolItem( aAttrPam, *pAttr->m_pItem, SetAttrMode::DONTREPLACE ); + } + aAttrPam.DeleteMark(); + + delete pAttr; + pAttr = pPrev; + } + } + } + + for( auto n = m_aMoveFlyFrames.size(); n; ) + { + SwFrameFormat *pFrameFormat = m_aMoveFlyFrames[--n]->GetFrameFormat(); + if (!pFrameFormat) + { + SAL_WARN("sw.html", "SwFrameFormat deleted during import"); + m_aMoveFlyFrames.erase( m_aMoveFlyFrames.begin() + n ); + m_aMoveFlyCnts.erase( m_aMoveFlyCnts.begin() + n ); + continue; + } + + const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor(); + OSL_ENSURE( RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId(), + "Only At-Para flys need special handling" ); + SwNodeOffset nFlyParaIdx = rAnchor.GetAnchorNode()->GetIndex(); + bool bMoveFly; + if( bChkEnd ) + { + bMoveFly = nFlyParaIdx < rEndPos.GetNodeIndex() || + ( nFlyParaIdx == rEndPos.GetNodeIndex() && + m_aMoveFlyCnts[n] < nEndCnt ); + } + else + { + SwNodeOffset nEndOfIcons = m_xDoc->GetNodes().GetEndOfExtras().GetIndex(); + bMoveFly = nFlyParaIdx < rEndPos.GetNodeIndex() || + rEndPos.GetNodeIndex() > nEndOfIcons || + nFlyParaIdx <= nEndOfIcons; + } + if( bMoveFly ) + { + pFrameFormat->DelFrames(); + *aAttrPam.GetPoint() = *rAnchor.GetContentAnchor(); + aAttrPam.GetPoint()->SetContent( m_aMoveFlyCnts[n] ); + SwFormatAnchor aAnchor( rAnchor ); + aAnchor.SetType( RndStdIds::FLY_AT_CHAR ); + aAnchor.SetAnchor( aAttrPam.GetPoint() ); + pFrameFormat->SetFormatAttr( aAnchor ); + + const SwFormatHoriOrient& rHoriOri = pFrameFormat->GetHoriOrient(); + if( text::HoriOrientation::LEFT == rHoriOri.GetHoriOrient() ) + { + SwFormatHoriOrient aHoriOri( rHoriOri ); + aHoriOri.SetRelationOrient( text::RelOrientation::CHAR ); + pFrameFormat->SetFormatAttr( aHoriOri ); + } + const SwFormatVertOrient& rVertOri = pFrameFormat->GetVertOrient(); + if( text::VertOrientation::TOP == rVertOri.GetVertOrient() ) + { + SwFormatVertOrient aVertOri( rVertOri ); + aVertOri.SetRelationOrient( text::RelOrientation::CHAR ); + pFrameFormat->SetFormatAttr( aVertOri ); + } + + pFrameFormat->MakeFrames(); + m_aMoveFlyFrames.erase( m_aMoveFlyFrames.begin() + n ); + m_aMoveFlyCnts.erase( m_aMoveFlyCnts.begin() + n ); + } + } + for (auto & field : aFields) + { + pCNd = field->m_nStartPara.GetNode().GetContentNode(); + aAttrPam.GetPoint()->Assign( *pCNd, field->m_nStartContent ); + + if( bBeforeTable && + aAttrPam.GetPoint()->GetNodeIndex() == rEndPos.GetNodeIndex() ) + { + OSL_ENSURE( !bBeforeTable, "Aha, the case does occur" ); + OSL_ENSURE( !aAttrPam.GetPoint()->GetContentIndex(), + "Content-Position before table not 0???" ); + // !!! + aAttrPam.Move( fnMoveBackward ); + } + + m_xDoc->getIDocumentContentOperations().InsertPoolItem( aAttrPam, *field->m_pItem ); + + field.reset(); + } + aFields.clear(); +} + +void SwHTMLParser::NewAttr(const std::shared_ptr<HTMLAttrTable>& rAttrTable, HTMLAttr **ppAttr, const SfxPoolItem& rItem ) +{ + // Font height and font colour as well as escape attributes may not be + // combined. Therefore they're saved in a list and in it the last opened + // attribute is at the beginning and count is always one. For all other + // attributes count is just incremented. + if( *ppAttr ) + { + HTMLAttr *pAttr = new HTMLAttr(*m_pPam->GetPoint(), rItem, ppAttr, rAttrTable); + pAttr->InsertNext( *ppAttr ); + (*ppAttr) = pAttr; + } + else + (*ppAttr) = new HTMLAttr(*m_pPam->GetPoint(), rItem, ppAttr, rAttrTable); +} + +bool SwHTMLParser::EndAttr( HTMLAttr* pAttr, bool bChkEmpty ) +{ + bool bRet = true; + + // The list header is saved in the attribute. + HTMLAttr **ppHead = pAttr->m_ppHead; + + OSL_ENSURE( ppHead, "No list header attribute found!" ); + + // save the current position as end position + const SwPosition* pEndPos = m_pPam->GetPoint(); + sal_Int32 nEndCnt = m_pPam->GetPoint()->GetContentIndex(); + + // Is the last started or an earlier started attribute being ended? + HTMLAttr *pLast = nullptr; + if( ppHead && pAttr != *ppHead ) + { + // The last started attribute isn't being ended + + // Then we look for attribute which was started immediately afterwards, + // which has also not yet been ended (otherwise it would no longer be + // in the list). + pLast = *ppHead; + while( pLast && pLast->GetNext() != pAttr ) + pLast = pLast->GetNext(); + + OSL_ENSURE( pLast, "Attribute not found in own list!" ); + } + + bool bMoveBack = false; + sal_uInt16 nWhich = pAttr->m_pItem->Which(); + if( !nEndCnt && RES_PARATR_BEGIN <= nWhich && + pEndPos->GetNodeIndex() != pAttr->GetStartParagraph().GetIndex() ) + { + // Then move back one position in the content! + bMoveBack = m_pPam->Move( fnMoveBackward ); + nEndCnt = m_pPam->GetPoint()->GetContentIndex(); + } + + // now end the attribute + HTMLAttr *pNext = pAttr->GetNext(); + + bool bInsert; + sal_uInt16 nScriptItem = 0; + bool bScript = false; + // does it have a non-empty range? + if( !bChkEmpty || (RES_PARATR_BEGIN <= nWhich && bMoveBack) || + RES_PAGEDESC == nWhich || RES_BREAK == nWhich || + pEndPos->GetNodeIndex() != pAttr->GetStartParagraph().GetIndex() || + nEndCnt != pAttr->GetStartContent() ) + { + bInsert = true; + // We do some optimization for script dependent attributes here. + if( pEndPos->GetNodeIndex() == pAttr->GetStartParagraph().GetIndex() ) + { + lcl_swhtml_getItemInfo( *pAttr, bScript, nScriptItem ); + } + } + else + { + bInsert = false; + } + + const SwTextNode *pTextNd = (bInsert && bScript) ? + pAttr->GetStartParagraph().GetNode().GetTextNode() : + nullptr; + + if (pTextNd) + { + const OUString& rText = pTextNd->GetText(); + sal_uInt16 nScriptText = g_pBreakIt->GetBreakIter()->getScriptType( + rText, pAttr->GetStartContent() ); + sal_Int32 nScriptEnd = g_pBreakIt->GetBreakIter() + ->endOfScript( rText, pAttr->GetStartContent(), nScriptText ); + while (nScriptEnd < nEndCnt && nScriptEnd != -1) + { + if( nScriptItem == nScriptText ) + { + HTMLAttr *pSetAttr = pAttr->Clone( pEndPos->GetNode(), nScriptEnd ); + pSetAttr->ClearPrev(); + if( pNext ) + pNext->InsertPrev( pSetAttr ); + else + { + if (pSetAttr->m_bInsAtStart) + m_aSetAttrTab.push_front( pSetAttr ); + else + m_aSetAttrTab.push_back( pSetAttr ); + } + } + pAttr->m_nStartContent = nScriptEnd; + nScriptText = g_pBreakIt->GetBreakIter()->getScriptType( + rText, nScriptEnd ); + nScriptEnd = g_pBreakIt->GetBreakIter() + ->endOfScript( rText, nScriptEnd, nScriptText ); + } + bInsert = nScriptItem == nScriptText; + } + if( bInsert ) + { + pAttr->m_nEndPara = pEndPos->GetNode(); + pAttr->m_nEndContent = nEndCnt; + pAttr->m_bInsAtStart = RES_TXTATR_INETFMT != nWhich && + RES_TXTATR_CHARFMT != nWhich; + + if( !pNext ) + { + // No open attributes of that type exists any longer, so all + // can be set. Except they depend on another attribute, then + // they're appended there. + if (pAttr->m_bInsAtStart) + m_aSetAttrTab.push_front( pAttr ); + else + m_aSetAttrTab.push_back( pAttr ); + } + else + { + // There are other open attributes of that type, + // therefore the setting must be postponed. + // Hence the current attribute is added at the end + // of the Prev-List of the successor. + pNext->InsertPrev( pAttr ); + } + } + else + { + // Then don't insert, but delete. Because of the "faking" of styles + // by hard attributing there can be also other empty attributes in the + // Prev-List, which must be set anyway. + HTMLAttr *pPrev = pAttr->GetPrev(); + bRet = false; + delete pAttr; + + if( pPrev ) + { + // The previous attributes must be set anyway. + if( pNext ) + pNext->InsertPrev( pPrev ); + else + { + if (pPrev->m_bInsAtStart) + m_aSetAttrTab.push_front( pPrev ); + else + m_aSetAttrTab.push_back( pPrev ); + } + } + + } + + // If the first attribute of the list was set, then the list header + // must be corrected as well. + if( pLast ) + pLast->m_pNext = pNext; + else if( ppHead ) + *ppHead = pNext; + + if( bMoveBack ) + m_pPam->Move( fnMoveForward ); + + return bRet; +} + +void SwHTMLParser::DeleteAttr( HTMLAttr* pAttr ) +{ + // preliminary paragraph attributes are not allowed here, they could + // be set here and then the pointers become invalid! + OSL_ENSURE(m_aParaAttrs.empty(), + "Danger: there are non-final paragraph attributes"); + m_aParaAttrs.clear(); + + // The list header is saved in the attribute + HTMLAttr **ppHead = pAttr->m_ppHead; + + OSL_ENSURE( ppHead, "no list header attribute found!" ); + + // Is the last started or an earlier started attribute being removed? + HTMLAttr *pLast = nullptr; + if( ppHead && pAttr != *ppHead ) + { + // The last started attribute isn't being ended + + // Then we look for attribute which was started immediately afterwards, + // which has also not yet been ended (otherwise it would no longer be + // in the list). + pLast = *ppHead; + while( pLast && pLast->GetNext() != pAttr ) + pLast = pLast->GetNext(); + + OSL_ENSURE( pLast, "Attribute not found in own list!" ); + } + + // now delete the attribute + HTMLAttr *pNext = pAttr->GetNext(); + HTMLAttr *pPrev = pAttr->GetPrev(); + //hold ref to xAttrTab until end of scope to ensure *ppHead validity + std::shared_ptr<HTMLAttrTable> xKeepAlive(pAttr->m_xAttrTab); + delete pAttr; + + if( pPrev ) + { + // The previous attributes must be set anyway. + if( pNext ) + pNext->InsertPrev( pPrev ); + else + { + if (pPrev->m_bInsAtStart) + m_aSetAttrTab.push_front( pPrev ); + else + m_aSetAttrTab.push_back( pPrev ); + } + } + + // If the first attribute of the list was deleted, then the list header + // must be corrected as well. + if( pLast ) + pLast->m_pNext = pNext; + else if( ppHead ) + *ppHead = pNext; +} + +void SwHTMLParser::SaveAttrTab(std::shared_ptr<HTMLAttrTable> const & rNewAttrTab) +{ + // preliminary paragraph attributes are not allowed here, they could + // be set here and then the pointers become invalid! + OSL_ENSURE(m_aParaAttrs.empty(), + "Danger: there are non-final paragraph attributes"); + m_aParaAttrs.clear(); + + HTMLAttr** pHTMLAttributes = reinterpret_cast<HTMLAttr**>(m_xAttrTab.get()); + HTMLAttr** pSaveAttributes = reinterpret_cast<HTMLAttr**>(rNewAttrTab.get()); + + for (auto nCnt = sizeof(HTMLAttrTable) / sizeof(HTMLAttr*); nCnt--; ++pHTMLAttributes, ++pSaveAttributes) + { + *pSaveAttributes = *pHTMLAttributes; + + HTMLAttr *pAttr = *pSaveAttributes; + while (pAttr) + { + pAttr->SetHead(pSaveAttributes, rNewAttrTab); + pAttr = pAttr->GetNext(); + } + + *pHTMLAttributes = nullptr; + } +} + +void SwHTMLParser::SplitAttrTab( std::shared_ptr<HTMLAttrTable> const & rNewAttrTab, + bool bMoveEndBack ) +{ + // preliminary paragraph attributes are not allowed here, they could + // be set here and then the pointers become invalid! + OSL_ENSURE(m_aParaAttrs.empty(), + "Danger: there are non-final paragraph attributes"); + m_aParaAttrs.clear(); + + SwNodeIndex nEndIdx( m_pPam->GetPoint()->GetNode() ); + + // close all still open attributes and re-open them after the table + HTMLAttr** pHTMLAttributes = reinterpret_cast<HTMLAttr**>(m_xAttrTab.get()); + HTMLAttr** pSaveAttributes = reinterpret_cast<HTMLAttr**>(rNewAttrTab.get()); + bool bSetAttr = true; + const sal_Int32 nSttCnt = m_pPam->GetPoint()->GetContentIndex(); + sal_Int32 nEndCnt = nSttCnt; + + if( bMoveEndBack ) + { + SwNodeOffset nOldEnd = nEndIdx.GetIndex(); + SwNodeOffset nTmpIdx; + if( ( nTmpIdx = m_xDoc->GetNodes().GetEndOfExtras().GetIndex()) >= nOldEnd || + ( nTmpIdx = m_xDoc->GetNodes().GetEndOfAutotext().GetIndex()) >= nOldEnd ) + { + nTmpIdx = m_xDoc->GetNodes().GetEndOfInserts().GetIndex(); + } + SwContentNode* pCNd = SwNodes::GoPrevious(&nEndIdx); + + // Don't set attributes, when the PaM was moved outside of the content area. + bSetAttr = pCNd && nTmpIdx < nEndIdx.GetIndex(); + + nEndCnt = (bSetAttr ? pCNd->Len() : 0); + } + for (auto nCnt = sizeof(HTMLAttrTable) / sizeof(HTMLAttr*); nCnt--; (++pHTMLAttributes, ++pSaveAttributes)) + { + HTMLAttr *pAttr = *pHTMLAttributes; + *pSaveAttributes = nullptr; + while( pAttr ) + { + HTMLAttr *pNext = pAttr->GetNext(); + HTMLAttr *pPrev = pAttr->GetPrev(); + + if( bSetAttr && + ( pAttr->GetStartParagraphIdx() < nEndIdx.GetIndex() || + (pAttr->GetStartParagraph() == nEndIdx && + pAttr->GetStartContent() != nEndCnt) ) ) + { + // The attribute must be set before the list. We need the + // original and therefore we clone it, because pointer to the + // attribute exist in the other contexts. The Next-List is lost + // in doing so, but the Previous-List is preserved. + HTMLAttr *pSetAttr = pAttr->Clone( nEndIdx.GetNode(), nEndCnt ); + + if( pNext ) + pNext->InsertPrev( pSetAttr ); + else + { + if (pSetAttr->m_bInsAtStart) + m_aSetAttrTab.push_front( pSetAttr ); + else + m_aSetAttrTab.push_back( pSetAttr ); + } + } + else if( pPrev ) + { + // If the attribute doesn't need to be set before the table, then + // the previous attributes must still be set. + if( pNext ) + pNext->InsertPrev( pPrev ); + else + { + if (pPrev->m_bInsAtStart) + m_aSetAttrTab.push_front( pPrev ); + else + m_aSetAttrTab.push_back( pPrev ); + } + } + + // set the start of the attribute anew and break link + pAttr->Reset(m_pPam->GetPoint()->GetNode(), nSttCnt, pSaveAttributes, rNewAttrTab); + + if (*pSaveAttributes) + { + HTMLAttr *pSAttr = *pSaveAttributes; + while( pSAttr->GetNext() ) + pSAttr = pSAttr->GetNext(); + pSAttr->InsertNext( pAttr ); + } + else + *pSaveAttributes = pAttr; + + pAttr = pNext; + } + + *pHTMLAttributes = nullptr; + } +} + +void SwHTMLParser::RestoreAttrTab(std::shared_ptr<HTMLAttrTable> const & rNewAttrTab) +{ + // preliminary paragraph attributes are not allowed here, they could + // be set here and then the pointers become invalid! + OSL_ENSURE(m_aParaAttrs.empty(), + "Danger: there are non-final paragraph attributes"); + m_aParaAttrs.clear(); + + HTMLAttr** pHTMLAttributes = reinterpret_cast<HTMLAttr**>(m_xAttrTab.get()); + HTMLAttr** pSaveAttributes = reinterpret_cast<HTMLAttr**>(rNewAttrTab.get()); + + for (auto nCnt = sizeof(HTMLAttrTable) / sizeof(HTMLAttr*); nCnt--; ++pHTMLAttributes, ++pSaveAttributes) + { + OSL_ENSURE(!*pHTMLAttributes, "The attribute table is not empty!"); + + *pHTMLAttributes = *pSaveAttributes; + + HTMLAttr *pAttr = *pHTMLAttributes; + while (pAttr) + { + OSL_ENSURE( !pAttr->GetPrev() || !pAttr->GetPrev()->m_ppHead, + "Previous attribute has still a header" ); + pAttr->SetHead(pHTMLAttributes, m_xAttrTab); + pAttr = pAttr->GetNext(); + } + + *pSaveAttributes = nullptr; + } +} + +void SwHTMLParser::InsertAttr( const SfxPoolItem& rItem, bool bInsAtStart ) +{ + HTMLAttr* pTmp = new HTMLAttr(*m_pPam->GetPoint(), rItem, nullptr, std::shared_ptr<HTMLAttrTable>()); + if (bInsAtStart) + m_aSetAttrTab.push_front( pTmp ); + else + m_aSetAttrTab.push_back( pTmp ); +} + +void SwHTMLParser::InsertAttrs( std::deque<std::unique_ptr<HTMLAttr>> rAttrs ) +{ + while( !rAttrs.empty() ) + { + std::unique_ptr<HTMLAttr> pAttr = std::move(rAttrs.front()); + InsertAttr( pAttr->GetItem(), false ); + rAttrs.pop_front(); + } +} + +void SwHTMLParser::NewStdAttr( HtmlTokenId nToken ) +{ + OUString aId, aStyle, aLang, aDir; + OUString aClass; + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::LANG: + aLang = rOption.GetString(); + break; + case HtmlOptionId::DIR: + aDir = rOption.GetString(); + break; + default: break; + } + } + + // create a new context + std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(nToken)); + + // parse styles + if( HasStyleOptions( aStyle, aId, aClass, &aLang, &aDir ) ) + { + SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + + if( ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo, &aLang, &aDir ) ) + { + if( HtmlTokenId::SPAN_ON != nToken || aClass.isEmpty() || + !CreateContainer( aClass, aItemSet, aPropInfo, xCntxt.get() ) ) + DoPositioning( aItemSet, aPropInfo, xCntxt.get() ); + InsertAttrs( aItemSet, aPropInfo, xCntxt.get(), true ); + } + } + + // save the context + PushContext(xCntxt); +} + +void SwHTMLParser::NewStdAttr( HtmlTokenId nToken, + HTMLAttr **ppAttr, const SfxPoolItem & rItem, + HTMLAttr **ppAttr2, const SfxPoolItem *pItem2, + HTMLAttr **ppAttr3, const SfxPoolItem *pItem3 ) +{ + OUString aId, aStyle, aClass, aLang, aDir; + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::LANG: + aLang = rOption.GetString(); + break; + case HtmlOptionId::DIR: + aDir = rOption.GetString(); + break; + default: break; + } + } + + // create a new context + std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(nToken)); + + // parse styles + if( HasStyleOptions( aStyle, aId, aClass, &aLang, &aDir ) ) + { + SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + + aItemSet.Put( rItem ); + if( pItem2 ) + aItemSet.Put( *pItem2 ); + if( pItem3 ) + aItemSet.Put( *pItem3 ); + + if( ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo, &aLang, &aDir ) ) + DoPositioning( aItemSet, aPropInfo, xCntxt.get() ); + + InsertAttrs( aItemSet, aPropInfo, xCntxt.get(), true ); + } + else + { + InsertAttr( ppAttr ,rItem, xCntxt.get() ); + if( pItem2 ) + { + OSL_ENSURE( ppAttr2, "missing table entry for item2" ); + InsertAttr( ppAttr2, *pItem2, xCntxt.get() ); + } + if( pItem3 ) + { + OSL_ENSURE( ppAttr3, "missing table entry for item3" ); + InsertAttr( ppAttr3, *pItem3, xCntxt.get() ); + } + } + + // save the context + PushContext(xCntxt); +} + +void SwHTMLParser::EndTag( HtmlTokenId nToken ) +{ + // fetch context + std::unique_ptr<HTMLAttrContext> xCntxt(PopContext(getOnToken(nToken))); + if (xCntxt) + { + // and maybe end the attributes + EndContext(xCntxt.get()); + } +} + +void SwHTMLParser::NewBasefontAttr() +{ + OUString aId, aStyle, aClass, aLang, aDir; + sal_uInt16 nSize = 3; + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::SIZE: + nSize = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + break; + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::LANG: + aLang = rOption.GetString(); + break; + case HtmlOptionId::DIR: + aDir = rOption.GetString(); + break; + default: break; + } + } + + if( nSize < 1 ) + nSize = 1; + + if( nSize > 7 ) + nSize = 7; + + // create a new context + std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(HtmlTokenId::BASEFONT_ON)); + + // parse styles + if( HasStyleOptions( aStyle, aId, aClass, &aLang, &aDir ) ) + { + SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + + //CJK has different defaults + SvxFontHeightItem aFontHeight( m_aFontHeights[nSize-1], 100, RES_CHRATR_FONTSIZE ); + aItemSet.Put( aFontHeight ); + SvxFontHeightItem aFontHeightCJK( m_aFontHeights[nSize-1], 100, RES_CHRATR_CJK_FONTSIZE ); + aItemSet.Put( aFontHeightCJK ); + //Complex type can contain so many types of letters, + //that it's not really worthy to bother, IMO. + //Still, I have set a default. + SvxFontHeightItem aFontHeightCTL( m_aFontHeights[nSize-1], 100, RES_CHRATR_CTL_FONTSIZE ); + aItemSet.Put( aFontHeightCTL ); + + if( ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo, &aLang, &aDir ) ) + DoPositioning( aItemSet, aPropInfo, xCntxt.get() ); + + InsertAttrs( aItemSet, aPropInfo, xCntxt.get(), true ); + } + else + { + SvxFontHeightItem aFontHeight( m_aFontHeights[nSize-1], 100, RES_CHRATR_FONTSIZE ); + InsertAttr( &m_xAttrTab->pFontHeight, aFontHeight, xCntxt.get() ); + SvxFontHeightItem aFontHeightCJK( m_aFontHeights[nSize-1], 100, RES_CHRATR_CJK_FONTSIZE ); + InsertAttr( &m_xAttrTab->pFontHeightCJK, aFontHeightCJK, xCntxt.get() ); + SvxFontHeightItem aFontHeightCTL( m_aFontHeights[nSize-1], 100, RES_CHRATR_CTL_FONTSIZE ); + InsertAttr( &m_xAttrTab->pFontHeightCTL, aFontHeightCTL, xCntxt.get() ); + } + + // save the context + PushContext(xCntxt); + + // save the font size + m_aBaseFontStack.push_back( nSize ); +} + +void SwHTMLParser::EndBasefontAttr() +{ + EndTag( HtmlTokenId::BASEFONT_ON ); + + // avoid stack underflow in tables + if( m_aBaseFontStack.size() > m_nBaseFontStMin ) + m_aBaseFontStack.erase( m_aBaseFontStack.begin() + m_aBaseFontStack.size() - 1 ); +} + +void SwHTMLParser::NewFontAttr( HtmlTokenId nToken ) +{ + sal_uInt16 nBaseSize = + ( m_aBaseFontStack.size() > m_nBaseFontStMin + ? (m_aBaseFontStack[m_aBaseFontStack.size()-1] & FONTSIZE_MASK) + : 3 ); + sal_uInt16 nFontSize = + ( m_aFontStack.size() > m_nFontStMin + ? (m_aFontStack[m_aFontStack.size()-1] & FONTSIZE_MASK) + : nBaseSize ); + + OUString aFace, aId, aStyle, aClass, aLang, aDir; + Color aColor; + sal_uLong nFontHeight = 0; // actual font height to set + sal_uInt16 nSize = 0; // font height in Netscape notation (1-7) + bool bColor = false; + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::SIZE: + if( HtmlTokenId::FONT_ON==nToken && !rOption.GetString().isEmpty() ) + { + sal_Int32 nSSize; + if( '+' == rOption.GetString()[0] || + '-' == rOption.GetString()[0] ) + nSSize = o3tl::saturating_add<sal_Int32>(nBaseSize, rOption.GetSNumber()); + else + nSSize = static_cast<sal_Int32>(rOption.GetNumber()); + + if( nSSize < 1 ) + nSSize = 1; + else if( nSSize > 7 ) + nSSize = 7; + + nSize = o3tl::narrowing<sal_uInt16>(nSSize); + nFontHeight = m_aFontHeights[nSize-1]; + } + break; + case HtmlOptionId::COLOR: + if( HtmlTokenId::FONT_ON==nToken ) + { + rOption.GetColor( aColor ); + bColor = true; + } + break; + case HtmlOptionId::FACE: + if( HtmlTokenId::FONT_ON==nToken ) + aFace = rOption.GetString(); + break; + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::LANG: + aLang = rOption.GetString(); + break; + case HtmlOptionId::DIR: + aDir = rOption.GetString(); + break; + default: break; + } + } + + if( HtmlTokenId::FONT_ON != nToken ) + { + // HTML_BIGPRINT_ON or HTML_SMALLPRINT_ON + + // In headings the current heading sets the font height + // and not BASEFONT. + const SwFormatColl *pColl = GetCurrFormatColl(); + sal_uInt16 nPoolId = pColl ? pColl->GetPoolFormatId() : 0; + if( nPoolId>=RES_POOLCOLL_HEADLINE1 && + nPoolId<=RES_POOLCOLL_HEADLINE6 ) + { + // If the font height in the heading wasn't changed yet, + // then take the one from the style. + if( m_nFontStHeadStart==m_aFontStack.size() ) + nFontSize = static_cast< sal_uInt16 >(6 - (nPoolId - RES_POOLCOLL_HEADLINE1)); + } + else + nPoolId = 0; + + if( HtmlTokenId::BIGPRINT_ON == nToken ) + nSize = ( nFontSize<7 ? nFontSize+1 : 7 ); + else + nSize = ( nFontSize>1 ? nFontSize-1 : 1 ); + + // If possible in headlines we fetch the new font height + // from the style. + if( nPoolId && nSize>=1 && nSize <=6 ) + nFontHeight = + m_pCSS1Parser->GetTextCollFromPool( + RES_POOLCOLL_HEADLINE1+6-nSize )->GetSize().GetHeight(); + else + nFontHeight = m_aFontHeights[nSize-1]; + } + + OSL_ENSURE( !nSize == !nFontHeight, "HTML-Font-Size != Font-Height" ); + + OUString aFontName; + const OUString aStyleName; + FontFamily eFamily = FAMILY_DONTKNOW; // family and pitch, + FontPitch ePitch = PITCH_DONTKNOW; // if not found + rtl_TextEncoding eEnc = osl_getThreadTextEncoding(); + + if( !aFace.isEmpty() && !m_pCSS1Parser->IsIgnoreFontFamily() ) + { + const FontList *pFList = nullptr; + SwDocShell *pDocSh = m_xDoc->GetDocShell(); + if( pDocSh ) + { + const SvxFontListItem *pFListItem = + static_cast<const SvxFontListItem *>(pDocSh->GetItem(SID_ATTR_CHAR_FONTLIST)); + if( pFListItem ) + pFList = pFListItem->GetFontList(); + } + + bool bFound = false; + sal_Int32 nStrPos = 0; + while( nStrPos!= -1 ) + { + OUString aFName = aFace.getToken( 0, ',', nStrPos ); + aFName = comphelper::string::strip(aFName, ' '); + if( !aFName.isEmpty() ) + { + if( !bFound && pFList ) + { + sal_Handle hFont = pFList->GetFirstFontMetric( aFName ); + if( nullptr != hFont ) + { + const FontMetric& rFMetric = FontList::GetFontMetric( hFont ); + if( RTL_TEXTENCODING_DONTKNOW != rFMetric.GetCharSet() ) + { + bFound = true; + if( RTL_TEXTENCODING_SYMBOL == rFMetric.GetCharSet() ) + eEnc = RTL_TEXTENCODING_SYMBOL; + } + } + } + if( !aFontName.isEmpty() ) + aFontName += ";"; + aFontName += aFName; + } + } + } + + // create a new context + std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(nToken)); + + // parse styles + if( HasStyleOptions( aStyle, aId, aClass, &aLang, &aDir ) ) + { + SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + + if( nFontHeight ) + { + SvxFontHeightItem aFontHeight( nFontHeight, 100, RES_CHRATR_FONTSIZE ); + aItemSet.Put( aFontHeight ); + SvxFontHeightItem aFontHeightCJK( nFontHeight, 100, RES_CHRATR_CJK_FONTSIZE ); + aItemSet.Put( aFontHeightCJK ); + SvxFontHeightItem aFontHeightCTL( nFontHeight, 100, RES_CHRATR_CTL_FONTSIZE ); + aItemSet.Put( aFontHeightCTL ); + } + if( bColor ) + aItemSet.Put( SvxColorItem(aColor, RES_CHRATR_COLOR) ); + if( !aFontName.isEmpty() ) + { + SvxFontItem aFont( eFamily, aFontName, aStyleName, ePitch, eEnc, RES_CHRATR_FONT ); + aItemSet.Put( aFont ); + SvxFontItem aFontCJK( eFamily, aFontName, aStyleName, ePitch, eEnc, RES_CHRATR_CJK_FONT ); + aItemSet.Put( aFontCJK ); + SvxFontItem aFontCTL( eFamily, aFontName, aStyleName, ePitch, eEnc, RES_CHRATR_CTL_FONT ); + aItemSet.Put( aFontCTL ); + } + + if( ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo, &aLang, &aDir ) ) + DoPositioning( aItemSet, aPropInfo, xCntxt.get() ); + + InsertAttrs( aItemSet, aPropInfo, xCntxt.get(), true ); + } + else + { + if( nFontHeight ) + { + SvxFontHeightItem aFontHeight( nFontHeight, 100, RES_CHRATR_FONTSIZE ); + InsertAttr( &m_xAttrTab->pFontHeight, aFontHeight, xCntxt.get() ); + SvxFontHeightItem aFontHeightCJK( nFontHeight, 100, RES_CHRATR_CJK_FONTSIZE ); + InsertAttr( &m_xAttrTab->pFontHeight, aFontHeightCJK, xCntxt.get() ); + SvxFontHeightItem aFontHeightCTL( nFontHeight, 100, RES_CHRATR_CTL_FONTSIZE ); + InsertAttr( &m_xAttrTab->pFontHeight, aFontHeightCTL, xCntxt.get() ); + } + if( bColor ) + InsertAttr( &m_xAttrTab->pFontColor, SvxColorItem(aColor, RES_CHRATR_COLOR), xCntxt.get() ); + if( !aFontName.isEmpty() ) + { + SvxFontItem aFont( eFamily, aFontName, aStyleName, ePitch, eEnc, RES_CHRATR_FONT ); + InsertAttr( &m_xAttrTab->pFont, aFont, xCntxt.get() ); + SvxFontItem aFontCJK( eFamily, aFontName, aStyleName, ePitch, eEnc, RES_CHRATR_CJK_FONT ); + InsertAttr( &m_xAttrTab->pFont, aFontCJK, xCntxt.get() ); + SvxFontItem aFontCTL( eFamily, aFontName, aStyleName, ePitch, eEnc, RES_CHRATR_CTL_FONT ); + InsertAttr( &m_xAttrTab->pFont, aFontCTL, xCntxt.get() ); + } + } + + // save the context + PushContext(xCntxt); + + m_aFontStack.push_back( nSize ); +} + +void SwHTMLParser::EndFontAttr( HtmlTokenId nToken ) +{ + EndTag( nToken ); + + // avoid stack underflow in tables + if( m_aFontStack.size() > m_nFontStMin ) + m_aFontStack.erase( m_aFontStack.begin() + m_aFontStack.size() - 1 ); +} + +void SwHTMLParser::NewPara() +{ + if( m_pPam->GetPoint()->GetContentIndex() ) + AppendTextNode( AM_SPACE ); + else + AddParSpace(); + + m_eParaAdjust = SvxAdjust::End; + OUString aId, aStyle, aClass, aLang, aDir; + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::ALIGN: + m_eParaAdjust = rOption.GetEnum( aHTMLPAlignTable, m_eParaAdjust ); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::LANG: + aLang = rOption.GetString(); + break; + case HtmlOptionId::DIR: + aDir = rOption.GetString(); + break; + case HtmlOptionId::XML_SPACE: + if (rOption.GetString() == "preserve") + SetPreserveSpaces(true); + break; + + default: break; + } + } + + // create a new context + std::unique_ptr<HTMLAttrContext> xCntxt( + !aClass.isEmpty() ? new HTMLAttrContext( HtmlTokenId::PARABREAK_ON, + RES_POOLCOLL_TEXT, aClass ) + : new HTMLAttrContext( HtmlTokenId::PARABREAK_ON )); + + // parse styles (Don't consider class. This is only possible as long as none of + // the CSS1 properties of the class must be formatted hard!!!) + if (HasStyleOptions(aStyle, aId, {}, &aLang, &aDir)) + { + SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + + if (ParseStyleOptions(aStyle, aId, OUString(), aItemSet, aPropInfo, &aLang, &aDir)) + { + OSL_ENSURE( aClass.isEmpty() || !m_pCSS1Parser->GetClass( aClass ), + "Class is not considered" ); + DoPositioning( aItemSet, aPropInfo, xCntxt.get() ); + InsertAttrs( aItemSet, aPropInfo, xCntxt.get() ); + + if (aPropInfo.m_bPreserveSpace) + SetPreserveSpaces(true); + } + } + + if( SvxAdjust::End != m_eParaAdjust ) + InsertAttr( &m_xAttrTab->pAdjust, SvxAdjustItem(m_eParaAdjust, RES_PARATR_ADJUST), xCntxt.get() ); + + // and push on stack + PushContext( xCntxt ); + + // set the current style or its attributes + SetTextCollAttrs( !aClass.isEmpty() ? m_aContexts.back().get() : nullptr ); + + // progress bar + ShowStatline(); + + OSL_ENSURE( m_nOpenParaToken == HtmlTokenId::NONE, "Now an open paragraph element will be lost." ); + m_nOpenParaToken = HtmlTokenId::PARABREAK_ON; +} + +void SwHTMLParser::EndPara( bool bReal ) +{ + if (HtmlTokenId::LI_ON==m_nOpenParaToken && m_xTable) + { +#if OSL_DEBUG_LEVEL > 0 + const SwNumRule *pNumRule = m_pPam->GetPointNode().GetTextNode()->GetNumRule(); + OSL_ENSURE( pNumRule, "Where is the NumRule" ); +#endif + } + + // Netscape skips empty paragraphs, we do the same; unless in XHTML mode, which prefers mapping + // the source document to the doc model 1:1 if possible. + if( bReal ) + { + if (m_pPam->GetPoint()->GetContentIndex() || m_bXHTML) + AppendTextNode( AM_SPACE ); + else + AddParSpace(); + } + + // If a DD or DT was open, it's an implied definition list, + // which must be closed now. + if( (m_nOpenParaToken == HtmlTokenId::DT_ON || m_nOpenParaToken == HtmlTokenId::DD_ON) && + m_nDefListDeep) + { + m_nDefListDeep--; + } + + // Pop the context of the stack. It can also be from an + // implied opened definition list. + std::unique_ptr<HTMLAttrContext> xCntxt( + PopContext( m_nOpenParaToken != HtmlTokenId::NONE ? getOnToken(m_nOpenParaToken) : HtmlTokenId::PARABREAK_ON )); + + // close attribute + if (xCntxt) + { + EndContext(xCntxt.get()); + SetAttr(); // because of JavaScript set paragraph attributes as fast as possible + xCntxt.reset(); + } + + // reset the existing style + if( bReal ) + SetTextCollAttrs(); + + m_nOpenParaToken = HtmlTokenId::NONE; + SetPreserveSpaces(false); +} + +void SwHTMLParser::NewHeading( HtmlTokenId nToken ) +{ + m_eParaAdjust = SvxAdjust::End; + + OUString aId, aStyle, aClass, aLang, aDir; + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::ALIGN: + m_eParaAdjust = rOption.GetEnum( aHTMLPAlignTable, m_eParaAdjust ); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::LANG: + aLang = rOption.GetString(); + break; + case HtmlOptionId::DIR: + aDir = rOption.GetString(); + break; + default: break; + } + } + + // open a new paragraph + if( m_pPam->GetPoint()->GetContentIndex() ) + AppendTextNode( AM_SPACE ); + else + AddParSpace(); + + // search for the matching style + sal_uInt16 nTextColl; + switch( nToken ) + { + case HtmlTokenId::HEAD1_ON: nTextColl = RES_POOLCOLL_HEADLINE1; break; + case HtmlTokenId::HEAD2_ON: nTextColl = RES_POOLCOLL_HEADLINE2; break; + case HtmlTokenId::HEAD3_ON: nTextColl = RES_POOLCOLL_HEADLINE3; break; + case HtmlTokenId::HEAD4_ON: nTextColl = RES_POOLCOLL_HEADLINE4; break; + case HtmlTokenId::HEAD5_ON: nTextColl = RES_POOLCOLL_HEADLINE5; break; + case HtmlTokenId::HEAD6_ON: nTextColl = RES_POOLCOLL_HEADLINE6; break; + default: nTextColl = RES_POOLCOLL_STANDARD; break; + } + + // create the context + std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(nToken, nTextColl, aClass)); + + // parse styles (regarding class see also NewPara) + if (HasStyleOptions(aStyle, aId, {}, &aLang, &aDir)) + { + SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + + if (ParseStyleOptions(aStyle, aId, OUString(), aItemSet, aPropInfo, &aLang, &aDir)) + { + OSL_ENSURE( aClass.isEmpty() || !m_pCSS1Parser->GetClass( aClass ), + "Class is not considered" ); + DoPositioning( aItemSet, aPropInfo, xCntxt.get() ); + InsertAttrs( aItemSet, aPropInfo, xCntxt.get() ); + } + } + + if( SvxAdjust::End != m_eParaAdjust ) + InsertAttr( &m_xAttrTab->pAdjust, SvxAdjustItem(m_eParaAdjust, RES_PARATR_ADJUST), xCntxt.get() ); + + // and push on stack + PushContext(xCntxt); + + // set the current style or its attributes + SetTextCollAttrs(m_aContexts.back().get()); + + m_nFontStHeadStart = m_aFontStack.size(); + + // progress bar + ShowStatline(); +} + +void SwHTMLParser::EndHeading() +{ + // open a new paragraph + if( m_pPam->GetPoint()->GetContentIndex() ) + AppendTextNode( AM_SPACE ); + else + AddParSpace(); + + // search context matching the token and fetch it from stack + std::unique_ptr<HTMLAttrContext> xCntxt; + auto nPos = m_aContexts.size(); + while( !xCntxt && nPos>m_nContextStMin ) + { + switch( m_aContexts[--nPos]->GetToken() ) + { + case HtmlTokenId::HEAD1_ON: + case HtmlTokenId::HEAD2_ON: + case HtmlTokenId::HEAD3_ON: + case HtmlTokenId::HEAD4_ON: + case HtmlTokenId::HEAD5_ON: + case HtmlTokenId::HEAD6_ON: + xCntxt = std::move(m_aContexts[nPos]); + m_aContexts.erase( m_aContexts.begin() + nPos ); + break; + default: break; + } + } + + // and now end attributes + if (xCntxt) + { + EndContext(xCntxt.get()); + SetAttr(); // because of JavaScript set paragraph attributes as fast as possible + xCntxt.reset(); + } + + // reset existing style + SetTextCollAttrs(); + + m_nFontStHeadStart = m_nFontStMin; +} + +void SwHTMLParser::NewTextFormatColl( HtmlTokenId nToken, sal_uInt16 nColl ) +{ + OUString aId, aStyle, aClass, aLang, aDir; + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::LANG: + aLang = rOption.GetString(); + break; + case HtmlOptionId::DIR: + aDir = rOption.GetString(); + break; + default: break; + } + } + + // open a new paragraph + SwHTMLAppendMode eMode = AM_NORMAL; + switch( nToken ) + { + case HtmlTokenId::LISTING_ON: + case HtmlTokenId::XMP_ON: + // These both tags will be mapped to the PRE style. For the case that a + // a CLASS exists we will delete it so that we don't get the CLASS of + // the PRE style. + aClass.clear(); + [[fallthrough]]; + case HtmlTokenId::BLOCKQUOTE_ON: + case HtmlTokenId::BLOCKQUOTE30_ON: + case HtmlTokenId::PREFORMTXT_ON: + eMode = AM_SPACE; + break; + case HtmlTokenId::ADDRESS_ON: + eMode = AM_NOSPACE; // ADDRESS can follow on a <P> without </P> + break; + case HtmlTokenId::DT_ON: + case HtmlTokenId::DD_ON: + eMode = AM_SOFTNOSPACE; + break; + default: + OSL_ENSURE( false, "unknown style" ); + break; + } + if( m_pPam->GetPoint()->GetContentIndex() ) + AppendTextNode( eMode ); + else if( AM_SPACE==eMode ) + AddParSpace(); + + // ... and save in a context + std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(nToken, nColl, aClass)); + + // parse styles (regarding class see also NewPara) + if (HasStyleOptions(aStyle, aId, {}, &aLang, &aDir)) + { + SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + + if (ParseStyleOptions(aStyle, aId, OUString(), aItemSet, aPropInfo, &aLang, &aDir)) + { + OSL_ENSURE( aClass.isEmpty() || !m_pCSS1Parser->GetClass( aClass ), + "Class is not considered" ); + DoPositioning( aItemSet, aPropInfo, xCntxt.get() ); + InsertAttrs( aItemSet, aPropInfo, xCntxt.get() ); + } + } + + PushContext(xCntxt); + + // set the new style + SetTextCollAttrs(m_aContexts.back().get()); + + // update progress bar + ShowStatline(); +} + +void SwHTMLParser::EndTextFormatColl( HtmlTokenId nToken ) +{ + SwHTMLAppendMode eMode = AM_NORMAL; + switch( getOnToken(nToken) ) + { + case HtmlTokenId::BLOCKQUOTE_ON: + case HtmlTokenId::BLOCKQUOTE30_ON: + case HtmlTokenId::PREFORMTXT_ON: + case HtmlTokenId::LISTING_ON: + case HtmlTokenId::XMP_ON: + eMode = AM_SPACE; + break; + case HtmlTokenId::ADDRESS_ON: + case HtmlTokenId::DT_ON: + case HtmlTokenId::DD_ON: + eMode = AM_SOFTNOSPACE; + break; + default: + OSL_ENSURE( false, "unknown style" ); + break; + } + if( m_pPam->GetPoint()->GetContentIndex() ) + AppendTextNode( eMode ); + else if( AM_SPACE==eMode ) + AddParSpace(); + + // pop current context of stack + std::unique_ptr<HTMLAttrContext> xCntxt(PopContext(getOnToken(nToken))); + + // and now end attributes + if (xCntxt) + { + EndContext(xCntxt.get()); + SetAttr(); // because of JavaScript set paragraph attributes as fast as possible + xCntxt.reset(); + } + + // reset existing style + SetTextCollAttrs(); +} + +void SwHTMLParser::NewDefList() +{ + OUString aId, aStyle, aClass, aLang, aDir; + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::LANG: + aLang = rOption.GetString(); + break; + case HtmlOptionId::DIR: + aDir = rOption.GetString(); + break; + default: break; + } + } + + // open a new paragraph + bool bSpace = (GetNumInfo().GetDepth() + m_nDefListDeep) == 0; + if( m_pPam->GetPoint()->GetContentIndex() ) + AppendTextNode( bSpace ? AM_SPACE : AM_SOFTNOSPACE ); + else if( bSpace ) + AddParSpace(); + + // one level more + m_nDefListDeep++; + + bool bInDD = false, bNotInDD = false; + auto nPos = m_aContexts.size(); + while( !bInDD && !bNotInDD && nPos>m_nContextStMin ) + { + HtmlTokenId nCntxtToken = m_aContexts[--nPos]->GetToken(); + switch( nCntxtToken ) + { + case HtmlTokenId::DEFLIST_ON: + case HtmlTokenId::DIRLIST_ON: + case HtmlTokenId::MENULIST_ON: + case HtmlTokenId::ORDERLIST_ON: + case HtmlTokenId::UNORDERLIST_ON: + bNotInDD = true; + break; + case HtmlTokenId::DD_ON: + bInDD = true; + break; + default: break; + } + } + + // ... and save in a context + std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(HtmlTokenId::DEFLIST_ON)); + + // in it save also the margins + sal_uInt16 nLeft=0, nRight=0; + short nIndent=0; + GetMarginsFromContext( nLeft, nRight, nIndent ); + + // The indentation, which already results from a DL, correlates with a DT + // on the current level and this correlates to a DD from the previous level. + // For a level >=2 we must add DD distance. + if( !bInDD && m_nDefListDeep > 1 ) + { + + // and the one of the DT-style of the current level + SvxTextLeftMarginItem const& rTextLeftMargin = + m_pCSS1Parser->GetTextFormatColl(RES_POOLCOLL_HTML_DD, OUString()) + ->GetTextLeftMargin(); + nLeft = nLeft + static_cast<sal_uInt16>(rTextLeftMargin.GetTextLeft()); + } + + xCntxt->SetMargins( nLeft, nRight, nIndent ); + + // parse styles + if( HasStyleOptions( aStyle, aId, aClass, &aLang, &aDir ) ) + { + SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + + if( ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo, &aLang, &aDir ) ) + { + DoPositioning( aItemSet, aPropInfo, xCntxt.get() ); + InsertAttrs( aItemSet, aPropInfo, xCntxt.get() ); + } + } + + PushContext(xCntxt); + + // set the attributes of the new style + if( m_nDefListDeep > 1 ) + SetTextCollAttrs(m_aContexts.back().get()); +} + +void SwHTMLParser::EndDefList() +{ + bool bSpace = (GetNumInfo().GetDepth() + m_nDefListDeep) == 1; + if( m_pPam->GetPoint()->GetContentIndex() ) + AppendTextNode( bSpace ? AM_SPACE : AM_SOFTNOSPACE ); + else if( bSpace ) + AddParSpace(); + + // one level less + if( m_nDefListDeep > 0 ) + m_nDefListDeep--; + + // pop current context of stack + std::unique_ptr<HTMLAttrContext> xCntxt(PopContext(HtmlTokenId::DEFLIST_ON)); + + // and now end attributes + if (xCntxt) + { + EndContext(xCntxt.get()); + SetAttr(); // because of JavaScript set paragraph attributes as fast as possible + xCntxt.reset(); + } + + // and set style + SetTextCollAttrs(); +} + +void SwHTMLParser::NewDefListItem( HtmlTokenId nToken ) +{ + // determine if the DD/DT exist in a DL + bool bInDefList = false, bNotInDefList = false; + auto nPos = m_aContexts.size(); + while( !bInDefList && !bNotInDefList && nPos>m_nContextStMin ) + { + HtmlTokenId nCntxtToken = m_aContexts[--nPos]->GetToken(); + switch( nCntxtToken ) + { + case HtmlTokenId::DEFLIST_ON: + bInDefList = true; + break; + case HtmlTokenId::DIRLIST_ON: + case HtmlTokenId::MENULIST_ON: + case HtmlTokenId::ORDERLIST_ON: + case HtmlTokenId::UNORDERLIST_ON: + bNotInDefList = true; + break; + default: break; + } + } + + // if not, then implicitly open a new DL + if( !bInDefList ) + { + m_nDefListDeep++; + OSL_ENSURE( m_nOpenParaToken == HtmlTokenId::NONE, + "Now an open paragraph element will be lost." ); + m_nOpenParaToken = nToken; + } + + NewTextFormatColl( nToken, static_cast< sal_uInt16 >(nToken==HtmlTokenId::DD_ON ? RES_POOLCOLL_HTML_DD + : RES_POOLCOLL_HTML_DT) ); +} + +void SwHTMLParser::EndDefListItem( HtmlTokenId nToken ) +{ + // open a new paragraph + if( nToken == HtmlTokenId::NONE && m_pPam->GetPoint()->GetContentIndex() ) + AppendTextNode( AM_SOFTNOSPACE ); + + // search context matching the token and fetch it from stack + nToken = getOnToken(nToken); + std::unique_ptr<HTMLAttrContext> xCntxt; + auto nPos = m_aContexts.size(); + while( !xCntxt && nPos>m_nContextStMin ) + { + HtmlTokenId nCntxtToken = m_aContexts[--nPos]->GetToken(); + switch( nCntxtToken ) + { + case HtmlTokenId::DD_ON: + case HtmlTokenId::DT_ON: + if( nToken == HtmlTokenId::NONE || nToken == nCntxtToken ) + { + xCntxt = std::move(m_aContexts[nPos]); + m_aContexts.erase( m_aContexts.begin() + nPos ); + } + break; + case HtmlTokenId::DEFLIST_ON: + // don't look at DD/DT outside the current DefList + case HtmlTokenId::DIRLIST_ON: + case HtmlTokenId::MENULIST_ON: + case HtmlTokenId::ORDERLIST_ON: + case HtmlTokenId::UNORDERLIST_ON: + // and also not outside another list + nPos = m_nContextStMin; + break; + default: break; + } + } + + // and now end attributes + if (xCntxt) + { + EndContext(xCntxt.get()); + SetAttr(); // because of JavaScript set paragraph attributes as fast as possible + } +} + +/** + * + * @param bNoSurroundOnly The paragraph contains at least one frame + * without wrapping. + * @param bSurroundOnly The paragraph contains at least one frame + * with wrapping, but none without wrapping. + * + * Otherwise the paragraph contains any frame. + */ +bool SwHTMLParser::HasCurrentParaFlys( bool bNoSurroundOnly, + bool bSurroundOnly ) const +{ + SwNode& rNode = m_pPam->GetPoint()->GetNode(); + + + bool bFound = false; + for(sw::SpzFrameFormat* pFormat: *m_xDoc->GetSpzFrameFormats()) + { + SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); + // A frame was found, when + // - it is paragraph-bound, and + // - is anchored in current paragraph, and + // - every paragraph-bound frame counts, or + // - (only frames without wrapping count and) the frame doesn't have + // a wrapping + SwNode const*const pAnchorNode = pAnchor->GetAnchorNode(); + if (pAnchorNode && + ((RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId())) && + *pAnchorNode == rNode ) + { + if( !(bNoSurroundOnly || bSurroundOnly) ) + { + bFound = true; + break; + } + else + { + // When looking for frames with wrapping, also disregard + // ones with wrap-through. In this case it's (still) HIDDEN-Controls, + // and you don't want to evade those when positioning. + css::text::WrapTextMode eSurround = pFormat->GetSurround().GetSurround(); + if( bNoSurroundOnly ) + { + if( css::text::WrapTextMode_NONE==eSurround ) + { + bFound = true; + break; + } + } + if( bSurroundOnly ) + { + if( css::text::WrapTextMode_NONE==eSurround ) + { + bFound = false; + break; + } + else if( css::text::WrapTextMode_THROUGH!=eSurround ) + { + bFound = true; + // Continue searching: It's possible that some without + // wrapping will follow... + } + } + } + } + } + + return bFound; +} + +// the special methods for inserting of objects + +const SwFormatColl *SwHTMLParser::GetCurrFormatColl() const +{ + const SwContentNode* pCNd = m_pPam->GetPointContentNode(); + return pCNd ? &pCNd->GetAnyFormatColl() : nullptr; +} + +void SwHTMLParser::SetTextCollAttrs( HTMLAttrContext *pContext ) +{ + SwTextFormatColl *pCollToSet = nullptr; // the style to set + SfxItemSet *pItemSet = nullptr; // set of hard attributes + sal_uInt16 nTopColl = pContext ? pContext->GetTextFormatColl() : 0; + const OUString rTopClass = pContext ? pContext->GetClass() : OUString(); + sal_uInt16 nDfltColl = RES_POOLCOLL_TEXT; + + bool bInPRE=false; // some context info + + sal_uInt16 nLeftMargin = 0, nRightMargin = 0; // the margins and + short nFirstLineIndent = 0; // indentations + + auto nDepth = m_aContexts.size(); + if (bFuzzing && nDepth > 128) + { + SAL_WARN("sw.html", "Not applying any more text collection attributes to a deeply nested node for fuzzing performance"); + nDepth = 0; + } + + for (auto i = m_nContextStAttrMin; i < nDepth; ++i) + { + const HTMLAttrContext *pCntxt = m_aContexts[i].get(); + + sal_uInt16 nColl = pCntxt->GetTextFormatColl(); + if( nColl ) + { + // There is a style to set. Then at first we must decide, + // if the style can be set. + bool bSetThis = true; + switch( nColl ) + { + case RES_POOLCOLL_HTML_PRE: + bInPRE = true; + break; + case RES_POOLCOLL_TEXT: + // <TD><P CLASS=xxx> must become TD.xxx + if( nDfltColl==RES_POOLCOLL_TABLE || + nDfltColl==RES_POOLCOLL_TABLE_HDLN ) + nColl = nDfltColl; + break; + case RES_POOLCOLL_HTML_HR: + // also <HR> in <PRE> set as style, otherwise it can't + // be exported anymore + break; + default: + if( bInPRE ) + bSetThis = false; + break; + } + + SwTextFormatColl *pNewColl = + m_pCSS1Parser->GetTextFormatColl( nColl, pCntxt->GetClass() ); + + if( bSetThis ) + { + // If now a different style should be set as previously, the + // previous style must be replaced by hard attribution. + + if( pCollToSet ) + { + // insert the attributes hard, which previous style sets + if( !pItemSet ) + pItemSet = new SfxItemSet( pCollToSet->GetAttrSet() ); + else + { + const SfxItemSet& rCollSet = pCollToSet->GetAttrSet(); + SfxItemSet aItemSet( *rCollSet.GetPool(), + rCollSet.GetRanges() ); + aItemSet.Set( rCollSet ); + pItemSet->Put( aItemSet ); + } + // but remove the attributes, which the current style sets, + // because otherwise they will be overwritten later + pItemSet->Differentiate( pNewColl->GetAttrSet() ); + } + + pCollToSet = pNewColl; + } + else + { + // hard attribution + if( !pItemSet ) + pItemSet = new SfxItemSet( pNewColl->GetAttrSet() ); + else + { + const SfxItemSet& rCollSet = pNewColl->GetAttrSet(); + SfxItemSet aItemSet( *rCollSet.GetPool(), + rCollSet.GetRanges() ); + aItemSet.Set( rCollSet ); + pItemSet->Put( aItemSet ); + } + } + } + else + { + // Maybe a default style exists? + nColl = pCntxt->GetDefaultTextFormatColl(); + if( nColl ) + nDfltColl = nColl; + } + + // if applicable fetch new paragraph indents + if( pCntxt->IsLRSpaceChanged() ) + { + sal_uInt16 nLeft=0, nRight=0; + + pCntxt->GetMargins( nLeft, nRight, nFirstLineIndent ); + nLeftMargin = nLeft; + nRightMargin = nRight; + } + } + + // If in current context a new style should be set, + // its paragraph margins must be inserted in the context. + if( pContext && nTopColl ) + { + // <TD><P CLASS=xxx> must become TD.xxx + if( nTopColl==RES_POOLCOLL_TEXT && + (nDfltColl==RES_POOLCOLL_TABLE || + nDfltColl==RES_POOLCOLL_TABLE_HDLN) ) + nTopColl = nDfltColl; + + const SwTextFormatColl *pTopColl = + m_pCSS1Parser->GetTextFormatColl( nTopColl, rTopClass ); + const SfxItemSet& rItemSet = pTopColl->GetAttrSet(); + if (rItemSet.GetItemIfSet(RES_MARGIN_FIRSTLINE) + || rItemSet.GetItemIfSet(RES_MARGIN_TEXTLEFT) + || rItemSet.GetItemIfSet(RES_MARGIN_RIGHT)) + { + sal_Int32 nLeft = rItemSet.Get(RES_MARGIN_TEXTLEFT).GetTextLeft(); + sal_Int32 nRight = rItemSet.Get(RES_MARGIN_RIGHT).GetRight(); + nFirstLineIndent = rItemSet.Get(RES_MARGIN_FIRSTLINE).GetTextFirstLineOffset(); + + // In Definition lists the margins also contain the margins from the previous levels + if( RES_POOLCOLL_HTML_DD == nTopColl ) + { + auto const*const pColl(m_pCSS1Parser->GetTextFormatColl(RES_POOLCOLL_HTML_DT, OUString())); + nLeft -= pColl->GetTextLeftMargin().GetTextLeft(); + nRight -= pColl->GetRightMargin().GetRight(); + } + else if( RES_POOLCOLL_HTML_DT == nTopColl ) + { + nLeft = 0; + nRight = 0; + } + + // the paragraph margins add up + nLeftMargin = nLeftMargin + static_cast< sal_uInt16 >(nLeft); + nRightMargin = nRightMargin + static_cast< sal_uInt16 >(nRight); + + pContext->SetMargins( nLeftMargin, nRightMargin, + nFirstLineIndent ); + } + if( const SvxULSpaceItem* pULItem = rItemSet.GetItemIfSet(RES_UL_SPACE) ) + { + pContext->SetULSpace( pULItem->GetUpper(), pULItem->GetLower() ); + } + } + + // If no style is set in the context use the text body. + if( !pCollToSet ) + { + pCollToSet = m_pCSS1Parser->GetTextCollFromPool( nDfltColl ); + if( !nLeftMargin ) + { + nLeftMargin = static_cast<sal_uInt16>(pCollToSet->GetTextLeftMargin().GetTextLeft()); + } + if( !nRightMargin ) + { + nRightMargin = static_cast<sal_uInt16>(pCollToSet->GetRightMargin().GetRight()); + } + if( !nFirstLineIndent ) + { + nFirstLineIndent = pCollToSet->GetFirstLineIndent().GetTextFirstLineOffset(); + } + } + + // remove previous hard attribution of paragraph + for( auto pParaAttr : m_aParaAttrs ) + pParaAttr->Invalidate(); + m_aParaAttrs.clear(); + + // set the style + m_xDoc->SetTextFormatColl( *m_pPam, pCollToSet ); + + // if applicable correct the paragraph indent + const SvxFirstLineIndentItem & rFirstLine = pCollToSet->GetFirstLineIndent(); + const SvxTextLeftMarginItem & rTextLeftMargin = pCollToSet->GetTextLeftMargin(); + const SvxRightMarginItem & rRightMargin = pCollToSet->GetRightMargin(); + bool bSetLRSpace = nLeftMargin != rTextLeftMargin.GetTextLeft() || + nFirstLineIndent != rFirstLine.GetTextFirstLineOffset() || + nRightMargin != rRightMargin.GetRight(); + + if( bSetLRSpace ) + { + SvxFirstLineIndentItem firstLine(rFirstLine); + SvxTextLeftMarginItem leftMargin(rTextLeftMargin); + SvxRightMarginItem rightMargin(rRightMargin); + firstLine.SetTextFirstLineOffset(nFirstLineIndent); + leftMargin.SetTextLeft(nLeftMargin); + rightMargin.SetRight(nRightMargin); + if( pItemSet ) + { + pItemSet->Put(firstLine); + pItemSet->Put(leftMargin); + pItemSet->Put(rightMargin); + } + else + { + NewAttr(m_xAttrTab, &m_xAttrTab->pFirstLineIndent, firstLine); + m_xAttrTab->pFirstLineIndent->SetLikePara(); + m_aParaAttrs.push_back(m_xAttrTab->pFirstLineIndent); + EndAttr(m_xAttrTab->pFirstLineIndent, false); + NewAttr(m_xAttrTab, &m_xAttrTab->pTextLeftMargin, leftMargin); + m_xAttrTab->pTextLeftMargin->SetLikePara(); + m_aParaAttrs.push_back(m_xAttrTab->pTextLeftMargin); + EndAttr(m_xAttrTab->pTextLeftMargin, false); + NewAttr(m_xAttrTab, &m_xAttrTab->pRightMargin, rightMargin); + m_xAttrTab->pRightMargin->SetLikePara(); + m_aParaAttrs.push_back(m_xAttrTab->pRightMargin); + EndAttr(m_xAttrTab->pRightMargin, false); + } + } + + // and now set the attributes + if( pItemSet ) + { + InsertParaAttrs( *pItemSet ); + delete pItemSet; + } +} + +void SwHTMLParser::NewCharFormat( HtmlTokenId nToken ) +{ + OUString aId, aStyle, aLang, aDir; + OUString aClass; + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + case HtmlOptionId::LANG: + aLang = rOption.GetString(); + break; + case HtmlOptionId::DIR: + aDir = rOption.GetString(); + break; + default: break; + } + } + + // create a new context + std::unique_ptr<HTMLAttrContext> xCntxt(new HTMLAttrContext(nToken)); + + // set the style and save it in the context + SwCharFormat* pCFormat = m_pCSS1Parser->GetChrFormat( nToken, aClass ); + OSL_ENSURE( pCFormat, "No character format found for token" ); + + // parse styles (regarding class see also NewPara) + if (HasStyleOptions(aStyle, aId, {}, &aLang, &aDir)) + { + SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + + if (ParseStyleOptions(aStyle, aId, OUString(), aItemSet, aPropInfo, &aLang, &aDir)) + { + OSL_ENSURE( aClass.isEmpty() || !m_pCSS1Parser->GetClass( aClass ), + "Class is not considered" ); + DoPositioning( aItemSet, aPropInfo, xCntxt.get() ); + InsertAttrs( aItemSet, aPropInfo, xCntxt.get(), true ); + } + } + + // Character formats are stored in their own stack and can never be inserted + // by styles. Therefore the attribute doesn't exist in CSS1-Which-Range. + if( pCFormat ) + InsertAttr( &m_xAttrTab->pCharFormats, SwFormatCharFormat( pCFormat ), xCntxt.get() ); + + // save the context + PushContext(xCntxt); +} + +void SwHTMLParser::InsertSpacer() +{ + // and if applicable change it via the options + sal_Int16 eVertOri = text::VertOrientation::TOP; + sal_Int16 eHoriOri = text::HoriOrientation::NONE; + Size aSize( 0, 0); + tools::Long nSize = 0; + bool bPercentWidth = false; + bool bPercentHeight = false; + sal_uInt16 nType = HTML_SPTYPE_HORI; + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::TYPE: + rOption.GetEnum( nType, aHTMLSpacerTypeTable ); + break; + case HtmlOptionId::ALIGN: + eVertOri = + rOption.GetEnum( aHTMLImgVAlignTable, + eVertOri ); + eHoriOri = + rOption.GetEnum( aHTMLImgHAlignTable, + eHoriOri ); + break; + case HtmlOptionId::WIDTH: + // First only save as pixel value! + bPercentWidth = (rOption.GetString().indexOf('%') != -1); + aSize.setWidth( static_cast<tools::Long>(rOption.GetNumber()) ); + break; + case HtmlOptionId::HEIGHT: + // First only save as pixel value! + bPercentHeight = (rOption.GetString().indexOf('%') != -1); + aSize.setHeight( static_cast<tools::Long>(rOption.GetNumber()) ); + break; + case HtmlOptionId::SIZE: + // First only save as pixel value! + nSize = rOption.GetNumber(); + break; + default: break; + } + } + + switch( nType ) + { + case HTML_SPTYPE_BLOCK: + { + // create an empty text frame + + // fetch the ItemSet + SfxItemSetFixed<RES_FRMATR_BEGIN, RES_FRMATR_END-1> aFrameSet( m_xDoc->GetAttrPool() ); + if( !IsNewDoc() ) + Reader::ResetFrameFormatAttrs( aFrameSet ); + + // set the anchor and the adjustment + SetAnchorAndAdjustment( eVertOri, eHoriOri, aFrameSet ); + + // and the size of the frame + Size aDfltSz( MINFLY, MINFLY ); + Size aSpace( 0, 0 ); + SfxItemSet aDummyItemSet( m_xDoc->GetAttrPool(), + m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aDummyPropInfo; + + SetFixSize( aSize, aDfltSz, bPercentWidth, bPercentHeight, + aDummyPropInfo, aFrameSet ); + SetSpace( aSpace, aDummyItemSet, aDummyPropInfo, aFrameSet ); + + // protect the content + SvxProtectItem aProtectItem( RES_PROTECT) ; + aProtectItem.SetContentProtect( true ); + aFrameSet.Put( aProtectItem ); + + // create the frame + RndStdIds eAnchorId = + aFrameSet.Get(RES_ANCHOR).GetAnchorId(); + SwFrameFormat *pFlyFormat = m_xDoc->MakeFlySection( eAnchorId, + m_pPam->GetPoint(), &aFrameSet ); + // Possibly create frames and register auto-bound frames. + RegisterFlyFrame( pFlyFormat ); + } + break; + case HTML_SPTYPE_VERT: + if( nSize > 0 ) + { + nSize = o3tl::convert(nSize, o3tl::Length::px, o3tl::Length::twip); + + // set a paragraph margin + SwTextNode *pTextNode = nullptr; + if( !m_pPam->GetPoint()->GetContentIndex() ) + { + // if possible change the bottom paragraph margin + // of previous node + + SetAttr(); // set still open paragraph attributes + + pTextNode = m_xDoc->GetNodes()[m_pPam->GetPoint()->GetNodeIndex()-1] + ->GetTextNode(); + + // If the previous paragraph isn't a text node, then now an + // empty paragraph is created, which already generates a single + // line of spacing. + if( !pTextNode ) + nSize = nSize>HTML_PARSPACE ? nSize-HTML_PARSPACE : 0; + } + + if( pTextNode ) + { + SvxULSpaceItem aULSpace( pTextNode->SwContentNode::GetAttr( RES_UL_SPACE ) ); + aULSpace.SetLower( aULSpace.GetLower() + o3tl::narrowing<sal_uInt16>(nSize) ); + pTextNode->SetAttr( aULSpace ); + } + else + { + NewAttr(m_xAttrTab, &m_xAttrTab->pULSpace, SvxULSpaceItem(0, o3tl::narrowing<sal_uInt16>(nSize), RES_UL_SPACE)); + EndAttr( m_xAttrTab->pULSpace, false ); + + AppendTextNode(); // Don't change spacing! + } + } + break; + case HTML_SPTYPE_HORI: + if( nSize > 0 ) + { + // If the paragraph is still empty, set first line + // indentation, otherwise apply letter spacing over a space. + + nSize = o3tl::convert(nSize, o3tl::Length::px, o3tl::Length::twip); + + if( !m_pPam->GetPoint()->GetContentIndex() ) + { + sal_uInt16 nLeft=0, nRight=0; + short nIndent = 0; + + GetMarginsFromContextWithNumberBullet( nLeft, nRight, nIndent ); + nIndent = nIndent + static_cast<short>(nSize); + + SvxFirstLineIndentItem const firstLine(nIndent, RES_MARGIN_FIRSTLINE); + SvxTextLeftMarginItem const leftMargin(nLeft, RES_MARGIN_TEXTLEFT); + SvxRightMarginItem const rightMargin(nRight, RES_MARGIN_RIGHT); + + NewAttr(m_xAttrTab, &m_xAttrTab->pFirstLineIndent, firstLine); + EndAttr(m_xAttrTab->pFirstLineIndent, false); + NewAttr(m_xAttrTab, &m_xAttrTab->pTextLeftMargin, leftMargin); + EndAttr(m_xAttrTab->pTextLeftMargin, false); + NewAttr(m_xAttrTab, &m_xAttrTab->pRightMargin, rightMargin); + EndAttr(m_xAttrTab->pRightMargin, false); + } + else + { + NewAttr(m_xAttrTab, &m_xAttrTab->pKerning, SvxKerningItem( static_cast<short>(nSize), RES_CHRATR_KERNING )); + m_xDoc->getIDocumentContentOperations().InsertString( *m_pPam, " " ); + EndAttr( m_xAttrTab->pKerning ); + } + } + } +} + +sal_uInt16 SwHTMLParser::ToTwips( sal_uInt16 nPixel ) +{ + return std::min(o3tl::convert(nPixel, o3tl::Length::px, o3tl::Length::twip), + sal_Int64(SAL_MAX_UINT16)); +} + +SwTwips SwHTMLParser::GetCurrentBrowseWidth() +{ + const SwTwips nWidth = SwHTMLTableLayout::GetBrowseWidth( *m_xDoc ); + if( nWidth ) + return nWidth; + + if( !m_aHTMLPageSize.Width() ) + { + const SwFrameFormat& rPgFormat = m_pCSS1Parser->GetMasterPageDesc()->GetMaster(); + + const SwFormatFrameSize& rSz = rPgFormat.GetFrameSize(); + const SvxLRSpaceItem& rLR = rPgFormat.GetLRSpace(); + const SvxULSpaceItem& rUL = rPgFormat.GetULSpace(); + const SwFormatCol& rCol = rPgFormat.GetCol(); + + m_aHTMLPageSize.setWidth( rSz.GetWidth() - rLR.GetLeft() - rLR.GetRight() ); + m_aHTMLPageSize.setHeight( rSz.GetHeight() - rUL.GetUpper() - rUL.GetLower() ); + + if( 1 < rCol.GetNumCols() ) + m_aHTMLPageSize.setWidth( m_aHTMLPageSize.Width() / ( rCol.GetNumCols()) ); + } + + return m_aHTMLPageSize.Width(); +} + +void SwHTMLParser::InsertIDOption() +{ + OUString aId; + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + if( HtmlOptionId::ID==rOption.GetToken() ) + { + aId = rOption.GetString(); + break; + } + } + + if( !aId.isEmpty() ) + InsertBookmark( aId ); +} + +void SwHTMLParser::InsertLineBreak() +{ + OUString aId, aStyle, aClass; // the id of bookmark + SwLineBreakClear eClear = SwLineBreakClear::NONE; + + // then we fetch the options + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::CLEAR: + { + const OUString &rClear = rOption.GetString(); + if( rClear.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_all ) ) + { + eClear = SwLineBreakClear::ALL; + } + else if( rClear.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_left ) ) + { + eClear = SwLineBreakClear::LEFT; + } + else if( rClear.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_right ) ) + { + eClear = SwLineBreakClear::LEFT; + } + } + break; + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::STYLE: + aStyle = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + aClass = rOption.GetString(); + break; + default: break; + } + } + + // parse styles + std::shared_ptr<SvxFormatBreakItem> aBreakItem(std::make_shared<SvxFormatBreakItem>(SvxBreak::NONE, RES_BREAK)); + bool bBreakItem = false; + if( HasStyleOptions( aStyle, aId, aClass ) ) + { + SfxItemSet aItemSet( m_xDoc->GetAttrPool(), m_pCSS1Parser->GetWhichMap() ); + SvxCSS1PropertyInfo aPropInfo; + + if( ParseStyleOptions( aStyle, aId, aClass, aItemSet, aPropInfo ) ) + { + if( m_pCSS1Parser->SetFormatBreak( aItemSet, aPropInfo ) ) + { + aBreakItem.reset(aItemSet.Get(RES_BREAK).Clone()); + bBreakItem = true; + } + if( !aPropInfo.m_aId.isEmpty() ) + InsertBookmark( aPropInfo.m_aId ); + } + } + + if( bBreakItem && SvxBreak::PageAfter == aBreakItem->GetBreak() ) + { + NewAttr(m_xAttrTab, &m_xAttrTab->pBreak, *aBreakItem); + EndAttr( m_xAttrTab->pBreak, false ); + } + + if (!bBreakItem) + { + if (eClear == SwLineBreakClear::NONE) + { + // If no CLEAR could or should be executed, a line break will be inserted + m_xDoc->getIDocumentContentOperations().InsertString(*m_pPam, "\x0A"); + } + else + { + // <BR CLEAR=xxx> is mapped an SwFormatLineBreak. + SwTextNode* pTextNode = m_pPam->GetPointNode().GetTextNode(); + if (pTextNode) + { + SwFormatLineBreak aLineBreak(eClear); + sal_Int32 nPos = m_pPam->GetPoint()->GetContentIndex(); + pTextNode->InsertItem(aLineBreak, nPos, nPos); + } + } + } + else if( m_pPam->GetPoint()->GetContentIndex() ) + { + // If a CLEAR is executed in a non-empty paragraph, then after it + // a new paragraph has to be opened. + // MIB 21.02.97: Here actually we should change the bottom paragraph + // margin to zero. This will fail for something like this <BR ..><P> + // (>Netscape). That's why we don't do it. + AppendTextNode( AM_NOSPACE ); + } + if( bBreakItem && SvxBreak::PageBefore == aBreakItem->GetBreak() ) + { + NewAttr(m_xAttrTab, &m_xAttrTab->pBreak, *aBreakItem); + EndAttr( m_xAttrTab->pBreak, false ); + } +} + +void SwHTMLParser::InsertHorzRule() +{ + sal_uInt16 nSize = 0; + sal_uInt16 nWidth = 0; + + SvxAdjust eAdjust = SvxAdjust::End; + + bool bPercentWidth = false; + bool bNoShade = false; + bool bColor = false; + + Color aColor; + OUString aId; + + // let's fetch the options + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::ID: + aId = rOption.GetString(); + break; + case HtmlOptionId::SIZE: + nSize = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + break; + case HtmlOptionId::WIDTH: + bPercentWidth = (rOption.GetString().indexOf('%') != -1); + nWidth = o3tl::narrowing<sal_uInt16>(rOption.GetNumber()); + if( bPercentWidth && nWidth>=100 ) + { + // the default case are 100% lines (no attributes necessary) + nWidth = 0; + bPercentWidth = false; + } + break; + case HtmlOptionId::ALIGN: + eAdjust = rOption.GetEnum( aHTMLPAlignTable, eAdjust ); + break; + case HtmlOptionId::NOSHADE: + bNoShade = true; + break; + case HtmlOptionId::COLOR: + rOption.GetColor( aColor ); + bColor = true; + break; + default: break; + } + } + + if( m_pPam->GetPoint()->GetContentIndex() ) + AppendTextNode( AM_NOSPACE ); + if( m_nOpenParaToken != HtmlTokenId::NONE ) + EndPara(); + AppendTextNode(); + m_pPam->Move( fnMoveBackward ); + + // ...and save in a context + std::unique_ptr<HTMLAttrContext> xCntxt( + new HTMLAttrContext(HtmlTokenId::HORZRULE, RES_POOLCOLL_HTML_HR, OUString())); + + PushContext(xCntxt); + + // set the new style + SetTextCollAttrs(m_aContexts.back().get()); + + // the hard attributes of the current paragraph will never become invalid + m_aParaAttrs.clear(); + + if( nSize>0 || bColor || bNoShade ) + { + // set line colour and/or width + if( !bColor ) + aColor = COL_GRAY; + + SvxBorderLine aBorderLine( &aColor ); + if( nSize ) + { + tools::Long nPWidth = 0; + tools::Long nPHeight = static_cast<tools::Long>(nSize); + SvxCSS1Parser::PixelToTwip( nPWidth, nPHeight ); + if ( !bNoShade ) + { + aBorderLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE); + } + aBorderLine.SetWidth( nPHeight ); + } + else if( bNoShade ) + { + aBorderLine.SetWidth( SvxBorderLineWidth::Medium ); + } + else + { + aBorderLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE); + aBorderLine.SetWidth(SvxBorderLineWidth::Hairline); + } + + SvxBoxItem aBoxItem(RES_BOX); + aBoxItem.SetLine( &aBorderLine, SvxBoxItemLine::BOTTOM ); + HTMLAttr* pTmp = new HTMLAttr(*m_pPam->GetPoint(), aBoxItem, nullptr, std::shared_ptr<HTMLAttrTable>()); + m_aSetAttrTab.push_back( pTmp ); + } + if( nWidth ) + { + // If we aren't in a table, then the width value will be "faked" with + // paragraph indents. That makes little sense in a table. In order to + // avoid that the line is considered during the width calculation, it + // still gets an appropriate LRSpace-Item. + if (!m_xTable) + { + // fake length and alignment of line above paragraph indents + tools::Long nBrowseWidth = GetCurrentBrowseWidth(); + nWidth = bPercentWidth ? o3tl::narrowing<sal_uInt16>((nWidth*nBrowseWidth) / 100) + : ToTwips( o3tl::narrowing<sal_uInt16>(nBrowseWidth) ); + if( nWidth < MINLAY ) + nWidth = MINLAY; + + const SwFormatColl *pColl = (static_cast<tools::Long>(nWidth) < nBrowseWidth) ? GetCurrFormatColl() : nullptr; + if (pColl) + { + tools::Long nDist = nBrowseWidth - nWidth; + ::std::optional<SvxTextLeftMarginItem> oLeft; + ::std::optional<SvxRightMarginItem> oRight; + + switch( eAdjust ) + { + case SvxAdjust::Right: + oLeft.emplace(o3tl::narrowing<sal_uInt16>(nDist), RES_MARGIN_TEXTLEFT); + break; + case SvxAdjust::Left: + oRight.emplace(o3tl::narrowing<sal_uInt16>(nDist), RES_MARGIN_RIGHT); + break; + case SvxAdjust::Center: + default: + nDist /= 2; + oLeft.emplace(o3tl::narrowing<sal_uInt16>(nDist), RES_MARGIN_TEXTLEFT); + oRight.emplace(o3tl::narrowing<sal_uInt16>(nDist), RES_MARGIN_RIGHT); + break; + } + + if (oLeft) + { + HTMLAttr* pTmp = new HTMLAttr(*m_pPam->GetPoint(), *oLeft, nullptr, std::shared_ptr<HTMLAttrTable>()); + m_aSetAttrTab.push_back( pTmp ); + } + if (oRight) + { + HTMLAttr* pTmp = new HTMLAttr(*m_pPam->GetPoint(), *oRight, nullptr, std::shared_ptr<HTMLAttrTable>()); + m_aSetAttrTab.push_back( pTmp ); + } + } + } + } + + // it's not possible to insert bookmarks in links + if( !aId.isEmpty() ) + InsertBookmark( aId ); + + // pop current context of stack + std::unique_ptr<HTMLAttrContext> xPoppedContext(PopContext(HtmlTokenId::HORZRULE)); + xPoppedContext.reset(); + + m_pPam->Move( fnMoveForward ); + + // and set the current style in the next paragraph + SetTextCollAttrs(); +} + +void SwHTMLParser::ParseMoreMetaOptions() +{ + OUString aName, aContent; + bool bHTTPEquiv = false; + + const HTMLOptions& rHTMLOptions = GetOptions(); + for (size_t i = rHTMLOptions.size(); i; ) + { + const HTMLOption& rOption = rHTMLOptions[--i]; + switch( rOption.GetToken() ) + { + case HtmlOptionId::NAME: + aName = rOption.GetString(); + bHTTPEquiv = false; + break; + case HtmlOptionId::HTTPEQUIV: + aName = rOption.GetString(); + bHTTPEquiv = true; + break; + case HtmlOptionId::CONTENT: + aContent = rOption.GetString(); + break; + default: break; + } + } + + // Here things get a little tricky: We know for sure, that the Doc-Info + // wasn't changed. Therefore it's enough to query for Generator and Refresh + // to find a not processed Token. These are the only ones which won't change + // the Doc-Info. + if( aName.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_META_generator ) || + aName.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_META_refresh ) || + aName.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_META_content_type ) || + aName.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_META_content_script_type ) ) + return; + + aContent = aContent.replaceAll("\r", "").replaceAll("\n", ""); + + if( aName.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_META_sdendnote ) ) + { + FillEndNoteInfo( aContent ); + return; + } + + if( aName.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_META_sdfootnote ) ) + { + FillFootNoteInfo( aContent ); + return; + } + + OUStringBuffer sText( + "HTML: <" + OOO_STRING_SVTOOLS_HTML_meta + " "); + if( bHTTPEquiv ) + sText.append(OOO_STRING_SVTOOLS_HTML_O_httpequiv); + else + sText.append(OOO_STRING_SVTOOLS_HTML_O_name); + sText.append( + "=\"" + aName + + "\" " + OOO_STRING_SVTOOLS_HTML_O_content + "=\"" + + aContent + + "\">"); + + SwPostItField aPostItField( + static_cast<SwPostItFieldType*>(m_xDoc->getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::Postit )), + OUString(), sText.makeStringAndClear(), OUString(), OUString(), DateTime(DateTime::SYSTEM)); + SwFormatField aFormatField( aPostItField ); + InsertAttr( aFormatField, false ); +} + +HTMLAttr::HTMLAttr( const SwPosition& rPos, const SfxPoolItem& rItem, + HTMLAttr **ppHd, std::shared_ptr<HTMLAttrTable> xAttrTab ) : + m_nStartPara( rPos.GetNode() ), + m_nEndPara( rPos.GetNode() ), + m_nStartContent( rPos.GetContentIndex() ), + m_nEndContent(rPos.GetContentIndex() ), + m_bInsAtStart( true ), + m_bLikePara( false ), + m_bValid( true ), + m_pItem( rItem.Clone() ), + m_xAttrTab(std::move( xAttrTab )), + m_pNext( nullptr ), + m_pPrev( nullptr ), + m_ppHead( ppHd ) +{ +} + +HTMLAttr::HTMLAttr( const HTMLAttr &rAttr, const SwNode &rEndPara, + sal_Int32 nEndCnt, HTMLAttr **ppHd, std::shared_ptr<HTMLAttrTable> xAttrTab ) : + m_nStartPara( rAttr.m_nStartPara ), + m_nEndPara( rEndPara ), + m_nStartContent( rAttr.m_nStartContent ), + m_nEndContent( nEndCnt ), + m_bInsAtStart( rAttr.m_bInsAtStart ), + m_bLikePara( rAttr.m_bLikePara ), + m_bValid( rAttr.m_bValid ), + m_pItem( rAttr.m_pItem->Clone() ), + m_xAttrTab(std::move( xAttrTab )), + m_pNext( nullptr ), + m_pPrev( nullptr ), + m_ppHead( ppHd ) +{ +} + +HTMLAttr::~HTMLAttr() +{ +} + +HTMLAttr *HTMLAttr::Clone(const SwNode& rEndPara, sal_Int32 nEndCnt) const +{ + // create the attribute anew with old start position + HTMLAttr *pNew = new HTMLAttr( *this, rEndPara, nEndCnt, m_ppHead, m_xAttrTab ); + + // The Previous-List must be taken over, the Next-List not! + pNew->m_pPrev = m_pPrev; + + return pNew; +} + +void HTMLAttr::Reset(const SwNode& rSttPara, sal_Int32 nSttCnt, + HTMLAttr **ppHd, const std::shared_ptr<HTMLAttrTable>& rAttrTab) +{ + // reset the start (and the end) + m_nStartPara = rSttPara; + m_nStartContent = nSttCnt; + m_nEndPara = rSttPara; + m_nEndContent = nSttCnt; + + // correct the head and nullify link + m_pNext = nullptr; + m_pPrev = nullptr; + m_ppHead = ppHd; + m_xAttrTab = rAttrTab; +} + +void HTMLAttr::InsertPrev( HTMLAttr *pPrv ) +{ + OSL_ENSURE( !pPrv->m_pNext || pPrv->m_pNext == this, + "HTMLAttr::InsertPrev: pNext wrong" ); + pPrv->m_pNext = nullptr; + + OSL_ENSURE( nullptr == pPrv->m_ppHead || m_ppHead == pPrv->m_ppHead, + "HTMLAttr::InsertPrev: ppHead wrong" ); + pPrv->m_ppHead = nullptr; + + HTMLAttr *pAttr = this; + while( pAttr->GetPrev() ) + pAttr = pAttr->GetPrev(); + + pAttr->m_pPrev = pPrv; +} + +bool SwHTMLParser::ParseMetaOptions( + const uno::Reference<document::XDocumentProperties> & i_xDocProps, + SvKeyValueIterator *i_pHeader ) +{ + // always call base ParseMetaOptions, it sets the encoding (#i96700#) + bool ret( HTMLParser::ParseMetaOptions(i_xDocProps, i_pHeader) ); + if (!ret && IsNewDoc()) + { + ParseMoreMetaOptions(); + } + return ret; +} + +// override so we can parse DOCINFO field subtypes INFO[1-4] +void SwHTMLParser::AddMetaUserDefined( OUString const & i_rMetaName ) +{ + // unless we already have 4 names, append the argument to m_InfoNames + OUString* pName // the first empty string in m_InfoNames + (m_InfoNames[0].isEmpty() ? &m_InfoNames[0] : + (m_InfoNames[1].isEmpty() ? &m_InfoNames[1] : + (m_InfoNames[2].isEmpty() ? &m_InfoNames[2] : + (m_InfoNames[3].isEmpty() ? &m_InfoNames[3] : nullptr )))); + if (pName) + { + (*pName) = i_rMetaName; + } +} + +void HTMLReader::SetupFilterOptions() +{ + // Reset state from previous Read() invocation. + m_aNamespace.clear(); + + if (!m_pMedium) + return; + + auto pItem = m_pMedium->GetItemSet().GetItem(SID_FILE_FILTEROPTIONS); + if (!pItem) + return; + + OUString aFilterOptions = pItem->GetValue(); + static constexpr OUString aXhtmlNsKey(u"xhtmlns="_ustr); + if (aFilterOptions.startsWith(aXhtmlNsKey)) + { + OUString aNamespace = aFilterOptions.copy(aXhtmlNsKey.getLength()); + m_aNamespace = aNamespace; + } +} + +namespace +{ + class FontCacheGuard + { + public: + ~FontCacheGuard() + { + FlushFontCache(); + } + }; +} + +bool TestImportHTML(SvStream &rStream) +{ + FontCacheGuard aFontCacheGuard; + HTMLReader aReader; + aReader.m_pStream = &rStream; + + SwGlobals::ensure(); + + SfxObjectShellLock xDocSh(new SwDocShell(SfxObjectCreateMode::INTERNAL)); + xDocSh->DoInitNew(); + SwDoc *pD = static_cast<SwDocShell*>((&xDocSh))->GetDoc(); + + SwPaM aPaM(pD->GetNodes().GetEndOfContent(), SwNodeOffset(-1)); + pD->SetInReading(true); + bool bRet = false; + try + { + bRet = aReader.Read(*pD, OUString(), aPaM, OUString()) == ERRCODE_NONE; + } + catch (const std::runtime_error&) + { + } + catch (const std::out_of_range&) + { + } + pD->SetInReading(false); + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/swhtml.hxx b/sw/source/filter/html/swhtml.hxx new file mode 100644 index 0000000000..33f03ecf3f --- /dev/null +++ b/sw/source/filter/html/swhtml.hxx @@ -0,0 +1,1069 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_FILTER_HTML_SWHTML_HXX +#define INCLUDED_SW_SOURCE_FILTER_HTML_SWHTML_HXX + +#include <config_java.h> + +#include <sfx2/sfxhtml.hxx> +#include <svl/listener.hxx> +#include <svl/macitem.hxx> +#include <svtools/htmltokn.h> +#include <editeng/svxenum.hxx> +#include <rtl/ref.hxx> +#include <rtl/ustrbuf.hxx> +#include <deletelistener.hxx> +#include <fmtftn.hxx> +#include <fltshell.hxx> +#include <swtypes.hxx> +#include <txtftn.hxx> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/form/XFormComponent.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> + +#include <memory> +#include <utility> +#include <vector> +#include <deque> +#include <stack> +#include <set> + +class SfxMedium; +class SfxViewFrame; +class SdrObject; +class SvxMacroTableDtor; +class SwDoc; +class SwPaM; +class SwViewShell; +class SwStartNode; +class SwFormatColl; +class SwField; +class SwHTMLForm_Impl; +class SwApplet_Impl; +struct SwHTMLFootEndNote_Impl; +class HTMLTableCnts; +struct SwPending; +class SvxCSS1PropertyInfo; +struct ImplSVEvent; + +constexpr tools::Long HTML_CJK_PARSPACE = o3tl::toTwips(25, o3tl::Length::mm10); // 2.5mm +constexpr tools::Long HTML_CTL_PARSPACE = o3tl::toTwips(25, o3tl::Length::mm10); // 2.5mm + +constexpr tools::Long HTML_DFLT_IMG_WIDTH = o3tl::toTwips(2, o3tl::Length::cm); // 2cm +constexpr tools::Long HTML_DFLT_IMG_HEIGHT = o3tl::toTwips(1, o3tl::Length::cm); // 1cm + +// some things you often need +extern HTMLOptionEnum<SvxAdjust> const aHTMLPAlignTable[]; +extern HTMLOptionEnum<sal_Int16> const aHTMLImgHAlignTable[]; +extern HTMLOptionEnum<sal_Int16> const aHTMLImgVAlignTable[]; + +// attribute stack: + +class HTMLAttr; +typedef std::deque<HTMLAttr *> HTMLAttrs; + +// Table of attributes: The order here is important: The attributes in the +// beginning of the table will set first in EndAllAttrs. +struct HTMLAttrTable +{ + HTMLAttr* pKeep; // frame attributes + HTMLAttr* pBox; + HTMLAttr* pBrush; + HTMLAttr* pBreak; + HTMLAttr* pPageDesc; + + HTMLAttr* pFirstLineIndent; // paragraph attributes + HTMLAttr* pTextLeftMargin; + HTMLAttr* pRightMargin; + HTMLAttr* pULSpace; + HTMLAttr* pLineSpacing; + HTMLAttr* pAdjust; + HTMLAttr* pDropCap; + HTMLAttr* pSplit; + HTMLAttr* pWidows; + HTMLAttr* pOrphans; + HTMLAttr* pDirection; + + HTMLAttr* pCharFormats; // text attributes + HTMLAttr* pINetFormat; + + HTMLAttr* pBold; // character attributes + HTMLAttr* pBoldCJK; + HTMLAttr* pBoldCTL; + HTMLAttr* pItalic; + HTMLAttr* pItalicCJK; + HTMLAttr* pItalicCTL; + HTMLAttr* pStrike; + HTMLAttr* pUnderline; + HTMLAttr* pBlink; + HTMLAttr* pFont; + HTMLAttr* pFontCJK; + HTMLAttr* pFontCTL; + HTMLAttr* pFontHeight; + HTMLAttr* pFontHeightCJK; + HTMLAttr* pFontHeightCTL; + HTMLAttr* pFontColor; + HTMLAttr* pEscapement; + HTMLAttr* pCaseMap; + HTMLAttr* pKerning; // (only for SPACER) + HTMLAttr* pCharBrush; // character background + HTMLAttr* pLanguage; + HTMLAttr* pLanguageCJK; + HTMLAttr* pLanguageCTL; + HTMLAttr* pCharBox; +}; + +class HTMLAttr +{ + friend class SwHTMLParser; + friend class CellSaveStruct; + + SwNodeIndex m_nStartPara; + SwNodeIndex m_nEndPara; + sal_Int32 m_nStartContent; + sal_Int32 m_nEndContent; + bool m_bInsAtStart : 1; + bool m_bLikePara : 1; // set attribute above the whole paragraph + bool m_bValid : 1; // is the attribute valid? + + std::unique_ptr<SfxPoolItem> m_pItem; + std::shared_ptr<HTMLAttrTable> m_xAttrTab; + HTMLAttr *m_pNext; // still to close attributes with different values + HTMLAttr *m_pPrev; // already closed but not set attributes + HTMLAttr **m_ppHead; // list head + + HTMLAttr( const SwPosition& rPos, const SfxPoolItem& rItem, + HTMLAttr **pHd, std::shared_ptr<HTMLAttrTable> xAttrTab ); + + HTMLAttr( const HTMLAttr &rAttr, const SwNode &rEndPara, + sal_Int32 nEndCnt, HTMLAttr **pHd, std::shared_ptr<HTMLAttrTable> xAttrTab ); + +public: + + ~HTMLAttr(); + + HTMLAttr *Clone( const SwNode& rEndPara, sal_Int32 nEndCnt ) const; + void Reset( const SwNode& rSttPara, sal_Int32 nSttCnt, + HTMLAttr **pHd, const std::shared_ptr<HTMLAttrTable>& rAttrTab ); + inline void SetStart( const SwPosition& rPos ); + + SwNodeOffset GetStartParagraphIdx() const { return m_nStartPara.GetIndex(); } + SwNodeOffset GetEndParagraphIdx() const { return m_nEndPara.GetIndex(); } + + const SwNodeIndex& GetStartParagraph() const { return m_nStartPara; } + const SwNodeIndex& GetEndParagraph() const { return m_nEndPara; } + + sal_Int32 GetStartContent() const { return m_nStartContent; } + sal_Int32 GetEndContent() const { return m_nEndContent; } + + bool IsLikePara() const { return m_bLikePara; } + void SetLikePara() { m_bLikePara = true; } + + SfxPoolItem& GetItem() { return *m_pItem; } + const SfxPoolItem& GetItem() const { return *m_pItem; } + + HTMLAttr *GetNext() const { return m_pNext; } + void InsertNext( HTMLAttr *pNxt ) { m_pNext = pNxt; } + + HTMLAttr *GetPrev() const { return m_pPrev; } + void InsertPrev( HTMLAttr *pPrv ); + void ClearPrev() { m_pPrev = nullptr; } + + void SetHead(HTMLAttr **ppHd, const std::shared_ptr<HTMLAttrTable>& rAttrTab) + { + m_ppHead = ppHd; + m_xAttrTab = rAttrTab; + } + + // During setting attributes from styles it can happen that these + // shouldn't be set anymore. To delete them would be very expensive, because + // you don't know all the places where they are linked in. Therefore they're + // made invalid and deleted at the next call of SetAttr_(). + void Invalidate() { m_bValid = false; } +}; + +class HTMLAttrContext_SaveDoc; + +enum SwHTMLAppendMode { + AM_NORMAL, // no paragraph spacing handling + AM_NOSPACE, // set spacing hard to 0cm + AM_SPACE, // set spacing hard to 0.5cm + AM_SOFTNOSPACE, // don't set spacing, but save 0cm + AM_NONE // no append +}; + +class HTMLAttrContext +{ + HTMLAttrs m_aAttrs; // the attributes created in the context + + OUString m_aClass; // context class + + std::unique_ptr<HTMLAttrContext_SaveDoc> m_pSaveDocContext; + std::unique_ptr<SfxItemSet> m_pFrameItemSet; + + HtmlTokenId m_nToken; // the token of the context + + sal_uInt16 m_nTextFormatColl; // a style created in the context or zero + + sal_uInt16 m_nLeftMargin; // a changed left border + sal_uInt16 m_nRightMargin; // a changed right border + sal_uInt16 m_nFirstLineIndent; // a changed first line indent + + sal_uInt16 m_nUpperSpace; + sal_uInt16 m_nLowerSpace; + + SwHTMLAppendMode m_eAppend; + + bool m_bLRSpaceChanged : 1; // left/right border, changed indent? + bool m_bULSpaceChanged : 1; // top/bottom border changed? + bool m_bDefaultTextFormatColl : 1;// nTextFormatColl is only default + bool m_bSpansSection : 1; // the context opens a SwSection + bool m_bPopStack : 1; // delete above stack elements + bool m_bFinishPREListingXMP : 1; + bool m_bRestartPRE : 1; + bool m_bRestartXMP : 1; + bool m_bRestartListing : 1; + bool m_bHeaderOrFooter : 1; + + bool m_bVisible = true; + +public: + void ClearSaveDocContext(); + + HTMLAttrContext( HtmlTokenId nTokn, sal_uInt16 nPoolId, OUString aClass, + bool bDfltColl=false ); + explicit HTMLAttrContext( HtmlTokenId nTokn ); + ~HTMLAttrContext(); + + HtmlTokenId GetToken() const { return m_nToken; } + + sal_uInt16 GetTextFormatColl() const { return m_bDefaultTextFormatColl ? 0 : m_nTextFormatColl; } + sal_uInt16 GetDefaultTextFormatColl() const { return m_bDefaultTextFormatColl ? m_nTextFormatColl : 0; } + + const OUString& GetClass() const { return m_aClass; } + + inline void SetMargins( sal_uInt16 nLeft, sal_uInt16 nRight, short nIndent ); + + bool IsLRSpaceChanged() const { return m_bLRSpaceChanged; } + inline void GetMargins( sal_uInt16& nLeft, sal_uInt16& nRight, + short &nIndent ) const; + + inline void SetULSpace( sal_uInt16 nUpper, sal_uInt16 nLower ); + bool IsULSpaceChanged() const { return m_bULSpaceChanged; } + inline void GetULSpace( sal_uInt16& rUpper, sal_uInt16& rLower ) const; + + bool HasAttrs() const { return !m_aAttrs.empty(); } + const HTMLAttrs& GetAttrs() const { return m_aAttrs; } + HTMLAttrs& GetAttrs() { return m_aAttrs; } + + void SetSpansSection( bool bSet ) { m_bSpansSection = bSet; } + bool GetSpansSection() const { return m_bSpansSection; } + + void SetPopStack( bool bSet ) { m_bPopStack = bSet; } + bool GetPopStack() const { return m_bPopStack; } + + bool HasSaveDocContext() const { return m_pSaveDocContext!=nullptr; } + HTMLAttrContext_SaveDoc *GetSaveDocContext( bool bCreate=false ); + + const SfxItemSet *GetFrameItemSet() const { return m_pFrameItemSet.get(); } + SfxItemSet *GetFrameItemSet( SwDoc *pCreateDoc ); + + void SetFinishPREListingXMP( bool bSet ) { m_bFinishPREListingXMP = bSet; } + bool IsFinishPREListingXMP() const { return m_bFinishPREListingXMP; } + + void SetRestartPRE( bool bSet ) { m_bRestartPRE = bSet; } + bool IsRestartPRE() const { return m_bRestartPRE; } + + void SetRestartXMP( bool bSet ) { m_bRestartXMP = bSet; } + bool IsRestartXMP() const { return m_bRestartXMP; } + + void SetRestartListing( bool bSet ) { m_bRestartListing = bSet; } + bool IsRestartListing() const { return m_bRestartListing; } + + void SetHeaderOrFooter( bool bSet ) { m_bHeaderOrFooter = bSet; } + bool IsHeaderOrFooter() const { return m_bHeaderOrFooter; } + + void SetAppendMode( SwHTMLAppendMode eMode ) { m_eAppend = eMode; } + SwHTMLAppendMode GetAppendMode() const { return m_eAppend; } + + void SetVisible(bool bVisible) { m_bVisible = bVisible; } + bool IsVisible() const { return m_bVisible; } +}; + +typedef std::vector<std::unique_ptr<HTMLAttrContext>> HTMLAttrContexts; + +class HTMLTable; +class SwCSS1Parser; +class SwHTMLNumRuleInfo; + +typedef std::vector<std::unique_ptr<ImageMap>> ImageMaps; + +enum class HtmlContextFlags { + ProtectStack = 0x0001, + StripPara = 0x0002, + KeepNumrule = 0x0004, + HeaderDist = 0x0008, + FooterDist = 0x0010, + KeepAttrs = 0x0020, + MultiColMask = StripPara | KeepNumrule | KeepAttrs // for headers, footers or footnotes +}; +namespace o3tl +{ + template<> struct typed_flags<HtmlContextFlags> : is_typed_flags<HtmlContextFlags, 0x03f> {}; +} + +enum class HtmlFrameFormatFlags { + Box = 0x0001, + Background = 0x0002, + Padding = 0x0004, + Direction = 0x0008, +}; +namespace o3tl +{ + template<> struct typed_flags<HtmlFrameFormatFlags> : is_typed_flags<HtmlFrameFormatFlags, 0x0f> {}; +} + +class SwHTMLFrameFormatListener : public SvtListener +{ + SwFrameFormat* m_pFrameFormat; +public: + SwHTMLFrameFormatListener(SwFrameFormat* pFrameFormat); + SwFrameFormat* GetFrameFormat() { return m_pFrameFormat; } + virtual void Notify(const SfxHint&) override; +}; + +class SwHTMLParser : public SfxHTMLParser, public SvtListener +{ + friend class SectionSaveStruct; + friend class CellSaveStruct; + friend class CaptionSaveStruct; + + /* + Progress bar + */ + std::unique_ptr<ImportProgress> m_xProgress; + + OUString m_aPathToFile; + OUString m_sBaseURL; + OUString m_aBasicLib; + OUString m_aBasicModule; + OUString m_aScriptSource; // content of the current script block + OUString m_aScriptType; // type of read script (StarBasic/VB/JAVA) + OUString m_aScriptURL; // script URL + OUString m_aStyleSource; // content of current style sheet + OUString m_aContents; // text of current marquee, field and so + OUStringBuffer m_sTitle; + OUString m_aUnknownToken; // a started unknown token + OUString m_aBulletGrfs[MAXLEVEL]; + OUString m_sJmpMark; + + std::vector<sal_uInt16> m_aBaseFontStack; // stack for <BASEFONT> + // Bit 0-2: font size (1-7) + std::vector<sal_uInt16> m_aFontStack; // stack for <FONT>, <BIG>, <SMALL> + // Bit 0-2: font size (1-7) + // Bit 15: font colour was set + + HTMLAttrs m_aSetAttrTab;// "closed", not set attributes + HTMLAttrs m_aParaAttrs; // temporary paragraph attributes + std::shared_ptr<HTMLAttrTable> m_xAttrTab; // "open" attributes + HTMLAttrContexts m_aContexts;// the current context of attribute/token + std::vector<std::unique_ptr<SwHTMLFrameFormatListener>> m_aMoveFlyFrames;// Fly-Frames, the anchor is moved + std::deque<sal_Int32> m_aMoveFlyCnts;// and the Content-Positions + //stray SwTableBoxes which need to be deleted to avoid leaking, but hold + //onto them until parsing is done + std::vector<std::unique_ptr<SwTableBox>> m_aOrphanedTableBoxes; + + std::unique_ptr<SwApplet_Impl> m_pAppletImpl; // current applet + + std::unique_ptr<SwCSS1Parser> m_pCSS1Parser; // Style-Sheet-Parser + std::unique_ptr<SwHTMLNumRuleInfo> m_pNumRuleInfo; + std::vector<SwPending> m_vPendingStack; + + rtl::Reference<SwDoc> m_xDoc; + SwPaM *m_pPam; // SwPosition should be enough, or ?? + SwViewShell *m_pActionViewShell; // SwViewShell, where StartAction was called + SwNodeIndex *m_pSttNdIdx; + + std::vector<HTMLTable*> m_aTables; + std::shared_ptr<HTMLTable> m_xTable; // current "outermost" table + SwHTMLForm_Impl* m_pFormImpl; // current form + rtl::Reference<SdrTextObj> m_pMarquee; // current marquee + std::unique_ptr<SwField> m_xField; // current field + ImageMap *m_pImageMap; // current image map + std::unique_ptr<ImageMaps> m_pImageMaps; ///< all Image-Maps that have been read + std::unique_ptr<SwHTMLFootEndNote_Impl> m_pFootEndNoteImpl; + + Size m_aHTMLPageSize; // page size of HTML template + + sal_uInt32 m_aFontHeights[7]; // font heights 1-7 + ImplSVEvent * m_nEventId; + + sal_uInt16 m_nBaseFontStMin; + sal_uInt16 m_nFontStMin; + sal_uInt16 m_nDefListDeep; + sal_uInt16 m_nFontStHeadStart; // elements in font stack at <Hn> + sal_uInt16 m_nSBModuleCnt; // counter for basic modules + sal_uInt16 m_nMissingImgMaps; // How many image maps are still missing? + size_t m_nParaCnt; + size_t m_nContextStMin; // lower limit of PopContext + size_t m_nContextStAttrMin; // lower limit of attributes + sal_uInt16 m_nSelectEntryCnt; // Number of entries in the actual listbox + HtmlTokenId m_nOpenParaToken; // opened paragraph element + + enum class JumpToMarks { NONE, Mark, Table, Region, Graphic }; + JumpToMarks m_eJumpTo; + +#ifdef DBG_UTIL + sal_uInt16 m_nContinue; // depth of Continue calls +#endif + + SvxAdjust m_eParaAdjust; // adjustment of current paragraph + HTMLScriptLanguage m_eScriptLang; // current script language + + bool m_bOldIsHTMLMode : 1; // Was it a HTML document? + + bool m_bDocInitialized : 1; // document resp. shell was initialize + // flag to prevent double init via recursion + bool m_bViewCreated : 1; // the view was already created (asynchronous) + bool m_bSetModEnabled : 1; + + bool m_bInFloatingFrame : 1; // We are in a floating frame + bool m_bInField : 1; + bool m_bKeepUnknown : 1; // handle unknown/not supported tokens + // 8 + bool m_bCallNextToken : 1; // In tables: call NextToken in any case + bool m_bIgnoreRawData : 1; // ignore content of script/style + bool m_bLBEntrySelected : 1; // Is the current option selected? + bool m_bTAIgnoreNewPara : 1; // ignore next LF in text area? + bool m_bFixMarqueeWidth : 1; // Change size of marquee? + + bool m_bUpperSpace : 1; // top paragraph spacing is needed + bool m_bNoParSpace : 1; + // 16 + + bool m_bInNoEmbed : 1; // we are in a NOEMBED area + + bool m_bInTitle : 1; // we are in title + + bool m_bChkJumpMark : 1; // maybe jump to predetermined mark + bool m_bUpdateDocStat : 1; + bool m_bFixSelectWidth : 1; // Set new width of select? + bool m_bTextArea : 1; + // 24 + bool m_bSelect : 1; + bool m_bInFootEndNoteAnchor : 1; + bool m_bInFootEndNoteSymbol : 1; + bool m_bIgnoreHTMLComments : 1; + bool m_bRemoveHidden : 1; // the filter implementation might set the hidden flag + + bool m_bBodySeen : 1; + bool m_bReadingHeaderOrFooter : 1; + bool m_bNotifyMacroEventRead : 1; + bool m_isInTableStructure; + + int m_nTableDepth; + int m_nFloatingFrames; + int m_nListItems; + + /// the names corresponding to the DOCINFO field subtypes INFO[1-4] + OUString m_InfoNames[4]; + + SfxViewFrame* m_pTempViewFrame; + + bool m_bXHTML = false; + bool m_bReqIF = false; + + /** + * Non-owning pointers to already inserted OLE nodes, matching opened + * <object> XHTML elements. + */ + std::stack<SwOLENode*> m_aEmbeds; + + std::set<OUString> m_aAllowedRTFOLEMimeTypes; + + /// This is the URL of the outer <object> data if it's not OLE2 or an image. + OUString m_aEmbedURL; + + void DeleteFormImpl(); + + void DocumentDetected(); + void Show(); + void ShowStatline(); + SwViewShell *CallStartAction( SwViewShell *pVSh = nullptr, bool bChkPtr = true ); + SwViewShell *CallEndAction( bool bChkAction = false, bool bChkPtr = true ); + SwViewShell *CheckActionViewShell(); + + DECL_LINK( AsyncCallback, void*, void ); + + // set attribute on document + void SetAttr_( bool bChkEnd, bool bBeforeTable, std::deque<std::unique_ptr<HTMLAttr>> *pPostIts ); + void SetAttr( bool bChkEnd = true, bool bBeforeTable = false, + std::deque<std::unique_ptr<HTMLAttr>> *pPostIts = nullptr ) + { + if( !m_aSetAttrTab.empty() || !m_aMoveFlyFrames.empty() ) + SetAttr_( bChkEnd, bBeforeTable, pPostIts ); + } + + HTMLAttr **GetAttrTabEntry( sal_uInt16 nWhich ); + + // create a new text node on PaM position + bool AppendTextNode( SwHTMLAppendMode eMode=AM_NORMAL, bool bUpdateNum=true ); + void AddParSpace(); + + // start/end an attribute + // ppDepAttr indicated an attribute table entry, which attribute has to be + // set, before the attribute is closed + void NewAttr(const std::shared_ptr<HTMLAttrTable>& rAttrTab, HTMLAttr **ppAttr, const SfxPoolItem& rItem); + bool EndAttr( HTMLAttr *pAttr, bool bChkEmpty=true ); + void DeleteAttr( HTMLAttr* pAttr ); + + void EndContextAttrs( HTMLAttrContext *pContext ); + void SaveAttrTab(std::shared_ptr<HTMLAttrTable> const & rNewAttrTab); + void SplitAttrTab( const SwPosition& rNewPos ); + void SplitAttrTab(std::shared_ptr<HTMLAttrTable> const & rNewAttrTab, bool bMoveEndBack); + void RestoreAttrTab(std::shared_ptr<HTMLAttrTable> const & rNewAttrTab); + void InsertAttr( const SfxPoolItem& rItem, bool bInsAtStart ); + void InsertAttrs( std::deque<std::unique_ptr<HTMLAttr>> rAttrs ); + + bool DoPositioning( SfxItemSet &rItemSet, + SvxCSS1PropertyInfo &rPropInfo, + HTMLAttrContext *pContext ); + bool CreateContainer( std::u16string_view rClass, SfxItemSet &rItemSet, + SvxCSS1PropertyInfo &rPropInfo, + HTMLAttrContext *pContext ); + bool EndSection( bool bLFStripped=false ); + + void InsertAttrs( SfxItemSet &rItemSet, SvxCSS1PropertyInfo const &rPropInfo, + HTMLAttrContext *pContext, bool bCharLvl=false ); + void InsertAttr( HTMLAttr **ppAttr, const SfxPoolItem & rItem, + HTMLAttrContext *pCntxt ); + void SplitPREListingXMP( HTMLAttrContext *pCntxt ); + void FixHeaderFooterDistance( bool bHeader, const SwPosition *pOldPos ); + + void EndContext( HTMLAttrContext *pContext ); + void ClearContext( HTMLAttrContext *pContext ); + + const SwFormatColl *GetCurrFormatColl() const; + + SwTwips GetCurrentBrowseWidth(); + + SwHTMLNumRuleInfo& GetNumInfo() { return *m_pNumRuleInfo; } + // add parameter <bCountedInList> + void SetNodeNum( sal_uInt8 nLevel ); + + // Manage paragraph styles + + // set the style resp. its attributes on the stack + void SetTextCollAttrs( HTMLAttrContext *pContext = nullptr ); + + void InsertParaAttrs( const SfxItemSet& rItemSet ); + + // Manage attribute context + + // save current context + void PushContext(std::unique_ptr<HTMLAttrContext>& rCntxt) + { + m_aContexts.push_back(std::move(rCntxt)); + } + + // Fetch top/specified context but not outside the context with token + // nLimit. If bRemove set then remove it. + std::unique_ptr<HTMLAttrContext> PopContext(HtmlTokenId nToken = HtmlTokenId::NONE); + + void GetMarginsFromContext( sal_uInt16 &nLeft, sal_uInt16 &nRight, short& nIndent, + bool bIgnoreCurrent=false ) const; + void GetMarginsFromContextWithNumberBullet( sal_uInt16 &nLeft, sal_uInt16 &nRight, + short& nIndent ) const; + void GetULSpaceFromContext( sal_uInt16 &rUpper, sal_uInt16 &rLower ) const; + + void MovePageDescAttrs( SwNode *pSrcNd, SwNodeOffset nDestIdx, bool bFormatBreak ); + + // Handling of tags at paragraph level + + // <P> and <H1> to <H6> + void NewPara(); + void EndPara( bool bReal = false ); + void NewHeading( HtmlTokenId nToken ); + void EndHeading(); + + // <ADDRESS>, <BLOCKQUOTE> and <PRE> + void NewTextFormatColl( HtmlTokenId nToken, sal_uInt16 nPoolId ); + void EndTextFormatColl( HtmlTokenId nToken ); + + // <DIV> and <CENTER> + void NewDivision( HtmlTokenId nToken ); + void EndDivision(); + + // insert/close Fly-Frames + void InsertFlyFrame( const SfxItemSet& rItemSet, HTMLAttrContext *pCntxt, + const OUString& rId ); + + void SaveDocContext( HTMLAttrContext *pCntxt, HtmlContextFlags nFlags, + const SwPosition *pNewPos ); + void RestoreDocContext( HTMLAttrContext *pCntxt ); + + // end all opened <DIV> areas + bool EndSections( bool bLFStripped ); + + // <MULTICOL> + void NewMultiCol( sal_uInt16 columnsFromCss=0 ); + + // <MARQUEE> + void NewMarquee( HTMLTable *pCurTable=nullptr ); + void EndMarquee(); + void InsertMarqueeText(); + + // Handling of lists + + // order list <OL> and unordered list <UL> with <LI> + void NewNumberBulletList( HtmlTokenId nToken ); + void EndNumberBulletList( HtmlTokenId nToken = HtmlTokenId::NONE ); + void NewNumberBulletListItem( HtmlTokenId nToken ); + void EndNumberBulletListItem( HtmlTokenId nToken, bool bSetColl); + + // definitions lists <DL> with <DD>, <DT> + void NewDefList(); + void EndDefList(); + void NewDefListItem( HtmlTokenId nToken ); + void EndDefListItem( HtmlTokenId nToken = HtmlTokenId::NONE ); + + // Handling of tags on character level + + // handle tags like <B>, <I> and so, which enable/disable a certain + // attribute or like <SPAN> get attributes from styles + void NewStdAttr( HtmlTokenId nToken ); + void NewStdAttr( HtmlTokenId nToken, + HTMLAttr **ppAttr, const SfxPoolItem & rItem, + HTMLAttr **ppAttr2=nullptr, const SfxPoolItem *pItem2=nullptr, + HTMLAttr **ppAttr3=nullptr, const SfxPoolItem *pItem3=nullptr ); + void EndTag( HtmlTokenId nToken ); + + // handle font attributes + void NewBasefontAttr(); // for <BASEFONT> + void EndBasefontAttr(); + void NewFontAttr( HtmlTokenId nToken ); // for <FONT>, <BIG> and <SMALL> + void EndFontAttr( HtmlTokenId nToken ); + + // tags realized via character styles + void NewCharFormat( HtmlTokenId nToken ); + + void DeleteSection(SwStartNode* pSttNd); + + // <SDFIELD> +public: + static SvxNumType GetNumType( std::u16string_view rStr, SvxNumType eDfltType ); +private: + void NewField(); + void EndField(); + void InsertFieldText(); + + // <SPACER> + void InsertSpacer(); + + // Inserting graphics, plug-ins and applets + + // search image maps and link with graphic nodes + ImageMap *FindImageMap( std::u16string_view rURL ) const; + void ConnectImageMaps(); + + // find anchor of Fly-Frames and set corresponding attributes + // in Attrset (htmlgrin.cxx) + void SetAnchorAndAdjustment( sal_Int16 eVertOri, + sal_Int16 eHoriOri, + const SvxCSS1PropertyInfo &rPropInfo, + SfxItemSet& rFrameSet ); + void SetAnchorAndAdjustment( sal_Int16 eVertOri, + sal_Int16 eHoriOri, + SfxItemSet& rFrameSet, + bool bDontAppend=false ); + void SetAnchorAndAdjustment( const SvxCSS1PropertyInfo &rPropInfo, + SfxItemSet &rFrameItemSet ); + + static void SetFrameFormatAttrs( SfxItemSet &rItemSet, + HtmlFrameFormatFlags nFlags, SfxItemSet &rFrameItemSet ); + + // create frames and register auto bound frames + void RegisterFlyFrame( SwFrameFormat *pFlyFrame ); + + // Adjust the size of the Fly-Frames to requirements and conditions + // (not for graphics, therefore htmlplug.cxx) + static void SetFixSize( const Size& rPixSize, const Size& rTwipDfltSize, + bool bPercentWidth, bool bPercentHeight, + SvxCSS1PropertyInfo const &rPropInfo, + SfxItemSet& rFlyItemSet ); + static void SetVarSize( SvxCSS1PropertyInfo const &rPropInfo, + SfxItemSet& rFlyItemSet, SwTwips nDfltWidth=MINLAY, + sal_uInt8 nDefaultPercentWidth=0 ); + static void SetSpace( const Size& rPixSpace, SfxItemSet &rItemSet, + SvxCSS1PropertyInfo &rPropInfo, SfxItemSet& rFlyItemSet ); + + sal_uInt16 IncGrfsThatResizeTable(); + + void GetDefaultScriptType( ScriptType& rType, + OUString& rTypeStr ) const; + + // the actual insert methods for <IMG>, <EMBED>, <APPLET> and <PARAM> + void InsertImage(); // htmlgrin.cxx + bool InsertEmbed(); // htmlplug.cxx + +#if HAVE_FEATURE_JAVA + void NewObject(); // htmlplug.cxx +#endif + void EndObject(); // link CommandLine with applet (htmlplug.cxx) +#if HAVE_FEATURE_JAVA + void InsertApplet(); // htmlplug.cxx +#endif + void EndApplet(); // link CommandLine with applet (htmlplug.cxx) + void InsertParam(); // htmlplug.cxx + + void InsertFloatingFrame(); + + // parse <BODY>-tag: set background graphic and background colour (htmlgrin.cxx) + void InsertBodyOptions(); + + // Inserting links and bookmarks (htmlgrin.cxx) + + // parse <A>-tag: insert a link resp. bookmark + void NewAnchor(); + void EndAnchor(); + + // insert bookmark + void InsertBookmark( const OUString& rName ); + + void InsertCommentText( std::string_view pTag ); + void InsertComment( const OUString& rName, std::string_view pTag = {} ); + + // Has the current paragraph bookmarks? + bool HasCurrentParaBookmarks( bool bIgnoreStack=false ) const; + + // Inserting script/basic elements + + // parse the last read basic module (htmlbas.cxx) + void NewScript(); + void EndScript(); + + void AddScriptSource(); + + // insert event in SFX configuration (htmlbas.cxx) + void InsertBasicDocEvent( const OUString& aEventName, const OUString& rName, + ScriptType eScrType, const OUString& rScrType ); + + // Inserting styles + + // <STYLE> + void NewStyle(); + void EndStyle(); + + static inline bool HasStyleOptions( std::u16string_view rStyle, std::u16string_view rId, + std::u16string_view rClass, const OUString *pLang=nullptr, + const OUString *pDir=nullptr ); + bool ParseStyleOptions( const OUString &rStyle, const OUString &rId, + const OUString &rClass, SfxItemSet &rItemSet, + SvxCSS1PropertyInfo &rPropInfo, + const OUString *pLang=nullptr, const OUString *pDir=nullptr ); + + // Inserting Controls and Forms (htmlform.cxx) + + // Insert draw object into document + void InsertDrawObject( SdrObject* pNewDrawObj, const Size& rSpace, + sal_Int16 eVertOri, + sal_Int16 eHoriOri, + SfxItemSet& rCSS1ItemSet, + SvxCSS1PropertyInfo& rCSS1PropInfo ); + css::uno::Reference< css::drawing::XShape > InsertControl( + const css::uno::Reference< css::form::XFormComponent > & rFormComp, + const css::uno::Reference< css::beans::XPropertySet > & rFCompPropSet, + const Size& rSize, + sal_Int16 eVertOri, + sal_Int16 eHoriOri, + SfxItemSet& rCSS1ItemSet, + SvxCSS1PropertyInfo& rCSS1PropInfo, + const SvxMacroTableDtor& rMacroTable, + const std::vector<OUString>& rUnoMacroTable, + const std::vector<OUString>& rUnoMacroParamTable, + bool bSetPropSet = true, + bool bHidden = false ); + void SetControlSize( const css::uno::Reference< css::drawing::XShape > & rShape, const Size& rTextSz, + bool bMinWidth, bool bMinHeight ); + +public: + static void ResizeDrawObject( SdrObject* pObj, SwTwips nWidth ); +private: + static void RegisterDrawObjectToTable( HTMLTable *pCurTable, SdrObject* pObj, + sal_uInt8 nWidth ); + + void NewForm( bool bAppend=true ); + void EndForm( bool bAppend=true ); + + // Insert methods for <INPUT>, <TEXTAREA> and <SELECT> + void InsertInput(); + + void NewTextArea(); + void InsertTextAreaText( HtmlTokenId nToken ); + void EndTextArea(); + + void NewSelect(); + void InsertSelectOption(); + void InsertSelectText(); + void EndSelect(); + + // Inserting tables (htmltab.cxx) +public: + + // Insert box content after the given node + const SwStartNode *InsertTableSection( const SwStartNode *pPrevStNd ); + + // Insert box content at the end of the table containing the PaM + // and move the PaM into the cell + const SwStartNode *InsertTableSection( sal_uInt16 nPoolId ); + + // Insert methods for various table tags + std::unique_ptr<HTMLTableCnts> InsertTableContents( bool bHead ); + +private: + // Create a section for the temporary storage of the table caption + SwStartNode *InsertTempTableCaptionSection(); + + void BuildTableCell( HTMLTable *pTable, bool bReadOptions, bool bHead ); + void BuildTableRow( HTMLTable *pTable, bool bReadOptions, + SvxAdjust eGrpAdjust, sal_Int16 eVertOri ); + void BuildTableSection( HTMLTable *pTable, bool bReadOptions, bool bHead ); + void BuildTableColGroup( HTMLTable *pTable, bool bReadOptions ); + void BuildTableCaption( HTMLTable *pTable ); + std::shared_ptr<HTMLTable> BuildTable(SvxAdjust eCellAdjust, + bool bIsParentHead = false, + bool bHasParentSection=true, + bool bHasToFlow = false); + + // misc ... + + void ParseMoreMetaOptions(); + + bool FileDownload( const OUString& rURL, OUString& rStr ); + void InsertLink(); + + void InsertIDOption(); + void InsertLineBreak(); + void InsertHorzRule(); + + void FillEndNoteInfo( std::u16string_view aContent ); + void FillFootNoteInfo( std::u16string_view aContent ); + void InsertFootEndNote( const OUString& rName, bool bEndNote, bool bFixed ); + void FinishFootEndNote(); + void InsertFootEndNoteText(); + SwNodeIndex *GetFootEndNoteSection( const OUString& rName ); + + sal_Int32 StripTrailingLF(); + + // Remove empty paragraph at the PaM position + void StripTrailingPara(); + // If removing an empty node would corrupt the document + bool CanRemoveNode(SwNodeOffset nNodeIdx) const; + + // Are there fly frames in the current paragraph? + bool HasCurrentParaFlys( bool bNoSurroundOnly = false, + bool bSurroundOnly = false ) const; + + class TableDepthGuard + { + private: + SwHTMLParser& m_rParser; + public: + TableDepthGuard(SwHTMLParser& rParser) + : m_rParser(rParser) + { + ++m_rParser.m_nTableDepth; + } + bool TooDeep() const { return m_rParser.m_nTableDepth > 1024; } + ~TableDepthGuard() + { + --m_rParser.m_nTableDepth; + } + }; + +public: // used in tables + + // Create brush item (with new) or 0 + SvxBrushItem* CreateBrushItem( const Color *pColor, + const OUString &rImageURL, + const OUString &rStyle, + const OUString &rId, + const OUString &rClass ); + +protected: + // Executed for each token recognized by CallParser + virtual void NextToken( HtmlTokenId nToken ) override; + virtual ~SwHTMLParser() override; + + // If the document is removed, remove the parser as well + virtual void Notify(const SfxHint&) override; + + virtual void AddMetaUserDefined( OUString const & i_rMetaName ) override; + +public: + + SwHTMLParser( SwDoc* pD, SwPaM & rCursor, SvStream& rIn, + OUString aFileName, + OUString aBaseURL, + bool bReadNewDoc, + SfxMedium* pMed, bool bReadUTF8, + bool bIgnoreHTMLComments, + const OUString& rNamespace); + + virtual SvParserState CallParser() override; + + static sal_uInt16 ToTwips( sal_uInt16 nPixel ); + + // for reading asynchronously from SvStream + virtual void Continue( HtmlTokenId nToken ) override; + + virtual bool ParseMetaOptions( const css::uno::Reference<css::document::XDocumentProperties>&, + SvKeyValueIterator* ) override; + + + void RegisterHTMLTable(HTMLTable* pNew) + { + m_aTables.push_back(pNew); + } + + void DeregisterHTMLTable(HTMLTable* pOld); + + SwDoc* GetDoc() const; + + bool IsReqIF() const; + + bool IsReadingHeaderOrFooter() const { return m_bReadingHeaderOrFooter; } + + void NotifyMacroEventRead(); + + /// Strips query and fragment from a URL path if base URL is a file:// one. + static OUString StripQueryFromPath(std::u16string_view rBase, const OUString& rPath); +}; + +struct SwPendingData +{ + virtual ~SwPendingData() {} +}; + +struct SwPending +{ + HtmlTokenId nToken; + std::unique_ptr<SwPendingData> pData; + + SwPending( HtmlTokenId nTkn ) + : nToken( nTkn ) + {} +}; + +inline void HTMLAttr::SetStart( const SwPosition& rPos ) +{ + m_nStartPara = rPos.GetNode(); + m_nStartContent = rPos.GetContentIndex(); + m_nEndPara = m_nStartPara; + m_nEndContent = m_nStartContent; +} + +inline void HTMLAttrContext::SetMargins( sal_uInt16 nLeft, sal_uInt16 nRight, + short nIndent ) +{ + m_nLeftMargin = nLeft; + m_nRightMargin = nRight; + m_nFirstLineIndent = nIndent; + m_bLRSpaceChanged = true; +} + +inline void HTMLAttrContext::GetMargins( sal_uInt16& nLeft, + sal_uInt16& nRight, + short& nIndent ) const +{ + if( m_bLRSpaceChanged ) + { + nLeft = m_nLeftMargin; + nRight = m_nRightMargin; + nIndent = m_nFirstLineIndent; + } +} + +inline void HTMLAttrContext::SetULSpace( sal_uInt16 nUpper, sal_uInt16 nLower ) +{ + m_nUpperSpace = nUpper; + m_nLowerSpace = nLower; + m_bULSpaceChanged = true; +} + +inline void HTMLAttrContext::GetULSpace( sal_uInt16& rUpper, + sal_uInt16& rLower ) const +{ + if( m_bULSpaceChanged ) + { + rUpper = m_nUpperSpace; + rLower = m_nLowerSpace; + } +} + +inline bool SwHTMLParser::HasStyleOptions( std::u16string_view rStyle, + std::u16string_view rId, + std::u16string_view rClass, + const OUString *pLang, + const OUString *pDir ) +{ + return !rStyle.empty() || !rId.empty() || !rClass.empty() || + (pLang && !pLang->isEmpty()) || (pDir && !pDir->isEmpty()); +} + +class SwTextFootnote; + +class SwHTMLTextFootnote +{ +private: + OUString m_sName; + SwTextFootnote* m_pTextFootnote; + std::unique_ptr<SvtDeleteListener> m_xDeleteListener; +public: + SwHTMLTextFootnote(OUString rName, SwTextFootnote* pInTextFootnote) + : m_sName(std::move(rName)) + , m_pTextFootnote(pInTextFootnote) + , m_xDeleteListener(new SvtDeleteListener(static_cast<SwFormatFootnote&>(pInTextFootnote->GetAttr()).GetNotifier())) + { + } + const OUString& GetName() const + { + return m_sName; + } + const SwNodeIndex* GetStartNode() const + { + if (m_xDeleteListener->WasDeleted()) + return nullptr; + return m_pTextFootnote->GetStartNode(); + } +}; + +struct SwHTMLFootEndNote_Impl +{ + std::vector<SwHTMLTextFootnote> aTextFootnotes; + + OUString sName; + OUString sContent; // information for the last footnote + bool bEndNote; + bool bFixed; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/wrthtml.cxx b/sw/source/filter/html/wrthtml.cxx new file mode 100644 index 0000000000..bd5015bb82 --- /dev/null +++ b/sw/source/filter/html/wrthtml.cxx @@ -0,0 +1,1694 @@ +/* -*- 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 <stdlib.h> +#include <hintids.hxx> +#include <comphelper/string.hxx> +#include <svl/urihelper.hxx> +#include <svl/languageoptions.hxx> +#include <rtl/tencinfo.h> +#include <sfx2/linkmgr.hxx> +#include <sfx2/docfile.hxx> + +#include <svtools/htmlcfg.hxx> +#include <svtools/htmltokn.h> +#include <svtools/htmlkywd.hxx> +#include <vcl/svapp.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <sfx2/frmhtmlw.hxx> +#include <svx/xoutbmp.hxx> +#include <svx/unobrushitemhelper.hxx> +#include <sfx2/htmlmode.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/langitem.hxx> +#include <svl/stritem.hxx> +#include <editeng/frmdiritem.hxx> + +#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> +#include <com/sun/star/document/XDocumentProperties.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <fmthdft.hxx> +#include <fmtfld.hxx> +#include <fmtpdsc.hxx> +#include <txatbase.hxx> +#include <frmatr.hxx> +#include <charfmt.hxx> +#include <docary.hxx> +#include <pam.hxx> +#include <doc.hxx> +#include <ndtxt.hxx> +#include <mdiexp.hxx> +#include <fltini.hxx> +#include <viewopt.hxx> +#include <IMark.hxx> +#include <poolfmt.hxx> +#include <pagedesc.hxx> +#include <section.hxx> +#include <swtable.hxx> +#include <fldbas.hxx> +#include <fmtclds.hxx> +#include <docsh.hxx> +#include "wrthtml.hxx" +#include "htmlnum.hxx" +#include "htmlfly.hxx" +#include <swmodule.hxx> +#include <strings.hrc> +#include <swerror.h> +#include <rtl/strbuf.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <IDocumentMarkAccess.hxx> +#include <xmloff/odffields.hxx> +#include <tools/urlobj.hxx> +#include <osl/file.hxx> +#include <comphelper/scopeguard.hxx> +#include <unotools/tempfile.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <officecfg/Office/Common.hxx> +#include <officecfg/Office/Writer.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/sequence.hxx> + +#define MAX_INDENT_LEVEL 20 + +using namespace css; + +static char sIndentTabs[MAX_INDENT_LEVEL+2] = + "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; + +SwHTMLWriter::SwHTMLWriter( const OUString& rBaseURL, std::u16string_view rFilterOptions ) + : m_pNumRuleInfo(new SwHTMLNumRuleInfo) + , m_nHTMLMode(0) + , m_eCSS1Unit(FieldUnit::NONE) + , m_pStartNdIdx(nullptr) + , m_pCurrPageDesc(nullptr) + , m_pFormatFootnote(nullptr) + , m_nWarn(0) + , m_nLastLFPos(0) + , m_nLastParaToken(HtmlTokenId::NONE) + , m_nBkmkTabPos(-1) + , m_nImgMapCnt(1) + , m_nFormCntrlCnt(0) + , m_nEndNote(0) + , m_nFootNote(0) + , m_nLeftMargin(0) + , m_nDfltLeftMargin(0) + , m_nDfltRightMargin(0) + , m_nFirstLineIndent(0) + , m_nDfltFirstLineIndent(0) + , m_nDfltTopMargin(0) + , m_nDfltBottomMargin(0) + , m_nIndentLvl(0) + , m_nWishLineLen(70) + , m_nDefListLvl(0) + , m_nDefListMargin(0) + , m_nHeaderFooterSpace(0) + , m_nTextAttrsToIgnore(0) + , m_nExportMode(0) + , m_nCSS1OutMode(0) + , m_nCSS1Script(CSS1_OUTMODE_WESTERN) + , m_nDirection(SvxFrameDirection::Horizontal_LR_TB) + , m_eLang(LANGUAGE_DONTKNOW) + , m_bCfgOutStyles( false ) + , m_bCfgPreferStyles( false ) + , m_bCfgFormFeed( false ) + , m_bCfgStarBasic( false ) + , m_bCfgCpyLinkedGrfs( false ) + , m_bFirstLine(true) + , m_bTagOn( false ) + , m_bTextAttr( false ) + , m_bOutOpts( false ) + , m_bOutTable( false ) + , m_bOutHeader( false ) + , m_bOutFooter( false ) + , m_bOutFlyFrame( false ) + , m_bFirstCSS1Rule( false ) + , m_bFirstCSS1Property( false ) + , m_bCSS1IgnoreFirstPageDesc( false ) + , m_bNoAlign( false ) + , m_bClearLeft( false ) + , m_bClearRight( false ) + , m_bPreserveForm( false ) + , m_bCfgNetscape4( false ) + , mbSkipImages(false) + , mbSkipHeaderFooter(false) + , mbEmbedImages(false) + , m_bCfgPrintLayout( false ) + , m_bParaDotLeaders( false ) +{ + SetBaseURL(rBaseURL); + + if (rBaseURL.isEmpty()) + { + // Paste: set base URL to a tempfile, so images are not lost. + mpTempBaseURL.reset(new utl::TempFileNamed()); + mpTempBaseURL->EnableKillingFile(); + SetBaseURL(mpTempBaseURL->GetURL()); + } + + SetupFilterOptions(rFilterOptions); + + if (mbXHTML) + { + m_bNoAlign = true; + } +} + +SwHTMLWriter::~SwHTMLWriter() +{ +} + +std::unique_ptr<SwHTMLNumRuleInfo> SwHTMLWriter::ReleaseNextNumInfo() +{ + return std::move(m_pNextNumRuleInfo); +} + +void SwHTMLWriter::SetupFilterOptions(SfxMedium& rMedium) +{ + uno::Sequence<beans::PropertyValue> aArgs = rMedium.GetArgs(); + if (const SfxStringItem* pItem = rMedium.GetItemSet().GetItemIfSet( SID_FILE_FILTEROPTIONS )) + { + const OUString sFilterOptions = pItem->GetValue(); + + if (sFilterOptions.startsWith("{")) + { + std::vector<beans::PropertyValue> aArgsVec + = comphelper::JsonToPropertyValues(sFilterOptions.toUtf8()); + aArgs = comphelper::containerToSequence(aArgsVec); + } + + SetupFilterOptions(sFilterOptions); + } + + SetupFilterFromPropertyValues(aArgs); +} + +void SwHTMLWriter::SetupFilterOptions(std::u16string_view rFilterOptions) +{ + comphelper::SequenceAsHashMap aStoreMap; + if (rFilterOptions.find(u"SkipImages") != std::u16string_view::npos) + { + aStoreMap["SkipImages"] <<= true; + } + else if (rFilterOptions.find(u"SkipHeaderFooter") != std::u16string_view::npos) + { + aStoreMap["SkipHeaderFooter"] <<= true; + } + else if (rFilterOptions.find(u"EmbedImages") != std::u16string_view::npos) + { + aStoreMap["EmbedImages"] <<= true; + } + + // this option can be "on" together with any of above + if (rFilterOptions.find(u"NoLineLimit") != std::u16string_view::npos) + { + aStoreMap["NoLineLimit"] <<= true; + } + + // this option can be "on" together with any of above + if (rFilterOptions.find(u"NoPrettyPrint") != std::u16string_view::npos) + { + aStoreMap["NoPrettyPrint"] <<= true; + } + + const uno::Sequence<OUString> aOptionSeq + = comphelper::string::convertCommaSeparated(rFilterOptions); + static constexpr OUString aXhtmlNsKey(u"xhtmlns="_ustr); + for (const auto& rOption : aOptionSeq) + { + if (rOption == "XHTML") + { + aStoreMap["XHTML"] <<= true; + } + else if (rOption.startsWith(aXhtmlNsKey)) + { + aStoreMap["XhtmlNs"] <<= rOption.copy(aXhtmlNsKey.getLength()); + } + } + + SetupFilterFromPropertyValues(aStoreMap.getAsConstPropertyValueList()); +} + +void SwHTMLWriter::SetupFilterFromPropertyValues( + const css::uno::Sequence<css::beans::PropertyValue>& rPropertyValues) +{ + comphelper::SequenceAsHashMap aStoreMap(rPropertyValues); + auto it = aStoreMap.find("RTFOLEMimeType"); + if (it != aStoreMap.end()) + { + it->second >>= m_aRTFOLEMimeType; + } + + it = aStoreMap.find("ExportImagesAsOLE"); + if (it != aStoreMap.end()) + { + it->second >>= m_bExportImagesAsOLE; + } + + it = aStoreMap.find("ExportFormulasAsPDF"); + if (it != aStoreMap.end()) + { + it->second >>= m_bExportFormulasAsPDF; + } + + it = aStoreMap.find("ShapeDPI"); + if (it != aStoreMap.end()) + { + sal_Int32 nVal{}; + it->second >>= nVal; + m_nShapeDPI.emplace(nVal); + } + + it = aStoreMap.find("SkipImages"); + if (it != aStoreMap.end()) + { + bool bVal{}; + it->second >>= bVal; + mbSkipImages = bVal; + } + + it = aStoreMap.find("SkipHeaderFooter"); + if (it != aStoreMap.end()) + { + bool bVal{}; + it->second >>= bVal; + mbSkipHeaderFooter = bVal; + } + + // this option can be "on" together with any of above + it = aStoreMap.find("NoPrettyPrint"); + if (it != aStoreMap.end()) + { + m_nWishLineLen = -1; + m_bPrettyPrint = false; + } + + it = aStoreMap.find("EmbedImages"); + if (it != aStoreMap.end()) + { + bool bVal{}; + it->second >>= bVal; + mbEmbedImages = bVal; + } + + it = aStoreMap.find("NoLineLimit"); + if (it != aStoreMap.end()) + { + bool bVal{}; + it->second >>= bVal; + if (bVal) + { + m_nWishLineLen = -1; + } + } + + it = aStoreMap.find("XHTML"); + if (it != aStoreMap.end()) + { + bool bVal{}; + it->second >>= bVal; + mbXHTML = bVal; + } + + it = aStoreMap.find("XhtmlNs"); + if (it != aStoreMap.end()) + { + OUString aVal; + it->second >>= aVal; + + maNamespace = aVal.toUtf8(); + if (maNamespace == "reqif-xhtml") + { + mbReqIF = true; + // XHTML is always just a fragment inside ReqIF. + mbSkipHeaderFooter = true; + } + // XHTML namespace implies XHTML. + mbXHTML = true; + } + + it = aStoreMap.find("LeadingTabWidth"); + if (it != aStoreMap.end()) + { + sal_Int32 nVal{}; + it->second >>= nVal; + m_nLeadingTabWidth.emplace(nVal); + } + + it = aStoreMap.find("PreserveSpaces"); + if (it != aStoreMap.end()) + { + // Paragraphs with leading/trailing/repeated whitespace will have "white-space: pre-wrap" + // style; whitespace in content will not be altered (except for "LeadingTabWidth" effects) + bool bVal = false; + it->second >>= bVal; + m_bPreserveSpacesOnWrite = bVal; + } +} + +ErrCode SwHTMLWriter::WriteStream() +{ + if (!SW_MOD()) + return ERRCODE_ABORT; + // Intercept paste output if requested. + char* pPasteEnv = getenv("SW_DEBUG_HTML_PASTE_TO"); + std::unique_ptr<SvStream> pPasteStream; + SvStream* pOldPasteStream = nullptr; + if (pPasteEnv) + { + OUString aPasteStr; + if (osl::FileBase::getFileURLFromSystemPath(OUString::fromUtf8(pPasteEnv), aPasteStr) + == osl::FileBase::E_None) + { + pPasteStream.reset(new SvFileStream(aPasteStr, StreamMode::WRITE)); + pOldPasteStream = &Strm(); + SetStream(pPasteStream.get()); + } + } + comphelper::ScopeGuard g([this, pOldPasteStream] { this->SetStream(pOldPasteStream); }); + + // font heights 1-7 + m_aFontHeights[0] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_1::get() * 20; + m_aFontHeights[1] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_2::get() * 20; + m_aFontHeights[2] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_3::get() * 20; + m_aFontHeights[3] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_4::get() * 20; + m_aFontHeights[4] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_5::get() * 20; + m_aFontHeights[5] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_6::get() * 20; + m_aFontHeights[6] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_7::get() * 20; + + // output styles anyway + // (then also top and bottom paragraph spacing) + m_nExportMode = SvxHtmlOptions::GetExportMode(); + m_nHTMLMode = GetHtmlMode(nullptr); + + if( HTML_CFG_WRITER == m_nExportMode || HTML_CFG_NS40 == m_nExportMode ) + m_nHTMLMode |= HTMLMODE_BLOCK_SPACER; + + if( HTML_CFG_WRITER == m_nExportMode || HTML_CFG_MSIE == m_nExportMode ) + m_nHTMLMode |= (HTMLMODE_FLOAT_FRAME | HTMLMODE_LSPACE_IN_NUMBER_BULLET); + + if( HTML_CFG_MSIE == m_nExportMode ) + m_nHTMLMode |= HTMLMODE_NBSP_IN_TABLES; + + // For all three of HTML_CFG_WRITER, HTML_CFG_NS40, HTML_CFG_MSIE + m_nHTMLMode |= HTMLMODE_ABS_POS_FLY | HTMLMODE_ABS_POS_DRAW; + + if( HTML_CFG_WRITER == m_nExportMode ) + m_nHTMLMode |= HTMLMODE_FLY_MARGINS; + + if( HTML_CFG_NS40 == m_nExportMode ) + m_nHTMLMode |= HTMLMODE_BORDER_NONE; + + m_nHTMLMode |= HTMLMODE_FONT_GENERIC; + + if( HTML_CFG_NS40==m_nExportMode ) + m_nHTMLMode |= HTMLMODE_NO_CONTROL_CENTERING; + + m_bCfgOutStyles = IsHTMLMode(HTMLMODE_SOME_STYLES | HTMLMODE_FULL_STYLES); + m_bCfgNetscape4 = (HTML_CFG_NS40 == m_nExportMode); + + if( IsHTMLMode(HTMLMODE_SOME_STYLES | HTMLMODE_FULL_STYLES) ) + m_nHTMLMode |= HTMLMODE_PRINT_EXT; + + m_eCSS1Unit = SW_MOD()->GetMetric( m_pDoc->getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE) ); + + // Only for the MS-IE we favour the export of styles. + m_bCfgPreferStyles = HTML_CFG_MSIE == m_nExportMode; + + m_bCfgStarBasic = officecfg::Office::Common::Filter::HTML::Export::Basic::get(); + + m_bCfgFormFeed = !IsHTMLMode(HTMLMODE_PRINT_EXT); + m_bCfgCpyLinkedGrfs = officecfg::Office::Common::Filter::HTML::Export::LocalGraphic::get(); + + m_bCfgPrintLayout = officecfg::Office::Common::Filter::HTML::Export::PrintLayout::get(); + + // get HTML template + bool bOldHTMLMode = false; + SwTextFormatColls::size_type nOldTextFormatCollCnt = 0; + SwCharFormats::size_type nOldCharFormatCnt = 0; + + OSL_ENSURE( !m_xTemplate.is(), "Where is the HTML template coming from?" ); + m_xTemplate = static_cast<HTMLReader*>(ReadHTML)->GetTemplateDoc(*m_pDoc); + if( m_xTemplate.is() ) + { + bOldHTMLMode = m_xTemplate->getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE); + m_xTemplate->getIDocumentSettingAccess().set(DocumentSettingId::HTML_MODE, true); + + nOldTextFormatCollCnt = m_xTemplate->GetTextFormatColls()->size(); + nOldCharFormatCnt = m_xTemplate->GetCharFormats()->size(); + } + + if( m_bShowProgress ) + ::StartProgress( STR_STATSTR_W4WWRITE, 0, sal_Int32(m_pDoc->GetNodes().Count()), + m_pDoc->GetDocShell()); + + m_xDfltColor.reset(); + m_xFootEndNotes.reset(); + m_pFormatFootnote = nullptr; + m_bOutTable = m_bOutHeader = m_bOutFooter = m_bOutFlyFrame = false; + mxFormComps.clear(); + m_nFormCntrlCnt = 0; + m_bPreserveForm = false; + m_bClearLeft = m_bClearRight = false; + m_bLFPossible = false; + m_bSpacePreserve = false; + + m_nLeftMargin = m_nDfltLeftMargin = m_nDfltRightMargin = 0; + m_nDfltTopMargin = m_nDfltBottomMargin = 0; + m_nFirstLineIndent = m_nDfltFirstLineIndent = 0; + m_bFirstCSS1Property = m_bFirstCSS1Rule = false; + m_bCSS1IgnoreFirstPageDesc = false; + m_nIndentLvl = 0; + m_nLastLFPos = 0; + m_nDefListLvl = 0; + m_nDefListMargin = ((m_xTemplate.is() && !m_bCfgOutStyles) ? m_xTemplate.get() : m_pDoc) + ->getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_HTML_DD, false ) + ->GetTextLeftMargin().GetTextLeft(); + m_nHeaderFooterSpace = 0; + m_nTextAttrsToIgnore = 0; + m_nCSS1OutMode = 0; + SvtScriptType nScript = SvtLanguageOptions::GetScriptTypeOfLanguage( GetAppLanguage() ); + switch( nScript ) + { + case SvtScriptType::ASIAN: + m_nCSS1Script = CSS1_OUTMODE_CJK; + break; + case SvtScriptType::COMPLEX: + m_nCSS1Script = CSS1_OUTMODE_CTL; + break; + default: + m_nCSS1Script = CSS1_OUTMODE_WESTERN; + break; + } + const SvxLanguageItem& rLangitem = m_pDoc->GetDefault(GetLangWhichIdFromScript(m_nCSS1Script)); + m_eLang = rLangitem.GetLanguage(); + + m_nFootNote = m_nEndNote = 0; + + m_nWarn = ERRCODE_NONE; + GetNumInfo().Clear(); + m_pNextNumRuleInfo = nullptr; + + OString aStartTags; + + // respect table and section at document beginning + { + if (m_bWriteAll) + { + while (const SwStartNode* pTNd = m_pCurrentPam->GetPointNode().FindTableBoxStartNode()) + { + // start with table node !! + m_pCurrentPam->GetPoint()->Assign(*pTNd->FindTableNode()); + + if (m_bWriteOnlyFirstTable) + m_pCurrentPam->GetMark()->Assign( + *m_pCurrentPam->GetPointNode().EndOfSectionNode()); + } + } + + // first node (which can contain a page break) + m_pStartNdIdx = new SwNodeIndex( m_pCurrentPam->GetPoint()->GetNode() ); + + SwSectionNode * pSNd = m_pCurrentPam->GetPointNode().FindSectionNode(); + while( pSNd ) + { + if( m_bWriteAll ) + { + // start with section node !! + m_pCurrentPam->GetPoint()->Assign(*pSNd); + } + else + { + OSL_ENSURE( SectionType::FileLink != pSNd->GetSection().GetType(), + "Export linked areas at document beginning is not implemented" ); + + // save only the tag of section + OString aName = HTMLOutFuncs::ConvertStringToHTML( + pSNd->GetSection().GetSectionName() ); + + aStartTags = + "<" + GetNamespace() + OOO_STRING_SVTOOLS_HTML_division + " " OOO_STRING_SVTOOLS_HTML_O_id + "=\"" + aName + "\">" + + aStartTags; + } + // FindSectionNode() on a SectionNode return the same! + pSNd = pSNd->StartOfSectionNode()->FindSectionNode(); + } + } + + // create table of the floating frames, but only when the whole + // document is saved + m_aHTMLPosFlyFrames.clear(); + CollectFlyFrames(); + m_nLastParaToken = HtmlTokenId::NONE; + + if (mbReqIF && !m_bWriteAll + && *m_pCurrentPam->GetPoint() == *m_pCurrentPam->GetMark() + && m_pCurrentPam->GetPoint()->GetNode().IsOLENode() && m_aHTMLPosFlyFrames.size() == 1) + { + // A single OLE object selection must be output: do it directly (without replacement) + OutHTML_FrameFormatOLENodeGrf(*this, m_aHTMLPosFlyFrames[0]->GetFormat(), true, false); + } + else + { + GetControls(); + CollectLinkTargets(); + + sal_uInt16 nHeaderAttrs = 0; + m_pCurrPageDesc = MakeHeader( nHeaderAttrs ); + + SetLFPossible(true); + + // output forms which contain only HiddenControls + OutHiddenForms(); + + if( !aStartTags.isEmpty() ) + Strm().WriteOString( aStartTags ); + + const SwFormatHeader *pFormatHeader; + const SfxItemSet& rPageItemSet = m_pCurrPageDesc->GetMaster().GetAttrSet(); + if( !m_bWriteClipboardDoc && m_pDoc->GetDocShell() && + (!m_pDoc->getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE) && + !m_pDoc->getIDocumentSettingAccess().get(DocumentSettingId::BROWSE_MODE)) && + (pFormatHeader = rPageItemSet.GetItemIfSet( RES_HEADER )) ) + { + const SwFrameFormat *pHeaderFormat = pFormatHeader->GetHeaderFormat(); + if( pHeaderFormat ) + OutHTML_HeaderFooter( *this, *pHeaderFormat, true ); + } + + m_nTextAttrsToIgnore = nHeaderAttrs; + Out_SwDoc( m_pOrigPam ); + m_nTextAttrsToIgnore = 0; + + if( mxFormComps.is() ) + OutForm( false, mxFormComps ); + + if( m_xFootEndNotes ) + OutFootEndNotes(); + + const SwFormatFooter* pFormatFooter; + if( !m_bWriteClipboardDoc && m_pDoc->GetDocShell() && + (!m_pDoc->getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE) && !m_pDoc->getIDocumentSettingAccess().get(DocumentSettingId::BROWSE_MODE)) && + (pFormatFooter = rPageItemSet.GetItemIfSet( RES_FOOTER )) ) + { + const SwFrameFormat *pFooterFormat = pFormatFooter->GetFooterFormat(); + if( pFooterFormat ) + OutHTML_HeaderFooter( *this, *pFooterFormat, false ); + } + + if (IsLFPossible()) + OutNewLine(); + if (!mbSkipHeaderFooter) + { + HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + OOO_STRING_SVTOOLS_HTML_body), false ); + OutNewLine(); + HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + OOO_STRING_SVTOOLS_HTML_html), false ); + } + else if (mbReqIF) + // ReqIF: end xhtml.BlkStruct.class. + HTMLOutFuncs::Out_AsciiTag(Strm(), Concat2View(GetNamespace() + OOO_STRING_SVTOOLS_HTML_division), false); + } + // delete the table with floating frames + OSL_ENSURE( m_aHTMLPosFlyFrames.empty(), "Were not all frames output?" ); + m_aHTMLPosFlyFrames.clear(); + + m_aHTMLControls.clear(); + + m_CharFormatInfos.clear(); + m_TextCollInfos.clear(); + m_aImgMapNames.clear(); + m_aImplicitMarks.clear(); + m_aOutlineMarks.clear(); + m_aOutlineMarkPoss.clear(); + m_aNumRuleNames.clear(); + m_aScriptParaStyles.clear(); + m_aScriptTextStyles.clear(); + + m_xDfltColor.reset(); + + delete m_pStartNdIdx; + m_pStartNdIdx = nullptr; + + mxFormComps.clear(); + + OSL_ENSURE( !m_xFootEndNotes, + "SwHTMLWriter::Write: Footnotes not deleted by OutFootEndNotes" ); + + m_pCurrPageDesc = nullptr; + + ClearNextNumInfo(); + + for(OUString & s : m_aBulletGrfs) + s.clear(); + + if( m_bShowProgress ) + ::EndProgress( m_pDoc->GetDocShell() ); + + if( m_xTemplate.is() ) + { + // delete character and paragraph templates created during export + auto nTextFormatCollCnt = m_xTemplate->GetTextFormatColls()->size(); + while( nTextFormatCollCnt > nOldTextFormatCollCnt ) + m_xTemplate->DelTextFormatColl( --nTextFormatCollCnt ); + OSL_ENSURE( m_xTemplate->GetTextFormatColls()->size() == nOldTextFormatCollCnt, + "wrong number of TextFormatColls deleted" ); + + auto nCharFormatCnt = m_xTemplate->GetCharFormats()->size(); + while( nCharFormatCnt > nOldCharFormatCnt ) + m_xTemplate->DelCharFormat( --nCharFormatCnt ); + OSL_ENSURE( m_xTemplate->GetCharFormats()->size() == nOldCharFormatCnt, + "wrong number of CharFormats deleted" ); + + // restore HTML mode + m_xTemplate->getIDocumentSettingAccess().set(DocumentSettingId::HTML_MODE, bOldHTMLMode); + + m_xTemplate.clear(); + } + + return m_nWarn; +} + +static const SwFormatCol *lcl_html_GetFormatCol( const SwSection& rSection, + const SwSectionFormat& rFormat ) +{ + if( SectionType::FileLink == rSection.GetType() ) + return nullptr; + + const SwFormatCol *pCol = rFormat.GetAttrSet().GetItemIfSet(RES_COL,false); + if (pCol->GetNumCols() > 1 ) + return pCol; + + return nullptr; +} + +static bool lcl_html_IsMultiColStart( const SwHTMLWriter& rHTMLWrt, SwNodeOffset nIndex ) +{ + bool bRet = false; + const SwSectionNode *pSectNd = + rHTMLWrt.m_pDoc->GetNodes()[nIndex]->GetSectionNode(); + if( pSectNd ) + { + const SwSection& rSection = pSectNd->GetSection(); + const SwSectionFormat *pFormat = rSection.GetFormat(); + if( pFormat && lcl_html_GetFormatCol( rSection, *pFormat ) ) + bRet = true; + } + + return bRet; +} + +static bool lcl_html_IsMultiColEnd( const SwHTMLWriter& rHTMLWrt, SwNodeOffset nIndex ) +{ + bool bRet = false; + const SwEndNode *pEndNd = rHTMLWrt.m_pDoc->GetNodes()[nIndex]->GetEndNode(); + if( pEndNd ) + bRet = lcl_html_IsMultiColStart( rHTMLWrt, + pEndNd->StartOfSectionIndex() ); + + return bRet; +} + +static void lcl_html_OutSectionStartTag( SwHTMLWriter& rHTMLWrt, + const SwSection& rSection, + const SwSectionFormat& rFormat, + const SwFormatCol *pCol, + bool bContinued=false ) +{ + OSL_ENSURE( pCol || !bContinued, "Continuation of DIV" ); + + if (rHTMLWrt.IsLFPossible()) + rHTMLWrt.OutNewLine(); + + OStringBuffer sOut("<" + rHTMLWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_division); + + const OUString& rName = rSection.GetSectionName(); + if( !rName.isEmpty() && !bContinued ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_id "=\""); + rHTMLWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( rHTMLWrt.Strm(), rName ); + sOut.append('\"'); + } + + rHTMLWrt.Strm().WriteOString( sOut.makeStringAndClear() ); + if (!rHTMLWrt.mbXHTML) + { + SvxFrameDirection nDir = rHTMLWrt.GetHTMLDirection(rFormat.GetAttrSet()); + rHTMLWrt.OutDirection(nDir); + } + + if( SectionType::FileLink == rSection.GetType() ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_href "=\""); + rHTMLWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + + const OUString& aFName = rSection.GetLinkFileName(); + sal_Int32 nIdx{ 0 }; + OUString aURL( aFName.getToken(0, sfx2::cTokenSeparator, nIdx) ); + OUString aFilter( aFName.getToken(0, sfx2::cTokenSeparator, nIdx) ); + OUString aSection( aFName.getToken(0, sfx2::cTokenSeparator, nIdx) ); + + OUString aEncURL( URIHelper::simpleNormalizedMakeRelative(rHTMLWrt.GetBaseURL(), aURL ) ); + sal_Unicode cDelim = 255U; + bool bURLContainsDelim = (-1 != aEncURL.indexOf( cDelim ) ); + + HTMLOutFuncs::Out_String( rHTMLWrt.Strm(), aEncURL ); + const char* const pDelim = "ÿ"; + if( !aFilter.isEmpty() || !aSection.isEmpty() || bURLContainsDelim ) + rHTMLWrt.Strm().WriteOString( pDelim ); + if( !aFilter.isEmpty() ) + HTMLOutFuncs::Out_String( rHTMLWrt.Strm(), aFilter ); + if( !aSection.isEmpty() || bURLContainsDelim ) + rHTMLWrt.Strm().WriteOString( pDelim ); + if( !aSection.isEmpty() ) + { + aSection = aSection.replaceAll(u"%", u"%25"); + aSection = aSection.replaceAll(OUStringChar(cDelim), u"%FF"); + HTMLOutFuncs::Out_String( rHTMLWrt.Strm(), aSection ); + } + sOut.append('\"'); + } + else if( pCol ) + { + // minimum gutter width + sal_uInt16 nGutter = pCol->GetGutterWidth( true ); + if( nGutter!=USHRT_MAX ) + { + nGutter = SwHTMLWriter::ToPixel(nGutter); + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_gutter "=\"" + OString::number(nGutter) + "\""); + } + } + + rHTMLWrt.Strm().WriteOString( sOut ); + sOut.setLength(0); + if( rHTMLWrt.IsHTMLMode( rHTMLWrt.m_bCfgOutStyles ? HTMLMODE_ON : 0 ) ) + rHTMLWrt.OutCSS1_SectionFormatOptions( rFormat, pCol ); + + rHTMLWrt.Strm().WriteChar( '>' ); + + rHTMLWrt.SetLFPossible(true); + if( !rName.isEmpty() && !bContinued ) + rHTMLWrt.OutImplicitMark( rName, "region" ); + + rHTMLWrt.IncIndentLevel(); +} + +static void lcl_html_OutSectionEndTag( SwHTMLWriter& rHTMLWrt ) +{ + rHTMLWrt.DecIndentLevel(); + if (rHTMLWrt.IsLFPossible()) + rHTMLWrt.OutNewLine(); + HTMLOutFuncs::Out_AsciiTag( rHTMLWrt.Strm(), Concat2View(rHTMLWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_division), false ); + rHTMLWrt.SetLFPossible(true); +} + +static SwHTMLWriter& OutHTML_Section( SwHTMLWriter& rWrt, const SwSectionNode& rSectNd ) +{ + // End <PRE> and any <DL>, because a definition list's level may + // change inside the section. + rWrt.ChangeParaToken( HtmlTokenId::NONE ); + rWrt.OutAndSetDefList( 0 ); + + const SwSection& rSection = rSectNd.GetSection(); + const SwSectionFormat *pFormat = rSection.GetFormat(); + OSL_ENSURE( pFormat, "Section without a format?" ); + + bool bStartTag = true; + bool bEndTag = true; + const SwSectionFormat *pSurrFormat = nullptr; + const SwSectionNode *pSurrSectNd = nullptr; + const SwSection *pSurrSection = nullptr; + const SwFormatCol *pSurrCol = nullptr; + + SwNodeOffset nSectSttIdx = rSectNd.GetIndex(); + SwNodeOffset nSectEndIdx = rSectNd.EndOfSectionIndex(); + const SwFormatCol *pCol = lcl_html_GetFormatCol( rSection, *pFormat ); + if( pCol ) + { + // If the next node is a columned section node, too, don't export + // an empty section. + if( lcl_html_IsMultiColStart( rWrt, nSectSttIdx+1 ) ) + bStartTag = false; + + // The same applies if the section end with another columned section. + if( lcl_html_IsMultiColEnd( rWrt, nSectEndIdx-1 ) ) + bEndTag = false; + + // is there a columned section around this one? + const SwStartNode *pSttNd = rSectNd.StartOfSectionNode(); + if( pSttNd ) + { + pSurrSectNd = pSttNd->FindSectionNode(); + if( pSurrSectNd ) + { + const SwStartNode *pBoxSttNd = pSttNd->FindTableBoxStartNode(); + if( !pBoxSttNd || + pBoxSttNd->GetIndex() < pSurrSectNd->GetIndex() ) + { + pSurrSection = &pSurrSectNd->GetSection(); + pSurrFormat = pSurrSection->GetFormat(); + if( pSurrFormat ) + pSurrCol = lcl_html_GetFormatCol( *pSurrSection, + *pSurrFormat ); + } + } + } + } + + // The surrounding section must be closed before the current one is + // opened, except that it start immediately before the current one or + // another end immediately before the current one + if( pSurrCol && nSectSttIdx - pSurrSectNd->GetIndex() > SwNodeOffset(1) && + !lcl_html_IsMultiColEnd( rWrt, nSectSttIdx-1 ) ) + lcl_html_OutSectionEndTag( rWrt ); + + if( bStartTag ) + lcl_html_OutSectionStartTag( rWrt, rSection, *pFormat, pCol ); + + { + HTMLSaveData aSaveData( rWrt, + rWrt.m_pCurrentPam->GetPoint()->GetNodeIndex()+1, + rSectNd.EndOfSectionIndex(), + false, pFormat ); + rWrt.Out_SwDoc( rWrt.m_pCurrentPam.get() ); + } + + rWrt.m_pCurrentPam->GetPoint()->Assign(*rSectNd.EndOfSectionNode()); + + if( bEndTag ) + lcl_html_OutSectionEndTag( rWrt ); + + // The surrounding section must be started again, except that it ends + // immediately behind the current one. + if( pSurrCol && + pSurrSectNd->EndOfSectionIndex() - nSectEndIdx > SwNodeOffset(1) && + !lcl_html_IsMultiColStart( rWrt, nSectEndIdx+1 ) ) + lcl_html_OutSectionStartTag( rWrt, *pSurrSection, *pSurrFormat, + pSurrCol, true ); + + return rWrt; +} + +void SwHTMLWriter::Out_SwDoc( SwPaM* pPam ) +{ + bool bSaveWriteAll = m_bWriteAll; // save + bool bIncludeHidden = officecfg::Office::Writer::FilterFlags::HTML::IncludeHiddenText::get(); + + // search next text::Bookmark position from text::Bookmark table + m_nBkmkTabPos = m_bWriteAll ? FindPos_Bkmk( *m_pCurrentPam->GetPoint() ) : -1; + + // output all areas of PaM's in the HTML file + do { + m_bWriteAll = bSaveWriteAll; + m_bFirstLine = true; + + // search for first on PaM created FlyFrame + // still missing: + + while( m_pCurrentPam->GetPoint()->GetNodeIndex() < m_pCurrentPam->GetMark()->GetNodeIndex() || + (m_pCurrentPam->GetPoint()->GetNodeIndex() == m_pCurrentPam->GetMark()->GetNodeIndex() && + m_pCurrentPam->GetPoint()->GetContentIndex() <= m_pCurrentPam->GetMark()->GetContentIndex()) ) + { + SwNode& rNd = m_pCurrentPam->GetPointNode(); + + OSL_ENSURE( !(rNd.IsGrfNode() || rNd.IsOLENode()), + "Unexpected Grf- or OLE-Node here" ); + + if( rNd.IsTextNode() ) + { + SwTextNode* pTextNd = rNd.GetTextNode(); + if (!pTextNd->IsHidden() || bIncludeHidden) + { + if (!m_bFirstLine) + m_pCurrentPam->GetPoint()->Assign(*pTextNd, 0); + + OutHTML_SwTextNode(*this, *pTextNd); + } + } + else if( rNd.IsTableNode() ) + { + OutHTML_SwTableNode( *this, *rNd.GetTableNode(), nullptr ); + m_nBkmkTabPos = m_bWriteAll ? FindPos_Bkmk( *m_pCurrentPam->GetPoint() ) : -1; + } + else if( rNd.IsSectionNode() ) + { + SwSectionNode* pSectionNode = rNd.GetSectionNode(); + if (!pSectionNode->GetSection().IsHiddenFlag() || bIncludeHidden) + { + OutHTML_Section( *this, *pSectionNode ); + m_nBkmkTabPos = m_bWriteAll ? FindPos_Bkmk( *m_pCurrentPam->GetPoint() ) : -1; + } + } + else if( &rNd == &m_pDoc->GetNodes().GetEndOfContent() ) + break; + + m_pCurrentPam->GetPoint()->Adjust(SwNodeOffset(+1)); // move + SwNodeOffset nPos = m_pCurrentPam->GetPoint()->GetNodeIndex(); + + if( m_bShowProgress ) + ::SetProgressState( sal_Int32(nPos), m_pDoc->GetDocShell() ); // How far ? + + /* If only the selected area should be saved, so only the complete + * nodes should be saved, this means the first and n-th node + * partly, the 2nd till n-1 node complete. (complete means with + * all formats!) + */ + m_bWriteAll = bSaveWriteAll || + nPos != m_pCurrentPam->GetMark()->GetNodeIndex(); + m_bFirstLine = false; + m_bOutFooter = false; // after one node no footer anymore + } + + ChangeParaToken( HtmlTokenId::NONE ); // MIB 8.7.97: We're doing it here and not at the caller + OutAndSetDefList( 0 ); + + } while( CopyNextPam( &pPam ) ); // until all PaM's processed + + m_bWriteAll = bSaveWriteAll; // reset to old values +} + +// write the StyleTable, general data, header/footer/footnotes +static void OutBodyColor( const char* pTag, const SwFormat *pFormat, + SwHTMLWriter& rHWrt ) +{ + const SwFormat *pRefFormat = nullptr; + + if( rHWrt.m_xTemplate.is() ) + pRefFormat = SwHTMLWriter::GetTemplateFormat( pFormat->GetPoolFormatId(), + &rHWrt.m_xTemplate->getIDocumentStylePoolAccess() ); + + const SvxColorItem *pColorItem = nullptr; + + const SfxItemSet& rItemSet = pFormat->GetAttrSet(); + const SvxColorItem *pCItem = rItemSet.GetItemIfSet( RES_CHRATR_COLOR ); + const SvxColorItem *pRefItem = nullptr; + if (pRefFormat) + pRefItem = pRefFormat->GetAttrSet().GetItemIfSet( RES_CHRATR_COLOR ); + if( pCItem ) + { + // only when the item is set in the template of the current document + // or has a different value as the in HTML template, it will be set + + if( !pRefItem ) + { + pColorItem = pCItem; + } + else + { + Color aColor( pCItem->GetValue() ); + if( COL_AUTO == aColor ) + aColor = COL_BLACK; + + Color aRefColor( pRefItem->GetValue() ); + if( COL_AUTO == aRefColor ) + aRefColor = COL_BLACK; + + if( !aColor.IsRGBEqual( aRefColor ) ) + pColorItem = pCItem; + } + } + else if( pRefItem ) + { + // The item was still set in the HTML template so we output the default + pColorItem = &rItemSet.GetPool()->GetDefaultItem( RES_CHRATR_COLOR ); + } + + if( pColorItem ) + { + OString sOut = OString::Concat(" ") + pTag + "="; + rHWrt.Strm().WriteOString( sOut ); + Color aColor( pColorItem->GetValue() ); + if( COL_AUTO == aColor ) + aColor = COL_BLACK; + HTMLOutFuncs::Out_Color( rHWrt.Strm(), aColor ); + if( RES_POOLCOLL_STANDARD==pFormat->GetPoolFormatId() ) + rHWrt.m_xDfltColor = aColor; + } +} + +sal_uInt16 SwHTMLWriter::OutHeaderAttrs() +{ + SwNodeOffset nIdx = m_pCurrentPam->GetPoint()->GetNodeIndex(); + SwNodeOffset nEndIdx = m_pCurrentPam->GetMark()->GetNodeIndex(); + + SwTextNode *pTextNd = nullptr; + while( nIdx<=nEndIdx && + nullptr==(pTextNd=m_pDoc->GetNodes()[nIdx]->GetTextNode()) ) + nIdx++; + + OSL_ENSURE( pTextNd, "No Text-Node found" ); + if( !pTextNd || !pTextNd->HasHints() ) + return 0; + + sal_uInt16 nAttrs = 0; + const size_t nCntAttr = pTextNd->GetSwpHints().Count(); + sal_Int32 nOldPos = 0; + for( size_t i=0; i<nCntAttr; ++i ) + { + const SwTextAttr *pHt = pTextNd->GetSwpHints().Get(i); + if( !pHt->End() ) + { + sal_Int32 nPos = pHt->GetStart(); + if( nPos-nOldPos > 1 + || ( pHt->Which() != RES_TXTATR_FIELD + && pHt->Which() != RES_TXTATR_ANNOTATION ) ) + break; + + const SwFieldIds nFieldWhich = + static_cast<const SwFormatField&>(pHt->GetAttr()).GetField()->GetTyp()->Which(); + if( SwFieldIds::Postit!=nFieldWhich && + SwFieldIds::Script!=nFieldWhich ) + break; + + OutNewLine(); + OutHTML_SwFormatField( *this, pHt->GetAttr() ); + nOldPos = nPos; + OSL_ENSURE( nAttrs<SAL_MAX_UINT16, "Too many attributes" ); + nAttrs++; + } + } + + return nAttrs; +} + +const SwPageDesc *SwHTMLWriter::MakeHeader( sal_uInt16 &rHeaderAttrs ) +{ + OStringBuffer sOut; + if (!mbSkipHeaderFooter) + { + if (mbXHTML) + sOut.append(OOO_STRING_SVTOOLS_HTML_doctype " " OOO_STRING_SVTOOLS_XHTML_doctype11); + else + sOut.append(OOO_STRING_SVTOOLS_HTML_doctype " " OOO_STRING_SVTOOLS_HTML_doctype5); + HTMLOutFuncs::Out_AsciiTag( Strm(), sOut.makeStringAndClear() ); // No GetNamespace() here. + + // build prelude + OutNewLine(); + HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + OOO_STRING_SVTOOLS_HTML_html) ); + + OutNewLine(); + HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + OOO_STRING_SVTOOLS_HTML_head) ); + + IncIndentLevel(); // indent content of <HEAD> + + // DocumentInfo + OString sIndent = GetIndentString(); + + uno::Reference<document::XDocumentProperties> xDocProps; + SwDocShell *pDocShell(m_pDoc->GetDocShell()); + if (pDocShell) + { + uno::Reference<document::XDocumentPropertiesSupplier> xDPS( + pDocShell->GetModel(), uno::UNO_QUERY_THROW); + xDocProps.set(xDPS->getDocumentProperties()); + } + + // xDocProps may be null here (when copying) + SfxFrameHTMLWriter::Out_DocInfo( Strm(), GetBaseURL(), xDocProps, + sIndent.getStr() ); + + // comments and meta-tags of first paragraph + rHeaderAttrs = OutHeaderAttrs(); + + OutFootEndNoteInfo(); + } + + const SwPageDesc *pPageDesc = nullptr; + + // In none HTML documents the first set template will be exported + // and if none is set the default template + SwNodeOffset nNodeIdx = m_pCurrentPam->GetPoint()->GetNodeIndex(); + + while( nNodeIdx < m_pDoc->GetNodes().Count() ) + { + SwNode *pNd = m_pDoc->GetNodes()[ nNodeIdx ]; + if( pNd->IsContentNode() ) + { + pPageDesc = pNd->GetContentNode()->GetAttr(RES_PAGEDESC).GetPageDesc(); + break; + } + else if( pNd->IsTableNode() ) + { + pPageDesc = pNd->GetTableNode()->GetTable().GetFrameFormat() + ->GetPageDesc().GetPageDesc(); + break; + } + + nNodeIdx++; + } + + if( !pPageDesc ) + pPageDesc = &m_pDoc->GetPageDesc( 0 ); + + if (!mbSkipHeaderFooter) + { + // and now ... the style sheet!!! + if( m_bCfgOutStyles ) + { + OutStyleSheet( *pPageDesc ); + } + + // and now ... the BASIC and JavaScript! + if( m_pDoc->GetDocShell() ) // BASIC is possible only in case we have a DocShell + OutBasic(*this); + + DecIndentLevel(); // indent content of <HEAD> + OutNewLine(); + HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + OOO_STRING_SVTOOLS_HTML_head), false ); + + // the body won't be indented, because then everything would be indented! + OutNewLine(); + sOut.append("<" + GetNamespace() + OOO_STRING_SVTOOLS_HTML_body); + Strm().WriteOString( sOut ); + sOut.setLength(0); + + // language + OutLanguage( m_eLang ); + + // output text colour, when it was set in the default template or was changed + OutBodyColor( OOO_STRING_SVTOOLS_HTML_O_text, + m_pDoc->getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD, false ), + *this ); + + // colour of (un)visited links + OutBodyColor( OOO_STRING_SVTOOLS_HTML_O_link, + m_pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool( RES_POOLCHR_INET_NORMAL ), + *this ); + OutBodyColor( OOO_STRING_SVTOOLS_HTML_O_vlink, + m_pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool( RES_POOLCHR_INET_VISIT ), + *this ); + + const SfxItemSet& rItemSet = pPageDesc->GetMaster().GetAttrSet(); + + // fdo#86857 page styles now contain the XATTR_*, not RES_BACKGROUND + std::unique_ptr<SvxBrushItem> const aBrushItem(getSvxBrushItemFromSourceSet(rItemSet, RES_BACKGROUND)); + OutBackground(aBrushItem.get(), true); + + m_nDirection = GetHTMLDirection( rItemSet ); + OutDirection( m_nDirection ); + + if( m_bCfgOutStyles ) + { + OutCSS1_BodyTagStyleOpt( *this, rItemSet ); + } + // append events + if( m_pDoc->GetDocShell() ) // BASIC is possible only in case we have a DocShell + OutBasicBodyEvents(); + + Strm().WriteChar( '>' ); + } + else if (mbReqIF) + // ReqIF: start xhtml.BlkStruct.class. + HTMLOutFuncs::Out_AsciiTag(Strm(), Concat2View(GetNamespace() + OOO_STRING_SVTOOLS_HTML_division)); + + return pPageDesc; +} + +void SwHTMLWriter::OutAnchor( const OUString& rName ) +{ + if (mbReqIF) + { + // <a id=".."> has to be unique inside the whole document, but + // we only write a fragment, so we can't ensure the ID is indeed + // unique. Just don't write anchors in the ReqIF case. + return; + } + + OStringBuffer sOut("<" + GetNamespace() + OOO_STRING_SVTOOLS_HTML_anchor " "); + if (!mbXHTML) + { + sOut.append(OOO_STRING_SVTOOLS_HTML_O_name "=\""); + Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( Strm(), rName ).WriteOString( "\">" ); + } + else + { + // XHTML wants 'id' instead of 'name', also the value can't contain + // spaces. + sOut.append(OOO_STRING_SVTOOLS_HTML_O_id "=\""); + Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( Strm(), rName.replace(' ', '_') ).WriteOString( "\">" ); + } + HTMLOutFuncs::Out_AsciiTag( Strm(), Concat2View(GetNamespace() + OOO_STRING_SVTOOLS_HTML_anchor), false ); +} + +void SwHTMLWriter::OutBookmarks() +{ + // fetch current bookmark + const ::sw::mark::IMark* pBookmark = nullptr; + IDocumentMarkAccess* const pMarkAccess = m_pDoc->getIDocumentMarkAccess(); + if(m_nBkmkTabPos != -1) + pBookmark = pMarkAccess->getAllMarksBegin()[m_nBkmkTabPos]; + // Output all bookmarks in this paragraph. The content position + // for the moment isn't considered! + SwNodeOffset nNode = m_pCurrentPam->GetPoint()->GetNodeIndex(); + while( m_nBkmkTabPos != -1 + && pBookmark->GetMarkPos().GetNodeIndex() == nNode ) + { + // The area of bookmarks is first ignored, because it's not read. + + // first the SWG specific data: + if ( dynamic_cast< const ::sw::mark::IBookmark* >(pBookmark) && !pBookmark->GetName().isEmpty() ) + { + OutAnchor( pBookmark->GetName() ); + } + + if( ++m_nBkmkTabPos >= pMarkAccess->getAllMarksCount() ) + m_nBkmkTabPos = -1; + else + pBookmark = pMarkAccess->getAllMarksBegin()[m_nBkmkTabPos]; + } + + decltype(m_aOutlineMarkPoss)::size_type nPos; + for( nPos = 0; nPos < m_aOutlineMarkPoss.size() && + m_aOutlineMarkPoss[nPos] < nNode; nPos++ ) + ; + + while( nPos < m_aOutlineMarkPoss.size() && m_aOutlineMarkPoss[nPos] == nNode ) + { + OUString sMark( m_aOutlineMarks[nPos] ); + OutAnchor( sMark.replace('?', '_') ); // '?' causes problems in IE/Netscape 5 + m_aOutlineMarkPoss.erase( m_aOutlineMarkPoss.begin()+nPos ); + m_aOutlineMarks.erase( m_aOutlineMarks.begin() + nPos ); + } +} + +void SwHTMLWriter::OutPointFieldmarks( const SwPosition& rPos ) +{ + // "point" fieldmarks that occupy single character space, as opposed to + // range fieldmarks that are associated with start and end points. + + const IDocumentMarkAccess* pMarkAccess = m_pDoc->getIDocumentMarkAccess(); + if (!pMarkAccess) + return; + + const sw::mark::IFieldmark* pMark = pMarkAccess->getFieldmarkAt(rPos); + if (!pMark) + return; + + if (pMark->GetFieldname() != ODF_FORMCHECKBOX) + return; + + const sw::mark::ICheckboxFieldmark* pCheckBox = + dynamic_cast<const sw::mark::ICheckboxFieldmark*>(pMark); + + if (!pCheckBox) + return; + + OString aOut("<" + OOO_STRING_SVTOOLS_HTML_input + " " + OOO_STRING_SVTOOLS_HTML_O_type + "=\"" + OOO_STRING_SVTOOLS_HTML_IT_checkbox + "\""_ostr); + + if (pCheckBox->IsChecked()) + { + aOut += " " + OOO_STRING_SVTOOLS_HTML_O_checked + "=\"" + OOO_STRING_SVTOOLS_HTML_O_checked + "\""; + } + + aOut += "/>"; + Strm().WriteOString(aOut); + + // TODO : Handle other single-point fieldmark types here (if any). +} + +void SwHTMLWriter::OutImplicitMark( std::u16string_view rMark, + const char *pMarkType ) +{ + if( !rMark.empty() && !m_aImplicitMarks.empty() ) + { + OUString sMark(rMark + OUStringChar(cMarkSeparator) + OUString::createFromAscii(pMarkType)); + if( 0 != m_aImplicitMarks.erase( sMark ) ) + { + OutAnchor(sMark.replace('?', '_')); // '?' causes problems in IE/Netscape 5 + } + } +} + +OUString SwHTMLWriter::convertHyperlinkHRefValue(const OUString& rURL) +{ + OUString sURL(rURL); + sal_Int32 nPos = sURL.lastIndexOf(cMarkSeparator); + if (nPos != -1) + { + OUString sCompare = sURL.copy(nPos + 1).replaceAll(" ", ""); + if (!sCompare.isEmpty()) + { + sCompare = sCompare.toAsciiLowerCase(); + if( sCompare == "region" || sCompare == "frame" || + sCompare == "graphic" || sCompare == "ole" || + sCompare == "table" || sCompare == "outline" || + sCompare == "text" ) + { + sURL = sURL.replace( '?', '_' ); // '?' causes problems in IE/Netscape 5 + } + } + } + else if (!sURL.isEmpty() && sURL[0] != '#') + { + // Link is not started from "#", so looks like external link. Encode this URL. + INetURLObject aURL(sURL); + if (!aURL.HasError()) + sURL = aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE); + } + return URIHelper::simpleNormalizedMakeRelative( GetBaseURL(), sURL ); +} + +void SwHTMLWriter::OutHyperlinkHRefValue( const OUString& rURL ) +{ + OUString sURL = convertHyperlinkHRefValue(rURL); + HTMLOutFuncs::Out_String( Strm(), sURL ); +} + +void SwHTMLWriter::OutBackground( const SvxBrushItem *pBrushItem, bool bGraphic ) +{ + const Color &rBackColor = pBrushItem->GetColor(); + /// check, if background color is not "no fill"/"auto fill", instead of + /// only checking, if transparency is not set. + if( rBackColor != COL_TRANSPARENT ) + { + Strm().WriteOString( " " OOO_STRING_SVTOOLS_HTML_O_bgcolor "=" ); + HTMLOutFuncs::Out_Color( Strm(), rBackColor); + } + + if( !bGraphic ) + return; + + const Graphic* pGrf = pBrushItem->GetGraphic(); + OUString GraphicURL = pBrushItem->GetGraphicLink(); + if( mbEmbedImages || GraphicURL.isEmpty()) + { + if( pGrf ) + { + OUString aGraphicInBase64; + if( !XOutBitmap::GraphicToBase64(*pGrf, aGraphicInBase64) ) + { + m_nWarn = WARN_SWG_POOR_LOAD; + } + Strm().WriteOString( " " OOO_STRING_SVTOOLS_HTML_O_background "=\"" ); + Strm().WriteOString( OOO_STRING_SVTOOLS_HTML_O_data ":" ); + HTMLOutFuncs::Out_String( Strm(), aGraphicInBase64 ).WriteChar( '\"' ); + } + } + else + { + if( m_bCfgCpyLinkedGrfs ) + { + CopyLocalFileToINet( GraphicURL ); + } + OUString s( URIHelper::simpleNormalizedMakeRelative( GetBaseURL(), GraphicURL)); + Strm().WriteOString(" " OOO_STRING_SVTOOLS_HTML_O_background "=\"" ); + HTMLOutFuncs::Out_String( Strm(), s ); + Strm().WriteOString("\""); + + } +} + +void SwHTMLWriter::OutBackground( const SfxItemSet& rItemSet, bool bGraphic ) +{ + if( const SvxBrushItem* pItem = rItemSet.GetItemIfSet( RES_BACKGROUND, false ) ) + { + OutBackground( pItem, bGraphic ); + } +} + +TypedWhichId<SvxLanguageItem> SwHTMLWriter::GetLangWhichIdFromScript( sal_uInt16 nScript ) +{ + TypedWhichId<SvxLanguageItem> nWhichId(0); + switch( nScript ) + { + case CSS1_OUTMODE_CJK: + nWhichId = RES_CHRATR_CJK_LANGUAGE; + break; + case CSS1_OUTMODE_CTL: + nWhichId = RES_CHRATR_CJK_LANGUAGE; + break; + default: + nWhichId = RES_CHRATR_LANGUAGE; + break; + } + return nWhichId; +} + +void SwHTMLWriter::OutLanguage( LanguageType nLang ) +{ + // ReqIF mode: consumers would ignore language anyway. + if (!(LANGUAGE_DONTKNOW != nLang && !mbReqIF)) + return; + + OStringBuffer sOut(" "); + if (mbXHTML) + sOut.append(OOO_STRING_SVTOOLS_XHTML_O_lang); + else + sOut.append(OOO_STRING_SVTOOLS_HTML_O_lang); + sOut.append("=\""); + Strm().WriteOString( sOut ); + sOut.setLength(0); + HTMLOutFuncs::Out_String( Strm(), LanguageTag::convertToBcp47(nLang) ).WriteChar( '"' ); +} + +SvxFrameDirection SwHTMLWriter::GetHTMLDirection( const SfxItemSet& rItemSet ) const +{ + return GetHTMLDirection( rItemSet.Get( RES_FRAMEDIR ).GetValue() ); +} + +SvxFrameDirection SwHTMLWriter::GetHTMLDirection( SvxFrameDirection nDir ) const +{ + switch( nDir ) + { + case SvxFrameDirection::Vertical_LR_TB: + nDir = SvxFrameDirection::Horizontal_LR_TB; + break; + case SvxFrameDirection::Vertical_RL_TB: + nDir = SvxFrameDirection::Horizontal_RL_TB; + break; + case SvxFrameDirection::Environment: + nDir = m_nDirection; + break; + default: break; + } + + return nDir; +} + +void SwHTMLWriter::OutDirection( SvxFrameDirection nDir ) +{ + OString sConverted = convertDirection(nDir); + if (!sConverted.isEmpty()) + { + OString sOut = + " " OOO_STRING_SVTOOLS_HTML_O_dir + "=\"" + sConverted + "\""; + Strm().WriteOString( sOut ); + } +} + +OString SwHTMLWriter::convertDirection(SvxFrameDirection nDir) +{ + OString sConverted; + switch (nDir) + { + case SvxFrameDirection::Horizontal_LR_TB: + case SvxFrameDirection::Vertical_LR_TB: + sConverted = "ltr"_ostr; + break; + case SvxFrameDirection::Horizontal_RL_TB: + case SvxFrameDirection::Vertical_RL_TB: + sConverted = "rtl"_ostr; + break; + default: break; + } + return sConverted; +} + +OString SwHTMLWriter::GetIndentString(sal_uInt16 nIncLvl) +{ + OString sRet; + + // somewhat cumbersome, but we have only one indent string! + sal_uInt16 nLevel = m_nIndentLvl + nIncLvl; + + if( nLevel && nLevel <= MAX_INDENT_LEVEL) + { + sIndentTabs[nLevel] = 0; + sRet = sIndentTabs; + sIndentTabs[nLevel] = '\t'; + } + + return sRet; +} + +void SwHTMLWriter::OutNewLine( bool bCheck ) +{ + if( !bCheck || (Strm().Tell()-m_nLastLFPos) > m_nIndentLvl ) + { + Strm().WriteOString( SAL_NEWLINE_STRING ); + m_nLastLFPos = Strm().Tell(); + } + + if( m_nIndentLvl && m_nIndentLvl <= MAX_INDENT_LEVEL) + { + sIndentTabs[m_nIndentLvl] = 0; + Strm().WriteOString( sIndentTabs ); + sIndentTabs[m_nIndentLvl] = '\t'; + } +} + +sal_uInt16 SwHTMLWriter::GetHTMLFontSize( sal_uInt32 nHeight ) const +{ + sal_uInt16 nSize = 1; + for( sal_uInt16 i=6; i>0; i-- ) + { + if( nHeight > (m_aFontHeights[i] + m_aFontHeights[i-1])/2 ) + { + nSize = i+1; + break; + } + } + + return nSize; +} + +// Paragraphs with Table of Contents and other index styles will be typeset with +// dot leaders at the position of the last tabulator in PrintLayout (CSS2) mode +sal_Int32 SwHTMLWriter::indexOfDotLeaders( sal_uInt16 nPoolId, std::u16string_view rStr ) +{ + if (m_bCfgPrintLayout && ((nPoolId >= RES_POOLCOLL_TOX_CNTNT1 && nPoolId <= RES_POOLCOLL_TOX_CNTNT5) || + (nPoolId >= RES_POOLCOLL_TOX_IDX1 && nPoolId <= RES_POOLCOLL_TOX_IDX3) || + (nPoolId >= RES_POOLCOLL_TOX_USER1 && nPoolId <= RES_POOLCOLL_TOX_CNTNT10) || + nPoolId == RES_POOLCOLL_TOX_ILLUS1 || nPoolId == RES_POOLCOLL_TOX_TABLES1 || + nPoolId == RES_POOLCOLL_TOX_OBJECT1 || + (nPoolId >= RES_POOLCOLL_TOX_AUTHORITIES1 && nPoolId <= RES_POOLCOLL_TOX_USER10))) { + size_t i = rStr.rfind('\t'); + // there are only ASCII (Latin-1) characters after the tabulator + if (i != std::u16string_view::npos && OUStringToOString(rStr.substr(i + 1), RTL_TEXTENCODING_ASCII_US).indexOf('?') == -1) + return i; + } + return -1; +} + +OString SwHTMLWriter::GetNamespace() const +{ + if (maNamespace.isEmpty()) + return OString(); + + return maNamespace + ":"; +} + +// Structure caches the current data of the writer to output a +// other part of the document, like e.g. header/footer +HTMLSaveData::HTMLSaveData(SwHTMLWriter& rWriter, SwNodeOffset nStt, + SwNodeOffset nEnd, bool bSaveNum, + const SwFrameFormat *pFrameFormat) + : rWrt(rWriter) + , pOldPam(rWrt.m_pCurrentPam) + , pOldEnd(rWrt.GetEndPaM()) + , nOldDefListLvl(rWrt.m_nDefListLvl) + , nOldDirection(rWrt.m_nDirection) + , bOldOutHeader(rWrt.m_bOutHeader) + , bOldOutFooter(rWrt.m_bOutFooter) + , bOldOutFlyFrame(rWrt.m_bOutFlyFrame) +{ + bOldWriteAll = rWrt.m_bWriteAll; + + rWrt.m_pCurrentPam = Writer::NewUnoCursor(*rWrt.m_pDoc, nStt, nEnd); + + // recognize table in special areas + if( nStt != rWrt.m_pCurrentPam->GetMark()->GetNodeIndex() ) + { + const SwNode *pNd = rWrt.m_pDoc->GetNodes()[ nStt ]; + if( pNd->IsTableNode() || pNd->IsSectionNode() ) + rWrt.m_pCurrentPam->GetMark()->Assign(*pNd); + } + + rWrt.SetEndPaM( rWrt.m_pCurrentPam.get() ); + rWrt.m_pCurrentPam->Exchange( ); + rWrt.m_bWriteAll = true; + rWrt.m_nDefListLvl = 0; + rWrt.m_bOutHeader = rWrt.m_bOutFooter = false; + + // Maybe save the current numbering information, so that it can be started again. + // Only then also the numbering information of the next paragraph will be valid. + if( bSaveNum ) + { + pOldNumRuleInfo.reset( new SwHTMLNumRuleInfo( rWrt.GetNumInfo() ) ); + pOldNextNumRuleInfo = rWrt.ReleaseNextNumInfo(); + } + else + { + rWrt.ClearNextNumInfo(); + } + + // The numbering will be in any case interrupted. + rWrt.GetNumInfo().Clear(); + + if( pFrameFormat ) + rWrt.m_nDirection = rWrt.GetHTMLDirection( pFrameFormat->GetAttrSet() ); +} + +HTMLSaveData::~HTMLSaveData() +{ + rWrt.m_pCurrentPam.reset(); // delete PaM again + + rWrt.m_pCurrentPam = pOldPam; + rWrt.SetEndPaM( pOldEnd ); + rWrt.m_bWriteAll = bOldWriteAll; + rWrt.m_nBkmkTabPos = bOldWriteAll ? rWrt.FindPos_Bkmk( *pOldPam->GetPoint() ) : -1; + rWrt.m_nLastParaToken = HtmlTokenId::NONE; + rWrt.m_nDefListLvl = nOldDefListLvl; + rWrt.m_nDirection = nOldDirection; + rWrt.m_bOutHeader = bOldOutHeader; + rWrt.m_bOutFooter = bOldOutFooter; + rWrt.m_bOutFlyFrame = bOldOutFlyFrame; + + // Maybe continue the numbering from before section. The numbering + // of the next paragraph will be invalid in any case. + if( pOldNumRuleInfo ) + { + rWrt.GetNumInfo().Set( *pOldNumRuleInfo ); + pOldNumRuleInfo.reset(); + rWrt.SetNextNumInfo( std::move(pOldNextNumRuleInfo) ); + } + else + { + rWrt.GetNumInfo().Clear(); + rWrt.ClearNextNumInfo(); + } +} + +void GetHTMLWriter( std::u16string_view rFilterOptions, const OUString& rBaseURL, WriterRef& xRet ) +{ + xRet = new SwHTMLWriter( rBaseURL, rFilterOptions ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/wrthtml.hxx b/sw/source/filter/html/wrthtml.hxx new file mode 100644 index 0000000000..a62bff941a --- /dev/null +++ b/sw/source/filter/html/wrthtml.hxx @@ -0,0 +1,748 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_FILTER_HTML_WRTHTML_HXX +#define INCLUDED_SW_SOURCE_FILTER_HTML_WRTHTML_HXX + +#include <memory> +#include <vector> +#include <set> +#include <string_view> +#include <map> +#include <optional> + +#include <com/sun/star/container/XIndexContainer.hpp> +#include <com/sun/star/form/XForm.hpp> +#include <i18nlangtag/lang.h> +#include <comphelper/stl_types.hxx> +#include <o3tl/sorted_vector.hxx> +#include <o3tl/typed_flags_set.hxx> +#include <rtl/ref.hxx> +#include <svtools/htmlout.hxx> +#include <tools/fldunit.hxx> + +#include <shellio.hxx> +#include <wrt_fn.hxx> +#include "htmlfly.hxx" + +// some forward declarations +class Color; +class SwFrameFormat; +class SwFlyFrameFormat; +class SwDrawFrameFormat; +class SwFormatINetFormat; +class SwFormatVertOrient; +class SwFormatFootnote; +class SwStartNode; +class SwTableNode; +class SwPageDesc; +class SwNodeIndex; +class ImageMap; +class SwNumRule; +class SdrObject; +class SdrUnoObj; +class SvxBrushItem; +class SvxFontItem; +class SwHTMLNumRuleInfo; +class SwHTMLPosFlyFrames; +class SwTextFootnote; +enum class HtmlPosition; +enum class HtmlTokenId : sal_Int16; +namespace utl { class TempFileNamed; } + +extern SwAttrFnTab aHTMLAttrFnTab; + +#define HTML_PARSPACE (o3tl::toTwips(5, o3tl::Length::mm)) + +// flags for the output of any kind of frames +// BORDER only possible if OutHTML_Image +// ANYSIZE indicates, if also VAR_SIZE and MIN_SIZE values should be exported +// ABSSIZE indicates, if spacing and framing should be ignored +enum class HtmlFrmOpts { + NONE = 0, + Align = 1<<0, + SAlign = 1<<1, + + Width = 1<<2, + Height = 1<<3, + Size = Width | Height, + SWidth = 1<<4, + SHeight = 1<<5, + SSize = SWidth | SHeight, + AnySize = 1<<6, + AbsSize = 1<<7, + MarginSize = 1<<8, + + Space = 1<<9, + SSpace = 1<<10, + + Border = 1<<11, + SBorder = 1<<12, + SNoBorder = 1<<13, + + SBackground = 1<<14, + + Name = 1<<15, + Alt = 1<<16, + BrClear = 1<<17, + SPixSize = 1<<18, + Id = 1<<19, + Dir = 1<<20, + /// The graphic frame is a replacement image of an OLE object. + Replacement = 1<<21, + + GenImgAllMask = Alt | Size | AbsSize | Name, + GenImgMask = GenImgAllMask | Align | Space | BrClear +}; +namespace o3tl { + template<> struct typed_flags<HtmlFrmOpts> : is_typed_flags<HtmlFrmOpts, ((1<<22)-1)> {}; +} + +#define HTMLMODE_BLOCK_SPACER 0x00010000 +#define HTMLMODE_FLOAT_FRAME 0x00020000 +#define HTMLMODE_VERT_SPACER 0x00040000 +#define HTMLMODE_NBSP_IN_TABLES 0x00080000 +#define HTMLMODE_LSPACE_IN_NUMBER_BULLET 0x00100000 +//was HTMLMODE_NO_BR_AT_PAREND 0x00200000 +#define HTMLMODE_PRINT_EXT 0x00400000 +#define HTMLMODE_ABS_POS_FLY 0x00800000 +#define HTMLMODE_ABS_POS_DRAW 0x01000000 +#define HTMLMODE_FLY_MARGINS 0x02000000 +#define HTMLMODE_BORDER_NONE 0x04000000 +#define HTMLMODE_FONT_GENERIC 0x08000000 +#define HTMLMODE_FRSTLINE_IN_NUMBER_BULLET 0x10000000 +#define HTMLMODE_NO_CONTROL_CENTERING 0x20000000 + +#define HTML_DLCOLL_DD 0x4000 +#define HTML_DLCOLL_DT 0x8000 + +#define CSS1_FMT_ISTAG (USHRT_MAX) +#define CSS1_FMT_CMPREF (USHRT_MAX-1) +#define CSS1_FMT_SPECIAL (USHRT_MAX-1) + +// the following flags only specify which descriptors, tags, options, +// and so on should be outputted +// bit 0,1,2 +#define CSS1_OUTMODE_SPAN_NO_ON 0x0000U +#define CSS1_OUTMODE_SPAN_TAG_ON 0x0001U +#define CSS1_OUTMODE_STYLE_OPT_ON 0x0002U +#define CSS1_OUTMODE_RULE_ON 0x0003U +#define CSS1_OUTMODE_SPAN_TAG1_ON 0x0004U +#define CSS1_OUTMODE_ANY_ON 0x0007U + +// bit 3,4,5 +#define CSS1_OUTMODE_SPAN_NO_OFF 0x0000U +#define CSS1_OUTMODE_SPAN_TAG_OFF (sal_uInt16(0x0001U << 3)) +#define CSS1_OUTMODE_STYLE_OPT_OFF (sal_uInt16(0x0002U << 3)) +#define CSS1_OUTMODE_RULE_OFF (sal_uInt16(0x0003U << 3)) +#define CSS1_OUTMODE_ANY_OFF (sal_uInt16(0x0007U << 3)) + +#define CSS1_OUTMODE_ONOFF(a) (CSS1_OUTMODE_##a##_ON|CSS1_OUTMODE_##a##_OFF) +#define CSS1_OUTMODE_SPAN_TAG CSS1_OUTMODE_ONOFF(SPAN_TAG) +#define CSS1_OUTMODE_STYLE_OPT CSS1_OUTMODE_ONOFF(STYLE_OPT) +#define CSS1_OUTMODE_RULE CSS1_OUTMODE_ONOFF(RULE) + +// the following flags specify what should be outputted +// bit 6,7,8,9 +#define CSS1_OUTMODE_TEMPLATE 0x0000U +#define CSS1_OUTMODE_BODY (sal_uInt16(0x0001U << 6)) +#define CSS1_OUTMODE_PARA (sal_uInt16(0x0002U << 6)) +#define CSS1_OUTMODE_HINT (sal_uInt16(0x0003U << 6)) +#define CSS1_OUTMODE_FRAME (sal_uInt16(0x0004U << 6)) +#define CSS1_OUTMODE_TABLE (sal_uInt16(0x0005U << 6)) +#define CSS1_OUTMODE_TABLEBOX (sal_uInt16(0x0006U << 6)) +#define CSS1_OUTMODE_DROPCAP (sal_uInt16(0x0007U << 6)) +#define CSS1_OUTMODE_SECTION (sal_uInt16(0x0008U << 6)) +#define CSS1_OUTMODE_SOURCE (sal_uInt16(0x000fU << 6)) + +// bit 10 +#define CSS1_OUTMODE_ENCODE (sal_uInt16(0x0001U << 10)) + +// bit 11,12,13 +// don't care about script +#define CSS1_OUTMODE_ANY_SCRIPT 0x0000U +// no cjk or ctl items +#define CSS1_OUTMODE_WESTERN (sal_uInt16(0x0001U << 11)) +// no western or ctl items +#define CSS1_OUTMODE_CJK (sal_uInt16(0x0002U << 11)) +// no western or cjk items +#define CSS1_OUTMODE_CTL (sal_uInt16(0x0003U << 11)) +// no western, cjk or ctl items +#define CSS1_OUTMODE_NO_SCRIPT (sal_uInt16(0x0004U << 11)) +#define CSS1_OUTMODE_SCRIPT (sal_uInt16(0x0007U << 11)) + +// the HTML writer +struct HTMLControl +{ + // the form to which the control belongs + css::uno::Reference<css::container::XIndexContainer> xFormComps; + SwNodeOffset nNdIdx; // the node in which it's anchored + sal_Int32 nCount; // how many controls are on the node + + HTMLControl( css::uno::Reference<css::container::XIndexContainer> xForm, SwNodeOffset nIdx ); + ~HTMLControl(); + + // operators for the sort array + bool operator<( const HTMLControl& rCtrl ) const + { + return nNdIdx < rCtrl.nNdIdx; + } +}; + +class HTMLControls : public o3tl::sorted_vector<std::unique_ptr<HTMLControl>, o3tl::less_uniqueptr_to<HTMLControl> > { +}; + +struct SwHTMLFormatInfo +{ + const SwFormat *pFormat; // the format itself + + OString aToken; // the token to output + OUString aClass; // the class to output + + std::optional<SfxItemSet> moItemSet; // the attribute set to output + + sal_Int32 nLeftMargin; // some default values for + sal_Int32 nRightMargin; // paragraph styles + short nFirstLineIndent; + + sal_uInt16 nTopMargin; + sal_uInt16 nBottomMargin; + + bool bScriptDependent; + + // ctor for a dummy to search + explicit SwHTMLFormatInfo( const SwFormat *pF ) : + pFormat( pF ), + nLeftMargin( 0 ), + nRightMargin( 0 ), + nFirstLineIndent(0), + nTopMargin( 0 ), + nBottomMargin( 0 ), + bScriptDependent(false) + {} + + // ctor for creating of the format information + SwHTMLFormatInfo( const SwFormat *pFormat, SwDoc *pDoc, SwDoc *pTemplate, + bool bOutStyles, LanguageType eDfltLang=LANGUAGE_DONTKNOW, + sal_uInt16 nScript=CSS1_OUTMODE_ANY_SCRIPT ); + ~SwHTMLFormatInfo(); + + friend bool operator<( const SwHTMLFormatInfo& rInfo1, + const SwHTMLFormatInfo& rInfo2 ) + { + return reinterpret_cast<sal_IntPtr>(rInfo1.pFormat) < reinterpret_cast<sal_IntPtr>(rInfo2.pFormat); + } + +}; + +typedef std::set<std::unique_ptr<SwHTMLFormatInfo>, + comphelper::UniquePtrValueLess<SwHTMLFormatInfo>> SwHTMLFormatInfos; + +class IDocumentStylePoolAccess; + +namespace sw +{ +enum class Css1Background +{ + Attr = 1, + Page = 2, + Table = 3, + Fly = 4, + Section = 5, + TableRow = 6, + TableCell = 7, +}; +} + +class SW_DLLPUBLIC SwHTMLWriter : public Writer +{ + SwHTMLPosFlyFrames m_aHTMLPosFlyFrames; + std::unique_ptr<SwHTMLNumRuleInfo> m_pNumRuleInfo;// current numbering + std::unique_ptr<SwHTMLNumRuleInfo> m_pNextNumRuleInfo; + sal_uInt32 m_nHTMLMode; // description of export configuration + + FieldUnit m_eCSS1Unit; + + bool m_bPrettyPrint = true; // Allows to add new lines to make it more readable + bool m_bLFPossible = false; // a line break can be inserted + bool m_bSpacePreserve = false; // Using xml::space="preserve", or "white-space: pre-wrap" style + bool m_bPreserveSpacesOnWrite = false; // If export should use m_bSpacePreserve + + sal_uInt16 OutHeaderAttrs(); + const SwPageDesc *MakeHeader( sal_uInt16& rHeaderAtrs ); + void GetControls(); + + void AddLinkTarget( std::u16string_view aURL ); + void CollectLinkTargets(); + + void SetupFilterOptions(std::u16string_view rFilterOptions); + +protected: + ErrCode WriteStream() override; + void SetupFilterOptions(SfxMedium& rMedium) override; + void SetupFilterFromPropertyValues( + const css::uno::Sequence<css::beans::PropertyValue>& rPropertyValues); + +public: + std::vector<OUString> m_aImgMapNames; // written image maps + std::set<OUString> m_aImplicitMarks; // implicit jump marks + std::set<OUString> m_aNumRuleNames; // names of exported num rules + std::set<OUString> m_aScriptParaStyles; // script dependent para styles + std::set<OUString> m_aScriptTextStyles; // script dependent text styles + std::vector<OUString> m_aOutlineMarks; + std::vector<SwNodeOffset> m_aOutlineMarkPoss; + HTMLControls m_aHTMLControls; // the forms to be written + SwHTMLFormatInfos m_CharFormatInfos; + SwHTMLFormatInfos m_TextCollInfos; + std::vector<SwFormatINetFormat*> m_aINetFormats; // the "open" INet attributes + std::optional<std::vector<SwTextFootnote*>> m_xFootEndNotes; + + OUString m_aCSS1Selector; // style selector + OUString m_aBulletGrfs[MAXLEVEL]; // list graphics + + css::uno::Reference<css::container::XIndexContainer> mxFormComps; // current form + + rtl::Reference<SwDoc> m_xTemplate; // HTML template + std::optional<Color> m_xDfltColor; // default colour + SwNodeIndex *m_pStartNdIdx; // index of first paragraph + const SwPageDesc *m_pCurrPageDesc;// current page style + const SwFormatFootnote *m_pFormatFootnote; + + sal_uInt32 m_aFontHeights[7]; // font heights 1-7 + + ErrCode m_nWarn; // warning code + sal_uInt64 m_nLastLFPos; // last position of LF + + HtmlTokenId m_nLastParaToken; // to hold paragraphs together + sal_Int32 m_nBkmkTabPos; // current position in bookmark table + sal_uInt16 m_nImgMapCnt; + sal_uInt16 m_nFormCntrlCnt; + sal_uInt16 m_nEndNote; + sal_uInt16 m_nFootNote; + sal_Int32 m_nLeftMargin; // left indent (e.g. from lists) + sal_Int32 m_nDfltLeftMargin; // defaults which doesn't have to be + sal_Int32 m_nDfltRightMargin; // written (from template) + short m_nFirstLineIndent; // first line indent (from lists) + short m_nDfltFirstLineIndent; // not to write default + sal_uInt16 m_nDfltTopMargin; // defaults which doesn't have to be + sal_uInt16 m_nDfltBottomMargin; // written (from template) + sal_uInt16 m_nIndentLvl; // How far is it indented? + sal_Int32 m_nWishLineLen; // How long can a line be? + sal_uInt16 m_nDefListLvl; // which DL level exists now + sal_Int32 m_nDefListMargin; // How far is the indentation in DL + sal_uInt16 m_nHeaderFooterSpace; + sal_uInt16 m_nTextAttrsToIgnore; + sal_uInt16 m_nExportMode; + sal_uInt16 m_nCSS1OutMode; + sal_uInt16 m_nCSS1Script; // contains default script (that's the one + // that is not contained in class names) + SvxFrameDirection m_nDirection; // the current direction + + LanguageType m_eLang; + + // description of the export configuration + // 0 + bool m_bCfgOutStyles : 1; // export styles + bool m_bCfgPreferStyles : 1; // prefer styles instead of usual tags + bool m_bCfgFormFeed : 1; // export form feeds + bool m_bCfgStarBasic : 1; // export StarBasic + bool m_bCfgCpyLinkedGrfs : 1; + + // description of what will be exported + + bool m_bFirstLine : 1; // is the first line outputted? + bool m_bTagOn : 1; // tag on or off i.e. Attr-Start or Attr-End + + // The following two flags specify how attributes are exported: + // bTextAttr bOutOpts + // 0 0 style sheets + // 1 0 Hints: Every attribute will be written as its own tag + // and an end tag exists + // 0 1 (paragraph)attribute: The Attribute will be exported as option + // of an already written tag. There is no end tag. + bool m_bTextAttr : 1; + // 8 + bool m_bOutOpts : 1; + + bool m_bOutTable : 1; // Will the table content be written? + bool m_bOutHeader : 1; + bool m_bOutFooter : 1; + bool m_bOutFlyFrame : 1; + + // flags for style export + + bool m_bFirstCSS1Rule : 1; // was a property already written + bool m_bFirstCSS1Property : 1; // was a property already written + + // 16 + bool m_bCSS1IgnoreFirstPageDesc : 1; + + // what must/can/may not be written? + + bool m_bNoAlign : 1; // HTML tag doesn't allow ALIGN=... + bool m_bClearLeft : 1; // <BR CLEAR=LEFT> write at end of paragraph + bool m_bClearRight : 1; // <BR CLEAR=RIGHT> write at end of paragraph + + // others + + bool m_bPreserveForm : 1; // preserve the current form + + bool m_bCfgNetscape4 : 1; // Netscape4 hacks + + bool mbSkipImages : 1; + /// If HTML header and footer should be written as well, or just the content itself. + bool mbSkipHeaderFooter : 1; + bool mbEmbedImages : 1; + /// Temporary base URL for paste of images. + std::unique_ptr<utl::TempFileNamed> mpTempBaseURL; + /// If XHTML markup should be written instead of HTML. + bool mbXHTML = false; + /// XML namespace, in case of XHTML. + OString maNamespace; + /// If the ReqIF subset of XHTML should be written. + bool mbReqIF = false; + +#define sCSS2_P_CLASS_leaders "leaders" + bool m_bCfgPrintLayout : 1; // PrintLayout option for TOC dot leaders + bool m_bParaDotLeaders : 1; // for TOC dot leaders + // 26 + + /// Tracks which text portion attributes are currently open: a which id -> open count map. + std::map<sal_uInt16, int> maStartedAttributes; + + OUString m_aRTFOLEMimeType; + + /// ReqIF mode: export images as OLE objects. + bool m_bExportImagesAsOLE = false; + + /// ReqIF mode: export formulas as PDFs. + bool m_bExportFormulasAsPDF = false; + + /// DPI used when exporting a vector shape as a bitmap. + std::optional<sal_Int32> m_nShapeDPI; + + /// If set, replace leading tabs with this many non-breaking spaces. + std::optional<sal_Int32> m_nLeadingTabWidth; + + /// Construct an instance of SwHTMLWriter and optionally give it + /// the filter options directly, which can also be set via SetupFilterOptions(). + explicit SwHTMLWriter( const OUString& rBaseURL, std::u16string_view rFilterOptions = std::u16string_view() ); + virtual ~SwHTMLWriter() override; + + void Out_SwDoc( SwPaM* ); // write the marked range + + // output all bookmarks of current paragraph + void OutAnchor( const OUString& rName ); + void OutBookmarks(); + void OutPointFieldmarks( const SwPosition& rPos ); + void OutImplicitMark( std::u16string_view rMark, const char *pMarkType ); + + OUString convertHyperlinkHRefValue(const OUString& rURL); + + void OutHyperlinkHRefValue( const OUString& rURL ); + + // output the FlyFrame anchored at current position + bool OutFlyFrame( SwNodeOffset nNdIdx, sal_Int32 nContentIdx, + HtmlPosition nPos ); + void OutFrameFormat( AllHtmlFlags nType, const SwFrameFormat& rFormat, + const SdrObject *pSdrObj ); + + void OutForm( bool bTagOn=true, const SwStartNode *pStNd=nullptr ); + void OutHiddenForms(); + void OutHiddenForm( const css::uno::Reference<css::form::XForm>& rForm ); + + void OutForm( bool bOn, const css::uno::Reference<css::container::XIndexContainer>& rFormComps ); + void OutHiddenControls( const css::uno::Reference<css::container::XIndexContainer>& rFormComps, + const css::uno::Reference<css::beans::XPropertySet>& rPropSet ); + bool HasControls() const; + + void OutFootEndNoteInfo(); + void OutFootEndNotes(); + OUString GetFootEndNoteSym( const SwFormatFootnote& rFormatFootnote ); + void OutFootEndNoteSym( const SwFormatFootnote& rFormatFootnote, const OUString& rNum, + sal_uInt16 nScript ); + + void OutBasic(const SwHTMLWriter& rHTMLWrt); + + // Used to indent inner blocks using dl/dd tags + void OutAndSetDefList( sal_uInt16 nNewLvl ); + + void OutStyleSheet( const SwPageDesc& rPageDesc ); + + inline void OutCSS1_PropertyAscii( std::string_view pProp, + std::string_view rVal ); + inline void OutCSS1_Property( std::string_view pProp, const OUString& rVal ); + void OutCSS1_Property( std::string_view pProp, std::string_view pVal, + const OUString *pSVal, std::optional<sw::Css1Background> oBackground = std::nullopt ); + void OutCSS1_UnitProperty( std::string_view pProp, tools::Long nVal ); + void OutCSS1_PixelProperty( std::string_view pProp, tools::Long nTwips ); + void OutCSS1_SfxItemSet( const SfxItemSet& rItemSet, bool bDeep=true, std::string_view rAdd = {} ); + + // events of BODY tag from SFX configuration + void OutBasicBodyEvents(); + + // BACKGROUND/BGCOLOR option + void OutBackground( const SvxBrushItem *pBrushItem, bool bGraphic ); + void OutBackground( const SfxItemSet& rItemSet, bool bGraphic ); + + void OutLanguage( LanguageType eLang ); + SvxFrameDirection GetHTMLDirection( SvxFrameDirection nDir ) const; + SvxFrameDirection GetHTMLDirection( const SfxItemSet& rItemSet ) const; + void OutDirection( SvxFrameDirection nDir ); + static OString convertDirection(SvxFrameDirection nDirection); + + // ALT/ALIGN/WIDTH/HEIGHT/HSPACE/VSPACE option of current + // frame format output and maybe add a <BR CLEAR=...> at the + // beginning of rEndTags + OString OutFrameFormatOptions( const SwFrameFormat& rFrameFormat, const OUString& rAltText, + HtmlFrmOpts nFrameOpts ); + + void writeFrameFormatOptions(HtmlWriter& aHtml, const SwFrameFormat& rFrameFormat, const OUString& rAltText, HtmlFrmOpts nFrameOpts); + + /// Writes the formatting for tables. + void OutCSS1_TableFrameFormatOptions( const SwFrameFormat& rFrameFormat ); + + /// Writes the borders and background for table cells. + void OutCSS1_TableCellBordersAndBG(const SwFrameFormat& rFrameFormat, const SvxBrushItem *pBrushItem); + + void OutCSS1_SectionFormatOptions( const SwFrameFormat& rFrameFormat, const SwFormatCol *pCol ); + void OutCSS1_FrameFormatOptions( const SwFrameFormat& rFrameFormat, HtmlFrmOpts nFrameOpts, + const SdrObject *pSdrObj=nullptr, + const SfxItemSet *pItemSet=nullptr ); + void OutCSS1_FrameFormatBackground( const SwFrameFormat& rFrameFormat ); + + void ChangeParaToken( HtmlTokenId nNew ); + + void IncIndentLevel() + { + m_nIndentLvl++; + } + void DecIndentLevel() + { + if ( m_nIndentLvl ) m_nIndentLvl--; + } + OString GetIndentString(sal_uInt16 nIncLvl = 0); + + sal_Int32 GetLineLen() + { + return static_cast<sal_Int32>(Strm().Tell()-m_nLastLFPos); + } + void OutNewLine( bool bCheck=false ); + + // for HTMLSaveData + SwPaM* GetEndPaM() { return m_pOrigPam; } + void SetEndPaM( SwPaM* pPam ) { m_pOrigPam = pPam; } + + static sal_uInt32 ToPixel(sal_uInt32 nTwips); + static Size ToPixel(Size aTwips); + + SwHTMLFrameType GuessFrameType( const SwFrameFormat& rFrameFormat, + const SdrObject*& rpStrObj ); + static SwHTMLFrameType GuessOLENodeFrameType( const SwNode& rNd ); + + void CollectFlyFrames(); + + sal_uInt16 GetHTMLFontSize( sal_uInt32 nFontHeight ) const; + + // Fetch current numbering information. + SwHTMLNumRuleInfo& GetNumInfo() { return *m_pNumRuleInfo; } + + // Fetch current numbering information of next paragraph. They + // don't have to exist yet! + SwHTMLNumRuleInfo *GetNextNumInfo() { return m_pNextNumRuleInfo.get(); } + std::unique_ptr<SwHTMLNumRuleInfo> ReleaseNextNumInfo(); + + // Set the numbering information of next paragraph. + void SetNextNumInfo( std::unique_ptr<SwHTMLNumRuleInfo> pNxt ); + + // Fill the numbering information of next paragraph. + void FillNextNumInfo(); + + // Clear numbering information of next paragraph. + void ClearNextNumInfo(); + + static const SdrObject* GetHTMLControl( const SwDrawFrameFormat& rFormat ); + static const SdrObject* GetMarqueeTextObj( const SwDrawFrameFormat& rFormat ); + static sal_uInt16 GetCSS1Selector( const SwFormat *pFormat, OString& rToken, + OUString& rClass, sal_uInt16& rRefPoolId, + OUString *pPseudo=nullptr ); + + static const SwFormat *GetTemplateFormat( sal_uInt16 nPoolId, IDocumentStylePoolAccess* /*SwDoc*/ pTemplate ); + static const SwFormat *GetParentFormat( const SwFormat& rFormat, sal_uInt16 nDeep ); + + static void SubtractItemSet( SfxItemSet& rItemSet, + const SfxItemSet& rRefItemSet, + bool bSetDefaults, + bool bClearSame = true, + const SfxItemSet *pRefScriptItemSet=nullptr ); + static bool HasScriptDependentItems( const SfxItemSet& rItemSet, + bool bCheckDropCap ); + + static void GetEEAttrsFromDrwObj( SfxItemSet& rItemSet, + const SdrObject *pObj ); + + static sal_uInt16 GetDefListLvl( std::u16string_view rNm, sal_uInt16 nPoolId ); + + sal_uInt32 GetHTMLMode() const + { + return m_nHTMLMode; + } + bool IsHTMLMode( sal_uInt32 nMode ) const + { + return (m_nHTMLMode & nMode) != 0; + } + + inline bool IsCSS1Source( sal_uInt16 n ) const; + inline bool IsCSS1Script( sal_uInt16 n ) const; + + static const char *GetNumFormat( sal_uInt16 nFormat ); + static void PrepareFontList( const SvxFontItem& rFontItem, OUString& rNames, + sal_Unicode cQuote, bool bGeneric ); + static sal_uInt16 GetCSS1ScriptForScriptType( sal_uInt16 nScriptType ); + static TypedWhichId<SvxLanguageItem> GetLangWhichIdFromScript( sal_uInt16 nScript ); + + FieldUnit GetCSS1Unit() const { return m_eCSS1Unit; } + + sal_Int32 indexOfDotLeaders( sal_uInt16 nPoolId, std::u16string_view rText ); + + /// Determines the prefix string needed to respect the requested namespace alias. + OString GetNamespace() const; + + bool IsPrettyPrint() const { return !m_bSpacePreserve && m_bPrettyPrint; } + bool IsLFPossible() const { return !m_bSpacePreserve && m_bLFPossible; } + void SetLFPossible(bool val) { m_bLFPossible = val; } + bool IsSpacePreserve() const { return m_bSpacePreserve; } + void SetSpacePreserve(bool val) { m_bSpacePreserve = val; } + bool IsPreserveSpacesOnWritePrefSet() const { return m_bPreserveSpacesOnWrite; } +}; + +inline bool SwHTMLWriter::IsCSS1Source( sal_uInt16 n ) const +{ + return n == (m_nCSS1OutMode & CSS1_OUTMODE_SOURCE); +} + +inline bool SwHTMLWriter::IsCSS1Script( sal_uInt16 n ) const +{ + sal_uInt16 nScript = (m_nCSS1OutMode & CSS1_OUTMODE_SCRIPT); + return CSS1_OUTMODE_ANY_SCRIPT == nScript || n == nScript; +} + +inline void SwHTMLWriter::OutCSS1_PropertyAscii( std::string_view pProp, + std::string_view rVal ) +{ + OutCSS1_Property( pProp, rVal, nullptr ); +} + +inline void SwHTMLWriter::OutCSS1_Property( std::string_view pProp, + const OUString& rVal ) +{ + OutCSS1_Property( pProp, std::string_view(), &rVal ); +} + + +// Structure caches the current data of the writer to output +// another part of the document, like e.g. header/footer +// With the two USHORTs in the ctor a new PaM is created and sets the +// positions in the document. +// In dtor all data is restored and the created PaM is deleted again. + +class HTMLSaveData +{ + SwHTMLWriter& rWrt; + std::shared_ptr<SwUnoCursor> pOldPam; + SwPaM *pOldEnd; + std::unique_ptr<SwHTMLNumRuleInfo> pOldNumRuleInfo; // Owner = this + std::unique_ptr<SwHTMLNumRuleInfo> pOldNextNumRuleInfo; + sal_uInt16 nOldDefListLvl; + SvxFrameDirection nOldDirection; + bool bOldWriteAll : 1; + bool bOldOutHeader : 1; + bool bOldOutFooter : 1; + bool bOldOutFlyFrame : 1; + +public: + HTMLSaveData( SwHTMLWriter&, SwNodeOffset nStt, SwNodeOffset nEnd, + bool bSaveNum=true, + const SwFrameFormat *pFrameFormat=nullptr ); + ~HTMLSaveData(); +}; + +// some function prototypes +SwHTMLWriter& OutHTML_FrameFormatOLENode( SwHTMLWriter& rWrt, const SwFrameFormat& rFormat, + bool bInCntnr ); +SwHTMLWriter& OutHTML_FrameFormatOLENodeGrf( SwHTMLWriter& rWrt, const SwFrameFormat& rFormat, + bool bInCntnr, bool bWriteReplacementGraphic = true ); + +SwHTMLWriter& OutHTML_SwTextNode( SwHTMLWriter&, const SwContentNode& ); +SwHTMLWriter& OutHTML_SwTableNode( SwHTMLWriter& , SwTableNode &, const SwFrameFormat *, + const OUString* pCaption=nullptr, bool bTopCaption=false ); + +SwHTMLWriter& OutHTML_DrawFrameFormatAsControl( SwHTMLWriter& rWrt, const SwDrawFrameFormat& rFormat, + const SdrUnoObj& rSdrObj, bool bInCntnr ); +SwHTMLWriter& OutHTML_DrawFrameFormatAsMarquee( SwHTMLWriter& rWrt, const SwDrawFrameFormat& rFormat, + const SdrObject& rSdrObj ); + +SwHTMLWriter& OutHTML_HeaderFooter( SwHTMLWriter& rWrt, const SwFrameFormat& rFrameFormat, + bool bHeader ); + +SwHTMLWriter& OutHTML_ImageStart( HtmlWriter& rHtml, SwHTMLWriter&, const SwFrameFormat& rFormat, + const OUString& rGraphicURL, + Graphic const & rGraphic, const OUString& rAlternateText, + const Size& rRealSize, HtmlFrmOpts nFrameOpts, + const char *pMarkType, + const ImageMap *pGenImgMap, + const OUString& rMimeType = {} ); +SwHTMLWriter& OutHTML_ImageEnd( HtmlWriter& rHtml, SwHTMLWriter& ); + +SwHTMLWriter& OutHTML_BulletImage( SwHTMLWriter& rWrt, const char *pTag, + const SvxBrushItem* pBrush, + const OUString& rGraphicURL); + +SwHTMLWriter& OutHTML_SwFormatField( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ); +SwHTMLWriter& OutHTML_SwFormatFootnote( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ); +SwHTMLWriter& OutHTML_SwFormatLineBreak(SwHTMLWriter& rWrt, const SfxPoolItem& rHt); +SwHTMLWriter& OutHTML_INetFormat( SwHTMLWriter&, const SwFormatINetFormat& rINetFormat, bool bOn ); + +SwHTMLWriter& OutCSS1_BodyTagStyleOpt( SwHTMLWriter& rWrt, const SfxItemSet& rItemSet ); +SwHTMLWriter& OutCSS1_ParaTagStyleOpt( SwHTMLWriter& rWrt, const SfxItemSet& rItemSet, std::string_view rAdd = {} ); + +SwHTMLWriter& OutCSS1_HintSpanTag( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ); +SwHTMLWriter& OutCSS1_HintStyleOpt( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ); + +/// Writes the background of table rows. +SwHTMLWriter& OutCSS1_TableBGStyleOpt( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ); + +SwHTMLWriter& OutCSS1_NumberBulletListStyleOpt( SwHTMLWriter& rWrt, const SwNumRule& rNumRule, + sal_uInt8 nLevel ); + +SwHTMLWriter& OutHTML_NumberBulletListStart( SwHTMLWriter& rWrt, + const SwHTMLNumRuleInfo& rInfo ); +SwHTMLWriter& OutHTML_NumberBulletListEnd( SwHTMLWriter& rWrt, + const SwHTMLNumRuleInfo& rNextInfo ); + +SwHTMLWriter& OutCSS1_SvxBox( SwHTMLWriter& rWrt, const SfxPoolItem& rHt ); + +OString GetCSS1_Color(const Color& rColor); + +/// Determines if rProperty with a given rValue has to be suppressed due to ReqIF mode. +bool IgnorePropertyForReqIF(bool bReqIF, std::string_view rProperty, std::string_view rValue, + std::optional<sw::Css1Background> oBackground = std::nullopt); + +#endif // INCLUDED_SW_SOURCE_FILTER_HTML_WRTHTML_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |