diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sw/source/filter/html/wrthtml.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sw/source/filter/html/wrthtml.cxx')
-rw-r--r-- | sw/source/filter/html/wrthtml.cxx | 1694 |
1 files changed, 1694 insertions, 0 deletions
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: */ |