/* -*- 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: */
