/* -*- 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/.
*
* 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 <boost/property_tree/json_parser/error.hpp>
#include <com/sun/star/uno/XComponentContext.hpp>
#include <com/sun/star/frame/Desktop.hpp>
#include <osl/diagnose.h>
#include <numrule.hxx>
#include <node.hxx>
#include <ndtxt.hxx>
#include <fltini.hxx>
#include <fchrfmt.hxx>
#include <swerror.h>
#include <strings.hrc>
#include <mdiexp.hxx>
#include <poolfmt.hxx>
#include <iodetect.hxx>
#include <hintids.hxx>
#include <sfx2/docfile.hxx>
#include <sfx2/sfxsids.hrc>
#include <IDocumentStylePoolAccess.hxx>
#include <fmtinfmt.hxx>
#include <frmatr.hxx>
#include <fmtanchr.hxx>
#include <fmtfsize.hxx>
#include <unotools/securityoptions.hxx>
#include <vcl/graph.hxx>
#include <vcl/graphicfilter.hxx>
#include <comphelper/random.hxx>
#include <comphelper/propertysequence.hxx>
#include <comphelper/sequence.hxx>
#include <comphelper/sequenceashashmap.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/propertyvalue.hxx>
#include <sfx2/sfxbasemodel.hxx>
#include <rtl/uri.hxx>
#include <ndgrf.hxx>
#include <swtypes.hxx>
#include <fmturl.hxx>
#include <formatcontentcontrol.hxx>
#include <docsh.hxx>
#include "swmd.hxx"
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 SwMarkdownParser::SetNodeNum(sal_uInt8 nLevel)
{
SwTextNode* pTextNode = m_pPam->GetPointNode().GetTextNode();
if (!pTextNode)
{
SAL_WARN("sw.md", "No Text-Node at PaM-Position");
return;
}
OSL_ENSURE(GetNumInfo().GetNumRule(), "No numbering rule");
const UIName& 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()->Invalidate();
}
sal_Int32 SwMarkdownParser::StripTrailingLF()
{
sal_Int32 nStripped = 0;
const sal_Int32 nLen = m_pPam->GetPoint()->GetContentIndex();
if (nLen)
{
SwTextNode* pTextNd = m_pPam->GetPoint()->GetNode().GetTextNode();
if (pTextNd)
{
sal_Int32 nPos = nLen;
sal_Int32 nLFCount = 0;
while (nPos && ('\x0a' == pTextNd->GetText()[--nPos]))
nLFCount++;
if (nLFCount)
{
if (nLFCount > 2)
{
nLFCount = 2;
}
nPos = nLen - nLFCount;
SwContentIndex nIdx(pTextNd, nPos);
pTextNd->EraseText(nIdx, nLFCount);
nStripped = nLFCount;
}
}
}
return nStripped;
}
bool SwMarkdownParser::AppendTextNode(SwMdAppendMode eMode, bool bUpdateNum)
{
sal_Int32 nLFStripped = StripTrailingLF();
if ((AM_NOSPACE == eMode || AM_SOFTNOSPACE == eMode) && nLFStripped > 1)
eMode = AM_SPACE;
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 : MD_PARSPACE, RES_UL_SPACE));
}
}
}
m_bNoParSpace = AM_NOSPACE == eMode || AM_SOFTNOSPACE == eMode;
bool bRet = m_xDoc->getIDocumentContentOperations().AppendTextNode(*m_pPam->GetPoint());
if (bUpdateNum)
{
if (GetNumInfo().GetDepth())
{
sal_uInt8 nLvl = GetNumInfo().GetLevel();
SetNodeNum(nLvl);
}
else
m_pPam->GetPointNode().GetTextNode()->ResetAttr(RES_PARATR_NUMRULE);
}
return bRet;
}
void SwMarkdownParser::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);
}
pTextNode->SetAttr(SvxULSpaceItem(rULSpace.GetUpper(), MD_PARSPACE, RES_UL_SPACE));
}
void SwMarkdownParser::AddBlockQuote()
{
if (m_pPam->GetPoint()->GetContentIndex())
AppendTextNode(AM_SPACE);
else
AddParSpace();
SwTextFormatColl* pColl
= m_xDoc->getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_HTML_BLOCKQUOTE);
m_nBlockQuoteDepth++;
// Set the style on the current paragraph.
m_xDoc->SetTextFormatColl(*m_pPam, pColl);
}
void SwMarkdownParser::EndBlockQuote()
{
if (m_pPam->GetPoint()->GetContentIndex())
AppendTextNode(AM_SPACE);
else
AddParSpace();
if (m_nBlockQuoteDepth == 0)
{
SwTextFormatColl* pColl
= m_xDoc->getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_TEXT);
m_xDoc->SetTextFormatColl(*m_pPam, pColl);
}
m_nBlockQuoteDepth--;
}
void SwMarkdownParser::AddHR()
{
if (m_pPam->GetPoint()->GetContentIndex())
AppendTextNode(AM_SPACE);
SwTextFormatColl* pColl
= m_xDoc->getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_HTML_HR);
m_xDoc->SetTextFormatColl(*m_pPam, pColl);
}
void SwMarkdownParser::EndHR()
{
AppendTextNode(AM_SPACE);
SwTextFormatColl* pColl
= m_xDoc->getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_TEXT);
m_xDoc->SetTextFormatColl(*m_pPam, pColl);
}
void SwMarkdownParser::StartPara()
{
if (m_pPam->GetPoint()->GetContentIndex())
AppendTextNode(AM_SPACE);
else
AddParSpace();
}
void SwMarkdownParser::EndPara()
{
if (m_pPam->GetPoint()->GetContentIndex())
AppendTextNode(AM_SPACE);
else
AddParSpace();
}
void SwMarkdownParser::StartHeading(sal_uInt8 nLvl)
{
if (m_pPam->GetPoint()->GetContentIndex())
AppendTextNode(AM_SPACE);
else
AddParSpace();
SwTextFormatColl* pColl = m_xDoc->getIDocumentStylePoolAccess().GetTextCollFromPool(
RES_POOLCOLL_HEADLINE_BASE + nLvl);
m_xDoc->SetTextFormatColl(*m_pPam, pColl);
}
void SwMarkdownParser::EndHeading()
{
if (m_pPam->GetPoint()->GetContentIndex())
AppendTextNode(AM_SPACE);
else
AddParSpace();
SwTextFormatColl* pColl
= m_xDoc->getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_TEXT);
m_xDoc->SetTextFormatColl(*m_pPam, pColl);
}
void SwMarkdownParser::StartNumberedBulletList(MD_BLOCKTYPE aListType)
{
SwMdNumRuleInfo& rInfo = GetNumInfo();
bool bSpace = (rInfo.GetDepth()) == 0;
if (m_pPam->GetPoint()->GetContentIndex())
AppendTextNode(bSpace ? AM_SPACE : AM_NOSPACE, false);
else if (bSpace)
AddParSpace();
rInfo.IncDepth();
sal_uInt8 nLevel
= static_cast<sal_uInt8>((rInfo.GetDepth() <= MAXLEVEL ? rInfo.GetDepth() : MAXLEVEL) - 1);
if (!rInfo.GetNumRule())
{
sal_uInt16 nPos = m_xDoc->MakeNumRule(m_xDoc->GetUniqueNumRuleName());
rInfo.SetNumRule(m_xDoc->GetNumRuleTable()[nPos]);
}
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 (aListType == MD_BLOCK_OL)
{
aNumFormat.SetNumberingType(SVX_NUM_ARABIC);
nChrFormatPoolId = RES_POOLCHR_NUM_LEVEL;
}
else
{
if (numfunc::IsDefBulletFontUserDefined())
{
aNumFormat.SetBulletFont(&numfunc::GetDefBulletFont());
}
aNumFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL);
aNumFormat.SetBulletChar(cBulletChar);
nChrFormatPoolId = RES_POOLCHR_BULLET_LEVEL;
}
sal_Int32 nAbsLSpace = MD_NUMBER_BULLET_MARGINLEFT;
sal_Int32 nFirstLineIndent = MD_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_xDoc->getIDocumentStylePoolAccess().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);
}
{
sal_uInt8 nLvl = nLevel;
SetNodeNum(nLvl);
}
if (bChangeNumFormat)
{
rInfo.GetNumRule()->Set(nLevel, aNumFormat);
m_xDoc->ChgNumRuleFormats(*rInfo.GetNumRule());
}
}
void SwMarkdownParser::EndNumberedBulletList()
{
SwMdNumRuleInfo& 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());
}
bool bSpace = (rInfo.GetDepth()) == 1;
if (bAppend)
AppendTextNode(bSpace ? AM_SPACE : AM_NOSPACE, false);
else if (bSpace)
AddParSpace();
if (rInfo.GetDepth() > 0)
{
rInfo.DecDepth();
if (!rInfo.GetDepth())
{
// 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())
{
if (numfunc::IsDefBulletFontUserDefined())
{
aNumFormat.SetBulletFont(&numfunc::GetDefBulletFont());
}
aNumFormat.SetBulletChar(cBulletChar);
}
aNumFormat.SetAbsLSpace((i + 1) * MD_NUMBER_BULLET_MARGINLEFT);
aNumFormat.SetFirstLineOffset(MD_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());
}
}
}
void SwMarkdownParser::StartNumberedBulletListItem(MD_BLOCK_LI_DETAIL aDetail)
{
sal_uInt8 nLevel = GetNumInfo().GetLevel();
sal_uInt16 nStart = GetNumInfo().GetNodeStartValue(nLevel);
GetNumInfo().SetNodeStartValue(nLevel);
// 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.md", "No Text-Node at PaM-Position");
return;
}
UIName aNumRuleName;
if (GetNumInfo().GetNumRule())
{
aNumRuleName = GetNumInfo().GetNumRule()->GetName();
if (aDetail.is_task)
{
// Map the task list item to a Writer checkbox content control.
bool bChecked = (aDetail.task_mark == 'x' || aDetail.task_mark == 'X') ? true : false;
auto pContentControl = std::make_shared<SwContentControl>(nullptr);
sal_Int32 nId = comphelper::rng::uniform_uint_distribution(
1, std::numeric_limits<sal_Int32>::max());
SwFormatContentControl aContentControl(pContentControl, RES_TXTATR_CONTENTCONTROL);
pContentControl->SetId(nId);
pContentControl->SetCheckbox(true);
pContentControl->SetCheckedState(SwContentControl::CHECKED_STATE);
pContentControl->SetUncheckedState(SwContentControl::UNCHECKED_STATE);
pContentControl->SetChecked(bChecked);
OUString aPlaceholder;
if (bChecked)
{
aPlaceholder = SwContentControl::CHECKED_STATE;
}
else
{
aPlaceholder = SwContentControl::UNCHECKED_STATE;
}
pTextNode->InsertText(aPlaceholder, SwContentIndex(pTextNode, pTextNode->Len()));
SwPosition aStart(*m_pPam->GetPoint());
aStart.nContent -= aPlaceholder.getLength();
SwPosition aEnd(*m_pPam->GetPoint());
SwPaM aPaM(aStart, aEnd);
m_xDoc->getIDocumentContentOperations().InsertPoolItem(aPaM, aContentControl);
pTextNode->InsertText(u" "_ustr, SwContentIndex(pTextNode, pTextNode->Len()));
}
}
else
{
aNumRuleName = m_xDoc->GetUniqueNumRuleName();
SwNumRule aNumRule(aNumRuleName, SvxNumberFormat::LABEL_WIDTH_AND_POSITION);
SwNumFormat aNumFormat(aNumRule.Get(0));
if (numfunc::IsDefBulletFontUserDefined())
{
aNumFormat.SetBulletFont(&numfunc::GetDefBulletFont());
}
aNumFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL);
aNumFormat.SetBulletChar(cBulletChar);
aNumFormat.SetCharFormat(
m_xDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool(RES_POOLCHR_BULLET_LEVEL));
aNumFormat.SetFirstLineOffset(MD_NUMBER_BULLET_INDENT);
aNumRule.Set(0, aNumFormat);
m_xDoc->MakeNumRule(aNumRuleName, &aNumRule);
}
static_cast<SwContentNode*>(pTextNode)->SetAttr(SwNumRuleItem(aNumRuleName));
pTextNode->SetAttrListLevel(nLevel);
if (nLevel < MAXLEVEL)
{
pTextNode->SetCountedInList(true);
}
if (nStart != USHRT_MAX)
{
pTextNode->SetListRestart(true);
pTextNode->SetAttrListRestartValue(nStart);
}
if (GetNumInfo().GetNumRule())
GetNumInfo().GetNumRule()->Invalidate();
}
void SwMarkdownParser::EndNumberedBulletListItem()
{
if (m_pPam->GetPoint()->GetContentIndex())
AppendTextNode(AM_SPACE);
}
void SwMarkdownParser::BeginHtmlBlock()
{
if (m_pPam->GetPoint()->GetContentIndex())
AppendTextNode(AM_SPACE);
else
AddParSpace();
}
void SwMarkdownParser::InsertHtmlData()
{
OString aData = rtl::OUStringToOString(m_htmlData, RTL_TEXTENCODING_UTF8);
SvMemoryStream aStream(const_cast<char*>(aData.getStr()), aData.getLength(), StreamMode::READ);
SwReader aReader(aStream, OUString(), OUString(), *m_pPam);
aReader.Read(*ReadHTML);
}
void SwMarkdownParser::EndHtmlBlock()
{
InsertHtmlData();
m_htmlData.clear();
}
void SwMarkdownParser::BeginCodeBlock()
{
if (m_pPam->GetPoint()->GetContentIndex())
AppendTextNode(AM_SPACE);
else
AddParSpace();
SwTextFormatColl* pColl
= m_xDoc->getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_HTML_PRE);
m_xDoc->SetTextFormatColl(*m_pPam, pColl);
SvxBrushItem aBrushItem(COL_CODE_BLOCK, RES_BACKGROUND);
m_xDoc->getIDocumentContentOperations().InsertPoolItem(*m_pPam, aBrushItem);
}
void SwMarkdownParser::EndCodeBlock()
{
if (m_pPam->GetPoint()->GetContentIndex())
AppendTextNode(AM_SPACE);
else
AddParSpace();
SwTextFormatColl* pColl
= m_xDoc->getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_TEXT);
m_xDoc->SetTextFormatColl(*m_pPam, pColl);
ClearAttrs();
}
void SwMarkdownParser::InsertText(OUString& aStr)
{
SwContentNode* pCnd = m_pPam->GetPointContentNode();
sal_Int32 nStartPos = m_pPam->GetPoint()->GetContentIndex();
m_xDoc->getIDocumentContentOperations().InsertString(*m_pPam, aStr);
SwPaM aAttrPam(*m_pPam->GetPoint());
aAttrPam.SetMark();
aAttrPam.GetMark()->Assign(*pCnd, nStartPos);
if (!m_aAttrStack.empty())
{
SetAttrs(aAttrPam);
ClearAttrs();
}
}
void SwMarkdownParser::SetAttrs(SwPaM& rRange)
{
for (const auto& pItem : m_aAttrStack)
{
if (pItem)
{
if (rRange.HasMark() && (*rRange.GetMark() != *rRange.GetPoint()))
{
m_xDoc->getIDocumentContentOperations().InsertPoolItem(rRange, *pItem);
}
}
}
}
void SwMarkdownParser::ClearAttrs() { m_xDoc->ResetAttrs(*m_pPam, true); }
void SwMarkdownParser::InsertImage(const MDImage& rImg)
{
OUString sGrfNm = INetURLObject::GetAbsURL(m_sBaseURL, rImg.url);
Graphic aGraphic;
INetURLObject aGraphicURL(sGrfNm);
if (aGraphicURL.GetProtocol() == INetProtocol::Data)
{
// 'data:' URL: read that here, initialize aGraphic and clear sGrfNm.
std::unique_ptr<SvMemoryStream> pStream = aGraphicURL.getData();
if (pStream)
{
GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
aGraphic = rFilter.ImportUnloadedGraphic(*pStream);
sGrfNm.clear();
}
}
if (!sGrfNm.isEmpty())
{
aGraphic.SetDefaultType();
}
Size aGrfSz(0, 0);
if (allowAccessLink(*m_xDoc) && !aGraphicURL.IsExoticProtocol() && !sGrfNm.isEmpty())
{
GraphicDescriptor aDescriptor(aGraphicURL);
if (aDescriptor.Detect(true))
aGrfSz
= o3tl::convert(aDescriptor.GetSizePixel(), o3tl::Length::px, o3tl::Length::twip);
}
else if (aGraphic.GetType() == GraphicType::Bitmap)
{
// We have an unloaded graphic in aGraphic, read its size.
aGrfSz = o3tl::convert(aGraphic.GetSizePixel(), o3tl::Length::px, o3tl::Length::twip);
}
tools::Long nWidth = 0;
tools::Long nHeight = 0;
if (!aGrfSz.IsEmpty())
{
nWidth = aGrfSz.getWidth();
nHeight = aGrfSz.getHeight();
if (nWidth > MD_MAX_IMAGE_WIDTH_IN_TWIPS || nHeight > MD_MAX_IMAGE_HEIGHT_IN_TWIPS)
{
double fScaleX = static_cast<double>(MD_MAX_IMAGE_WIDTH_IN_TWIPS) / nWidth;
double fScaleY = static_cast<double>(MD_MAX_IMAGE_HEIGHT_IN_TWIPS) / nHeight;
double fScale = std::min(fScaleX, fScaleY);
nWidth = static_cast<tools::Long>(nWidth * fScale);
nHeight = static_cast<tools::Long>(nHeight * fScale);
}
if (nWidth < MD_MIN_IMAGE_WIDTH_IN_TWIPS && nWidth < MD_MIN_IMAGE_HEIGHT_IN_TWIPS)
{
nWidth = MD_MIN_IMAGE_WIDTH_IN_TWIPS;
nHeight = MD_MIN_IMAGE_HEIGHT_IN_TWIPS;
}
}
else
{
nWidth = MD_MIN_IMAGE_WIDTH_IN_TWIPS;
nHeight = MD_MIN_IMAGE_HEIGHT_IN_TWIPS;
}
SfxItemSet aFlySet(
SfxItemSet::makeFixedSfxItemSet<RES_FRMATR_BEGIN, RES_FRMATR_END>(m_xDoc->GetAttrPool()));
aFlySet.Put(SwFormatAnchor(RndStdIds::FLY_AS_CHAR));
aFlySet.Put(SwFormatFrameSize(SwFrameSize::Fixed, nWidth, nHeight));
aFlySet.Put(SwFormatHoriOrient(0, text::HoriOrientation::NONE, text::RelOrientation::CHAR));
aFlySet.Put(
SwFormatVertOrient(0, text::VertOrientation::CHAR_CENTER, text::RelOrientation::CHAR));
if (!rImg.link.isEmpty())
{
// Have a link, set that on the image.
SwFormatURL aFormatURL;
aFormatURL.SetURL(rImg.link, /*bServerMap=*/false);
aFlySet.Put(aFormatURL);
}
SanitizeAnchor(aFlySet);
SwFlyFrameFormat* pFlyFormat = m_xDoc->getIDocumentContentOperations().InsertGraphic(
*m_pPam, sGrfNm, OUString(), &aGraphic, &aFlySet, nullptr, nullptr);
if (pFlyFormat)
{
pFlyFormat->SetObjTitle(rImg.title);
pFlyFormat->SetObjDescription(rImg.desc);
}
m_bNoParSpace = true;
}
void SwMarkdownParser::RegisterTable(MDTable* pTable) { m_aTables.push_back(pTable); }
void SwMarkdownParser::DeRegisterTable(MDTable* pTable) { std::erase(m_aTables, pTable); }
SwMarkdownParser::SwMarkdownParser(SwDoc& rD, SwPaM& rCursor, SvStream& rIn, OUString aBaseURL,
bool bReadNewDoc)
: m_xDoc(&rD)
, m_rInput(rIn)
// , m_pMedium(&rMedium)
, m_pNumRuleInfo(new SwMdNumRuleInfo)
, m_sBaseURL(std::move(aBaseURL))
, m_bNewDoc(bReadNewDoc)
{
rCursor.DeleteMark();
m_pPam = &rCursor;
m_rInput.ResetError();
m_nFilesize = m_rInput.TellEnd();
m_rInput.Seek(STREAM_SEEK_TO_BEGIN);
m_rInput.ResetError();
m_pArr.reset(new char[m_nFilesize + 1]);
}
void MarkdownReader::SetupFilterOptions(SwDoc& rDoc)
{
// See if any import options are provided: if so, collect them into a map.
if (!m_pMedium)
{
return;
}
auto pItem = m_pMedium->GetItemSet().GetItem(SID_FILE_FILTEROPTIONS);
if (!pItem)
{
return;
}
OUString aFilterOptions = pItem->GetValue();
if (!aFilterOptions.startsWith("{"))
{
return;
}
uno::Sequence<beans::PropertyValue> aFilterData;
try
{
std::vector<beans::PropertyValue> aData
= comphelper::JsonToPropertyValues(aFilterOptions.toUtf8());
aFilterData = comphelper::containerToSequence(aData);
}
catch (const boost::property_tree::json_parser::json_parser_error& e)
{
SAL_WARN("sw.md", "failed to parse FilterOptions as JSON: " << e.message());
}
comphelper::SequenceAsHashMap aMap(aFilterData);
OUString aTemplateURL;
aMap[u"TemplateURL"_ustr] >>= aTemplateURL;
if (aTemplateURL.isEmpty())
{
return;
}
// Have a TemplateURL: open it in a new object shell.
if (!m_pMedium->GetName().isEmpty())
{
aTemplateURL = rtl::Uri::convertRelToAbs(m_pMedium->GetName(), aTemplateURL);
}
SwDocShell* pDocShell = rDoc.GetDocShell();
if (!pDocShell)
{
return;
}
// Go via filter detection so non-ODF templates work, too.
uno::Reference<uno::XComponentContext> xContext = comphelper::getProcessComponentContext();
uno::Reference<frame::XDesktop2> xComponentLoader = frame::Desktop::create(xContext);
uno::Sequence<css::beans::PropertyValue> aTemplateArgs = {
comphelper::makePropertyValue("Hidden", true),
};
uno::Reference<lang::XComponent> xTemplateComponent
= xComponentLoader->loadComponentFromURL(aTemplateURL, u"_blank"_ustr, 0, aTemplateArgs);
auto pTemplateModel = dynamic_cast<SfxBaseModel*>(xTemplateComponent.get());
if (!pTemplateModel)
{
return;
}
SfxObjectShell* pTemplateShell = pTemplateModel->GetObjectShell();
if (!pTemplateShell)
{
return;
}
// Copy the styles from the template doc to our document.
pDocShell->LoadStyles(*pTemplateShell);
xTemplateComponent->dispose();
}
ErrCodeMsg MarkdownReader::Read(SwDoc& rDoc, const OUString& rBaseURL, SwPaM& rPam, const OUString&)
{
ErrCode nRet;
SetupFilterOptions(rDoc);
SwMarkdownParser parser(rDoc, rPam, *m_pStream, rBaseURL, !m_bInsertMode);
nRet = parser.CallParser();
return nRet;
}
ErrCode SwMarkdownParser::CallParser()
{
::StartProgress(STR_STATSTR_W4WREAD, 0, m_nFilesize, m_xDoc->GetDocShell());
SwTextFormatColl* pColl
= m_xDoc->getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_TEXT);
m_xDoc->SetTextFormatColl(*m_pPam, pColl);
m_rInput.ReadBytes(m_pArr.get(), m_nFilesize);
m_pArr[m_nFilesize] = '\0';
ErrCode nRet;
MD_PARSER parser = { 0,
MD_DIALECT_GITHUB | MD_FLAG_WIKILINKS,
enter_block_callback,
leave_block_callback,
enter_span_callback,
leave_span_callback,
text_callback,
nullptr,
nullptr };
int result = md_parse(m_pArr.get(), m_nFilesize, &parser, static_cast<void*>(this));
if (result != 0)
{
nRet = ERRCODE_IO_GENERAL;
}
::EndProgress(m_xDoc->GetDocShell());
return nRet;
}
SwMarkdownParser::~SwMarkdownParser()
{
m_pArr.reset();
m_pNumRuleInfo.reset();
m_xDoc.clear();
}
//static
void SwMarkdownParser::SanitizeAnchor(SfxItemSet& rFrameItemSet)
{
const SwFormatAnchor& rAnch = rFrameItemSet.Get(RES_ANCHOR);
if (SwNode* pAnchorNode = rAnch.GetAnchorNode())
{
if (pAnchorNode->IsEndNode())
{
SAL_WARN("sw.md", "Invalid EndNode Anchor");
rFrameItemSet.ClearItem(RES_ANCHOR);
}
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
↑ V530 The return value of function 'InsertText' is required to be utilized.
↑ V530 The return value of function 'InsertText' is required to be utilized.
↑ V530 The return value of function 'Read' is required to be utilized.
↑ V547 Expression 'nLevel < MAXLEVEL' is always true.
↑ V560 A part of conditional expression is always true: nWidth < MD_MIN_IMAGE_HEIGHT_IN_TWIPS.