/* -*- 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 "htmlex.hxx"
#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
#include <com/sun/star/frame/XModel.hpp>
#include <sal/log.hxx>
#include <rtl/tencinfo.h>
#include <comphelper/processfactory.hxx>
#include <comphelper/propertyvalue.hxx>
#include <comphelper/xmlencode.hxx>
#include <o3tl/safeint.hxx>
#include <osl/diagnose.h>
#include <osl/file.hxx>
#include <unotools/ucbstreamhelper.hxx>
#include <sfx2/frmhtmlw.hxx>
#include <sfx2/progress.hxx>
#include <utility>
#include <svx/svditer.hxx>
#include <editeng/eeitem.hxx>
#include <editeng/outlobj.hxx>
#include <svtools/htmlout.hxx>
#include <editeng/editeng.hxx>
#include <editeng/wghtitem.hxx>
#include <editeng/udlnitem.hxx>
#include <editeng/postitem.hxx>
#include <editeng/crossedoutitem.hxx>
#include <editeng/flditem.hxx>
#include <editeng/frmdiritem.hxx>
#include <svx/svdoutl.hxx>
#include <svx/svdogrp.hxx>
#include <svx/svdotable.hxx>
#include <tools/urlobj.hxx>
#include <svtools/sfxecode.hxx>
#include <tools/debug.hxx>
#include <drawdoc.hxx>
#include <DrawDocShell.hxx>
#include <Outliner.hxx>
#include <sdpage.hxx>
#include <strings.hrc>
#include <strings.hxx>
#include <sdresid.hxx>
using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::document;
using namespace sdr::table;
namespace {
// Helper class for the simple creation of files local/remote
class EasyFile
{
private:
std::unique_ptr<SvStream> pOStm;
bool bOpen;
public:
EasyFile();
~EasyFile();
ErrCode createStream( const OUString& rUrl, SvStream*& rpStr );
void createFileName( const OUString& rUrl, OUString& rFileName );
void close();
};
// create area for a circle; we expect pixel coordinates
OUString ColorToHTMLString( Color aColor )
{
static const char hex[] = "0123456789ABCDEF";
OUStringBuffer aStr( "#xxxxxx" );
aStr[1] = hex[(aColor.GetRed() >> 4) & 0xf];
aStr[2] = hex[aColor.GetRed() & 0xf];
aStr[3] = hex[(aColor.GetGreen() >> 4) & 0xf];
aStr[4] = hex[aColor.GetGreen() & 0xf];
aStr[5] = hex[(aColor.GetBlue() >> 4) & 0xf];
aStr[6] = hex[aColor.GetBlue() & 0xf];
return aStr.makeStringAndClear();
}
} //namespace
// Helper class for the embedding of text attributes into the html output
class HtmlState
{
private:
bool mbColor;
bool mbWeight;
bool mbItalic;
bool mbUnderline;
bool mbStrike;
bool mbLink;
Color maColor;
Color maDefColor;
OUString maLink;
OUString maTarget;
public:
explicit HtmlState( Color aDefColor );
OUString SetWeight( bool bWeight );
OUString SetItalic( bool bItalic );
OUString SetUnderline( bool bUnderline );
OUString SetColor( Color aColor );
OUString SetStrikeout( bool bStrike );
OUString SetLink( const OUString& aLink, const OUString& aTarget );
OUString Flush();
};
// close all still open tags
OUString HtmlState::Flush()
{
OUString aStr = SetWeight(false)
+ SetItalic(false)
+ SetUnderline(false)
+ SetStrikeout(false)
+ SetColor(maDefColor)
+ SetLink(u""_ustr,u""_ustr);
return aStr;
}
// c'tor with default color for the page
HtmlState::HtmlState( Color aDefColor )
: mbColor(false),
mbWeight(false),
mbItalic(false),
mbUnderline(false),
mbStrike(false),
mbLink(false),
maDefColor(aDefColor)
{
}
// enables/disables bold print
OUString HtmlState::SetWeight( bool bWeight )
{
OUString aStr;
if(bWeight && !mbWeight)
aStr = "<b>";
else if(!bWeight && mbWeight)
aStr = "</b>";
mbWeight = bWeight;
return aStr;
}
// enables/disables italic
OUString HtmlState::SetItalic( bool bItalic )
{
OUString aStr;
if(bItalic && !mbItalic)
aStr = "<i>";
else if(!bItalic && mbItalic)
aStr = "</i>";
mbItalic = bItalic;
return aStr;
}
// enables/disables underlines
OUString HtmlState::SetUnderline( bool bUnderline )
{
OUString aStr;
if(bUnderline && !mbUnderline)
aStr = "<u>";
else if(!bUnderline && mbUnderline)
aStr = "</u>";
mbUnderline = bUnderline;
return aStr;
}
// enables/disables strike through
OUString HtmlState::SetStrikeout( bool bStrike )
{
OUString aStr;
if(bStrike && !mbStrike)
aStr = "<strike>";
else if(!bStrike && mbStrike)
aStr = "</strike>";
mbStrike = bStrike;
return aStr;
}
// Sets the specified text color
OUString HtmlState::SetColor( Color aColor )
{
OUString aStr;
if(mbColor && aColor == maColor)
return aStr;
if(mbColor)
{
aStr = "</font>";
mbColor = false;
}
if(aColor != maDefColor)
{
maColor = aColor;
aStr += "<font color=\"" + ColorToHTMLString(aColor) + "\">";
mbColor = true;
}
return aStr;
}
// enables/disables a hyperlink
OUString HtmlState::SetLink( const OUString& aLink, const OUString& aTarget )
{
OUString aStr;
if(mbLink&&maLink == aLink&&maTarget==aTarget)
return aStr;
if(mbLink)
{
aStr = "</a>";
mbLink = false;
}
if (!aLink.isEmpty())
{
aStr += "<a href=\"" + comphelper::string::encodeForXml(aLink);
if (!aTarget.isEmpty())
{
aStr += "\" target=\"" + comphelper::string::encodeForXml(aTarget);
}
aStr += "\">";
mbLink = true;
maLink = aLink;
maTarget = aTarget;
}
return aStr;
}
namespace
{
OUString getParagraphStyle( const SdrOutliner* pOutliner, sal_Int32 nPara )
{
const SfxItemSet& aParaSet( pOutliner->GetParaAttribs( nPara ) );
OUString sStyle;
if( aParaSet.GetItem<SvxFrameDirectionItem>( EE_PARA_WRITINGDIR )->GetValue() == SvxFrameDirection::Horizontal_RL_TB )
{
sStyle = "direction: rtl;";
}
else
{
// This is the default so don't write it out
// sStyle += "direction: ltr;";
}
return sStyle;
}
void lclAppendStyle(OUStringBuffer& aBuffer, std::u16string_view aTag, std::u16string_view aStyle)
{
if (aStyle.empty())
aBuffer.append(OUString::Concat("<") + aTag + ">");
else
aBuffer.append(OUString::Concat("<") + aTag + " style=\"" + aStyle + "\">");
}
// Depending on the attributes of the specified set and the specified
// HtmlState, it creates the needed html tags in order to get the
// attributes
OUString TextAttribToHTMLString( SfxItemSet const * pSet, HtmlState* pState )
{
OUStringBuffer aStr;
if(nullptr == pSet)
return OUString();
OUString aLink, aTarget;
const SvxFieldItem* pItem = nullptr;
if ( pSet->GetItemState( EE_FEATURE_FIELD, true, &pItem ) == SfxItemState::SET )
{
const SvxURLField* pURL = dynamic_cast<const SvxURLField*>( pItem->GetField() );
if(pURL)
{
aLink = pURL->GetURL();
aTarget = pURL->GetTargetFrame();
}
}
bool bTemp;
OUString aTemp;
if ( pSet->GetItemState( EE_CHAR_WEIGHT ) == SfxItemState::SET )
{
bTemp = pSet->Get( EE_CHAR_WEIGHT ).GetWeight() == WEIGHT_BOLD;
aTemp = pState->SetWeight( bTemp );
if( bTemp )
aStr.insert(0, aTemp);
else
aStr.append(aTemp);
}
if ( pSet->GetItemState( EE_CHAR_UNDERLINE ) == SfxItemState::SET )
{
bTemp = pSet->Get( EE_CHAR_UNDERLINE ).GetLineStyle() != LINESTYLE_NONE;
aTemp = pState->SetUnderline( bTemp );
if( bTemp )
aStr.insert(0, aTemp);
else
aStr.append(aTemp);
}
if ( pSet->GetItemState( EE_CHAR_STRIKEOUT ) == SfxItemState::SET )
{
bTemp = pSet->Get( EE_CHAR_STRIKEOUT ).GetStrikeout() != STRIKEOUT_NONE;
aTemp = pState->SetStrikeout( bTemp );
if( bTemp )
aStr.insert(0, aTemp);
else
aStr.append(aTemp);
}
if ( pSet->GetItemState( EE_CHAR_ITALIC ) == SfxItemState::SET )
{
bTemp = pSet->Get( EE_CHAR_ITALIC ).GetPosture() != ITALIC_NONE;
aTemp = pState->SetItalic( bTemp );
if( bTemp )
aStr.insert(0, aTemp);
else
aStr.append(aTemp);
}
if (!aLink.isEmpty())
aStr.insert(0, pState->SetLink(aLink, aTarget));
else
aStr.append(pState->SetLink(aLink, aTarget));
return aStr.makeStringAndClear();
}
// escapes a string for html
OUString StringToHTMLString( std::u16string_view rString )
{
SvMemoryStream aMemStm;
HTMLOutFuncs::Out_String( aMemStm, rString );
return OUString( static_cast<char const *>(aMemStm.GetData()), aMemStm.GetSize(), RTL_TEXTENCODING_UTF8 );
}
// converts a paragraph of the outliner to html
OUString ParagraphToHTMLString( SdrOutliner const * pOutliner, sal_Int32 nPara )
{
OUStringBuffer aStr;
if(nullptr == pOutliner)
return OUString();
// TODO: MALTE!!!
EditEngine& rEditEngine = *const_cast<EditEngine*>(&pOutliner->GetEditEngine());
bool bOldUpdateMode = rEditEngine.SetUpdateLayout(true);
Paragraph* pPara = pOutliner->GetParagraph(nPara);
if(nullptr == pPara)
return OUString();
HtmlState aState( COL_BLACK );
std::vector<sal_Int32> aPortionList;
rEditEngine.GetPortions( nPara, aPortionList );
sal_Int32 nPos1 = 0;
for( sal_Int32 nPos2 : aPortionList )
{
ESelection aSelection( nPara, nPos1, nPara, nPos2);
SfxItemSet aSet( rEditEngine.GetAttribs( aSelection ) );
aStr.append(TextAttribToHTMLString( &aSet, &aState ) +
StringToHTMLString(rEditEngine.GetText( aSelection )));
nPos1 = nPos2;
}
aStr.append(aState.Flush());
rEditEngine.SetUpdateLayout(bOldUpdateMode);
return aStr.makeStringAndClear();
}
void WriteOutlinerParagraph(OUStringBuffer& aStr, SdrOutliner* pOutliner,
OutlinerParaObject const * pOutlinerParagraphObject,
bool bHeadLine)
{
if (pOutlinerParagraphObject == nullptr)
return;
pOutliner->SetText(*pOutlinerParagraphObject);
sal_Int32 nCount = pOutliner->GetParagraphCount();
sal_Int16 nCurrentDepth = -1;
for (sal_Int32 nIndex = 0; nIndex < nCount; nIndex++)
{
Paragraph* pParagraph = pOutliner->GetParagraph(nIndex);
if(pParagraph == nullptr)
continue;
const sal_Int16 nDepth = static_cast<sal_uInt16>(pOutliner->GetDepth(nIndex));
OUString aParaText = ParagraphToHTMLString(pOutliner, nIndex);
if (aParaText.isEmpty())
continue;
if (nDepth < 0)
{
OUString aTag = bHeadLine ? u"h2"_ustr : u"p"_ustr;
lclAppendStyle(aStr, aTag, getParagraphStyle(pOutliner, nIndex));
aStr.append(aParaText);
aStr.append("</" + aTag + ">\r\n");
}
else
{
while(nCurrentDepth < nDepth)
{
aStr.append("<ul>\r\n");
nCurrentDepth++;
}
while(nCurrentDepth > nDepth)
{
aStr.append("</ul>\r\n");
nCurrentDepth--;
}
lclAppendStyle(aStr, u"li", getParagraphStyle(pOutliner, nIndex));
aStr.append(aParaText);
aStr.append("</li>\r\n");
}
}
while(nCurrentDepth >= 0)
{
aStr.append("</ul>\r\n");
nCurrentDepth--;
}
pOutliner->Clear();
}
void WriteTable(OUStringBuffer& aStr, SdrTableObj const * pTableObject, SdrOutliner* pOutliner)
{
CellPos aStart, aEnd;
aStart = SdrTableObj::getFirstCell();
aEnd = pTableObject->getLastCell();
sal_Int32 nColCount = pTableObject->getColumnCount();
aStr.append("<table>\r\n");
for (sal_Int32 nRow = aStart.mnRow; nRow <= aEnd.mnRow; nRow++)
{
aStr.append(" <tr>\r\n");
for (sal_Int32 nCol = aStart.mnCol; nCol <= aEnd.mnCol; nCol++)
{
aStr.append(" <td>\r\n");
sal_Int32 nCellIndex = nRow * nColCount + nCol;
SdrText* pText = pTableObject->getText(nCellIndex);
if (pText == nullptr)
continue;
WriteOutlinerParagraph(aStr, pOutliner, pText->GetOutlinerParaObject(), false);
aStr.append(" </td>\r\n");
}
aStr.append(" </tr>\r\n");
}
aStr.append("</table>\r\n");
}
void WriteObjectGroup(OUStringBuffer& aStr, SdrObjGroup const * pObjectGroup, SdrOutliner* pOutliner,
bool bHeadLine)
{
SdrObjListIter aGroupIterator(pObjectGroup->GetSubList(), SdrIterMode::DeepNoGroups);
while (aGroupIterator.IsMore())
{
SdrObject* pCurrentObject = aGroupIterator.Next();
if (pCurrentObject->GetObjIdentifier() == SdrObjKind::Group)
{
SdrObjGroup* pCurrentGroupObject = static_cast<SdrObjGroup*>(pCurrentObject);
WriteObjectGroup(aStr, pCurrentGroupObject, pOutliner, bHeadLine);
}
else
{
OutlinerParaObject* pOutlinerParagraphObject = pCurrentObject->GetOutlinerParaObject();
if (pOutlinerParagraphObject != nullptr)
{
WriteOutlinerParagraph(aStr, pOutliner, pOutlinerParagraphObject, bHeadLine);
}
}
}
}
// get SdrTextObject with layout text of this page
SdrTextObj* GetLayoutTextObject(SdrPage const * pPage)
{
SdrTextObj* pResult = nullptr;
for (const rtl::Reference<SdrObject>& pObject : *pPage)
{
if (pObject->GetObjInventor() == SdrInventor::Default &&
pObject->GetObjIdentifier() == SdrObjKind::OutlineText)
{
pResult = static_cast<SdrTextObj*>(pObject.get());
break;
}
}
return pResult;
}
/** creates an outliner text for the title objects of a page
*/
OUString CreateTextForTitle( SdrOutliner* pOutliner, SdPage* pPage )
{
SdrTextObj* pTO = static_cast<SdrTextObj*>(pPage->GetPresObj(PresObjKind::Title));
if(!pTO)
pTO = GetLayoutTextObject(pPage);
if (pTO && !pTO->IsEmptyPresObj())
{
OutlinerParaObject* pOPO = pTO->GetOutlinerParaObject();
if(pOPO && pOutliner->GetParagraphCount() != 0)
{
pOutliner->Clear();
pOutliner->SetText(*pOPO);
return ParagraphToHTMLString(pOutliner, 0);
}
}
return OUString();
}
// creates an outliner text for a page
OUString CreateTextForPage(SdrOutliner* pOutliner, SdPage const * pPage,
bool bHeadLine)
{
OUStringBuffer aStr;
for (const rtl::Reference<SdrObject>& pObject : *pPage)
{
PresObjKind eKind = pPage->GetPresObjKind(pObject.get());
switch (eKind)
{
case PresObjKind::NONE:
{
if (pObject->GetObjIdentifier() == SdrObjKind::Group)
{
SdrObjGroup* pObjectGroup = static_cast<SdrObjGroup*>(pObject.get());
WriteObjectGroup(aStr, pObjectGroup, pOutliner, false);
}
else if (pObject->GetObjIdentifier() == SdrObjKind::Table)
{
SdrTableObj* pTableObject = static_cast<SdrTableObj*>(pObject.get());
WriteTable(aStr, pTableObject, pOutliner);
}
else
{
if (pObject->GetOutlinerParaObject())
{
WriteOutlinerParagraph(aStr, pOutliner, pObject->GetOutlinerParaObject(), false);
}
}
}
break;
case PresObjKind::Table:
{
SdrTableObj* pTableObject = static_cast<SdrTableObj*>(pObject.get());
WriteTable(aStr, pTableObject, pOutliner);
}
break;
case PresObjKind::Text:
case PresObjKind::Outline:
{
SdrTextObj* pTextObject = static_cast<SdrTextObj*>(pObject.get());
if (pTextObject->IsEmptyPresObj())
continue;
WriteOutlinerParagraph(aStr, pOutliner, pTextObject->GetOutlinerParaObject(), bHeadLine);
}
break;
default:
break;
}
}
return aStr.makeStringAndClear();
}
} // namespace
constexpr OUStringLiteral gaHTMLHeader(
u"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n"
" \"http://www.w3.org/TR/html4/transitional.dtd\">\r\n"
"<html>\r\n<head>\r\n" );
// constructor for the html export helper classes
HtmlExport::HtmlExport(
OUString aPath,
SdDrawDocument* pExpDoc,
sd::DrawDocShell* pDocShell )
: maPath(std::move( aPath )),
mpDoc(pExpDoc),
mpDocSh( pDocShell )
{
bool bChange = mpDoc->IsChanged();
Init();
ExportSingleDocument();
mpDoc->SetChanged(bChange);
}
HtmlExport::~HtmlExport()
{
}
void HtmlExport::Init()
{
SdPage* pPage = mpDoc->GetSdPage(0, PageKind::Standard);
// we come up with a destination...
INetURLObject aINetURLObj( maPath );
DBG_ASSERT( aINetURLObj.GetProtocol() != INetProtocol::NotValid, "invalid URL" );
maExportPath = aINetURLObj.GetPartBeforeLastName(); // with trailing '/'
maIndex = aINetURLObj.GetLastName();
mnSdPageCount = mpDoc->GetSdPageCount( PageKind::Standard );
for( sal_uInt16 nPage = 0; nPage < mnSdPageCount; nPage++ )
{
pPage = mpDoc->GetSdPage( nPage, PageKind::Standard );
maPages.push_back( pPage );
}
mnSdPageCount = maPages.size();
maDocFileName = maIndex;
}
void HtmlExport::ExportSingleDocument()
{
SdrOutliner* pOutliner = mpDoc->GetInternalOutliner();
mnPagesWritten = 0;
InitProgress(mnSdPageCount);
OUStringBuffer aStr(gaHTMLHeader
+ DocumentMetadata()
+ "\r\n"
"</head>\r\n"
"<body>\r\n");
for(sal_uInt16 nSdPage = 0; nSdPage < mnSdPageCount; ++nSdPage)
{
SdPage* pPage = maPages[nSdPage];
// page title
OUString sTitleText(CreateTextForTitle(pOutliner, pPage));
OUString sStyle;
if (nSdPage != 0) // First page - no need for a page break here
sStyle += "page-break-before:always; ";
sStyle += getParagraphStyle(pOutliner, 0);
lclAppendStyle(aStr, u"h1", sStyle);
aStr.append(sTitleText + "</h1>\r\n");
// write outline text
aStr.append(CreateTextForPage( pOutliner, pPage, true));
if (mpProgress)
mpProgress->SetState(++mnPagesWritten);
}
// close page
aStr.append("</body>\r\n</html>");
WriteHtml(maDocFileName, aStr);
pOutliner->Clear();
ResetProgress();
}
void HtmlExport::InitProgress( sal_uInt16 nProgrCount )
{
mpProgress.reset(new SfxProgress( mpDocSh, SdResId(STR_CREATE_PAGES), nProgrCount ));
}
void HtmlExport::ResetProgress()
{
mpProgress.reset();
}
OUString HtmlExport::DocumentMetadata() const
{
SvMemoryStream aStream;
uno::Reference<document::XDocumentProperties> xDocProps;
if (mpDocSh)
{
uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
mpDocSh->GetModel(), uno::UNO_QUERY_THROW);
xDocProps.set(xDPS->getDocumentProperties());
}
SfxFrameHTMLWriter::Out_DocInfo(aStream, maDocFileName, xDocProps,
" ");
const sal_uInt64 nLen = aStream.GetSize();
OSL_ENSURE(nLen < o3tl::make_unsigned(SAL_MAX_INT32), "Stream can't fit in OString");
std::string_view aData(static_cast<const char*>(aStream.GetData()), static_cast<sal_Int32>(nLen));
return OStringToOUString(aData, RTL_TEXTENCODING_UTF8);
}
/** exports the given html data into a non unicode file in the current export path with
the given filename */
bool HtmlExport::WriteHtml( std::u16string_view rFileName, std::u16string_view rHtmlData )
{
ErrCode nErr = ERRCODE_NONE;
EasyFile aFile;
SvStream* pStr;
OUString aFull(maExportPath + rFileName);
nErr = aFile.createStream(aFull , pStr);
if(nErr == ERRCODE_NONE)
{
OString aStr(OUStringToOString(rHtmlData, RTL_TEXTENCODING_UTF8));
pStr->WriteOString( aStr );
aFile.close();
}
if( nErr != ERRCODE_NONE )
ErrorHandler::HandleError(nErr);
return nErr == ERRCODE_NONE;
}
EasyFile::EasyFile() : bOpen(false)
{
}
EasyFile::~EasyFile()
{
if( bOpen )
close();
}
ErrCode EasyFile::createStream( const OUString& rUrl, SvStream* &rpStr )
{
if(bOpen)
close();
OUString aFileName;
createFileName( rUrl, aFileName );
ErrCode nErr = ERRCODE_NONE;
pOStm = ::utl::UcbStreamHelper::CreateStream( aFileName, StreamMode::WRITE | StreamMode::TRUNC );
if( pOStm )
{
bOpen = true;
nErr = pOStm->GetError();
}
else
{
nErr = ERRCODE_SFX_CANTCREATECONTENT;
}
if( nErr != ERRCODE_NONE )
{
bOpen = false;
pOStm.reset();
}
rpStr = pOStm.get();
return nErr;
}
void EasyFile::createFileName( const OUString& rURL, OUString& rFileName )
{
if( bOpen )
close();
INetURLObject aURL( rURL );
if( aURL.GetProtocol() == INetProtocol::NotValid )
{
OUString aURLStr;
osl::FileBase::getFileURLFromSystemPath( rURL, aURLStr );
aURL = INetURLObject( aURLStr );
}
DBG_ASSERT( aURL.GetProtocol() != INetProtocol::NotValid, "invalid URL" );
rFileName = aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE );
}
void EasyFile::close()
{
pOStm.reset();
bOpen = false;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'insert' is required to be utilized.
↑ V1029 Numeric Truncation Error. Return value of the 'size' function is written to the 16-bit variable.