/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office 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 <oox/drawingml/diagram/diagram.hxx>
#include "diagram.hxx"
#include <com/sun/star/awt/Point.hpp>
#include <com/sun/star/awt/Size.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/drawing/XShape.hpp>
#include <com/sun/star/drawing/XShapes.hpp>
#include <com/sun/star/xml/dom/XDocument.hpp>
#include <com/sun/star/xml/sax/XFastSAXSerializable.hpp>
#include <com/sun/star/xml/dom/XDocumentBuilder.hpp>
#include <com/sun/star/xml/dom/DocumentBuilder.hpp>
#include <sal/log.hxx>
#include <editeng/unoprnms.hxx>
#include <drawingml/fillproperties.hxx>
#include <drawingml/lineproperties.hxx>
#include <drawingml/customshapeproperties.hxx>
#include <o3tl/unit_conversion.hxx>
#include <oox/drawingml/theme.hxx>
#include <oox/token/namespaces.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <svx/svdpage.hxx>
#include <oox/ppt/pptimport.hxx>
#include <comphelper/xmltools.hxx>
#include "diagramlayoutatoms.hxx"
#include "layoutatomvisitors.hxx"
#include "diagramfragmenthandler.hxx"
#include <comphelper/processfactory.hxx>
#include <com/sun/star/io/TempFile.hpp>
#include <oox/export/drawingml.hxx>
#include <oox/export/shapes.hxx>

#include <com/sun/star/xml/sax/XSAXSerializable.hpp>
#include <com/sun/star/xml/sax/Writer.hpp>
#include <oox/core/fastparser.hxx>
#include <unotools/streamwrap.hxx>
#include <tools/stream.hxx>

#ifdef DBG_UTIL
#include <osl/file.hxx>
#include <o3tl/environment.hxx>
#include <tools/stream.hxx>
#include <unotools/streamwrap.hxx>
#include <comphelper/storagehelper.hxx>
#include <com/sun/star/embed/XRelationshipAccess.hpp>
#endif

using namespace ::com::sun::star;

namespace oox::drawingml {

static void sortChildrenByZOrder(const ShapePtr& pShape)
{
    std::vector<ShapePtr>& rChildren = pShape->getChildren();

    // Offset the children from their default z-order stacking, if necessary.
    for (size_t i = 0; i < rChildren.size(); ++i)
        rChildren[i]->setZOrder(i);

    for (size_t i = 0; i < rChildren.size(); ++i)
    {
        const ShapePtr& pChild = rChildren[i];
        sal_Int32 nZOrderOff = pChild->getZOrderOff();
        if (nZOrderOff <= 0)
            continue;

        // Increase my ZOrder by nZOrderOff.
        pChild->setZOrder(pChild->getZOrder() + nZOrderOff);
        pChild->setZOrderOff(0);

        for (sal_Int32 j = 0; j < nZOrderOff; ++j)
        {
            size_t nIndex = i + j + 1;
            if (nIndex >= rChildren.size())
                break;

            // Decrease the ZOrder of the next nZOrderOff elements by one.
            const ShapePtr& pNext = rChildren[nIndex];
            pNext->setZOrder(pNext->getZOrder() - 1);
        }
    }

    // Now that the ZOrders are adjusted, sort the children.
    std::sort(rChildren.begin(), rChildren.end(),
              [](const ShapePtr& a, const ShapePtr& b) { return a->getZOrder() < b->getZOrder(); });

    // Apply also for children.
    for (const auto& rChild : rChildren)
        sortChildrenByZOrder(rChild);
}

/// Removes empty group shapes, now that their spacing influenced the layout.
static void removeUnneededGroupShapes(const ShapePtr& pShape)
{
    std::vector<ShapePtr>& rChildren = pShape->getChildren();

    std::erase_if(rChildren,
                                   [](const ShapePtr& aChild) {
                                       return aChild->getServiceName()
                                                  == "com.sun.star.drawing.GroupShape"
                                              && aChild->getChildren().empty();
                                   });

    for (const auto& pChild : rChildren)
    {
        removeUnneededGroupShapes(pChild);
    }
}

void SmartArtDiagram::createShapeHierarchyFromModel( const ShapePtr & pParentShape, bool bCreate )
{
    if (pParentShape->getSize().Width == 0 || pParentShape->getSize().Height == 0)
        SAL_WARN("oox.drawingml", "SmartArtDiagram cannot be correctly laid out. Size: "
            << pParentShape->getSize().Width << "x" << pParentShape->getSize().Height);

    pParentShape->setChildSize(pParentShape->getSize());

    const svx::diagram::Point* pRootPoint = mpData->getRootPoint();
    if (bCreate && mpLayout->getNode() && pRootPoint)
    {
        // create Shape hierarchy
        ShapeCreationVisitor aCreationVisitor(*this, pRootPoint, pParentShape);
        mpLayout->getNode()->setExistingShape(pParentShape);
        mpLayout->getNode()->accept(aCreationVisitor);

        // layout shapes - now all shapes are created
        ShapeLayoutingVisitor aLayoutingVisitor(*this, pRootPoint);
        mpLayout->getNode()->accept(aLayoutingVisitor);

        sortChildrenByZOrder(pParentShape);
        removeUnneededGroupShapes(pParentShape);
    }

    ShapePtr pBackground = std::make_shared<Shape>("com.sun.star.drawing.CustomShape");
    pBackground->setSubType(XML_rect);
    pBackground->getCustomShapeProperties()->setShapePresetType(XML_rect);
    pBackground->setSize(pParentShape->getSize());
    if (mpData->getBackgroundShapeFillProperties())
        pBackground->getFillProperties() = *mpData->getBackgroundShapeFillProperties();
    if (mpData->getBackgroundShapeLineProperties())
        pBackground->getLineProperties() = *mpData->getBackgroundShapeLineProperties();

    // create BackgroundShape, use empty string for identification
    pBackground->setDiagramDataModelID(EMPTY_OUSTRING);

    auto& aChildren = pParentShape->getChildren();
    aChildren.insert(aChildren.begin(), pBackground);
}

uno::Reference<xml::dom::XDocument> SmartArtDiagram::convertAndSet(std::u16string_view rDOM, svx::diagram::DomMapFlag aDomMapFlag)
{
    // construct MemoryStream and OStreamWrapper
    const OString sUtf8(OUStringToOString(rDOM, RTL_TEXTENCODING_UTF8));
    SvMemoryStream aStream(const_cast<char*>(sUtf8.getStr()), sUtf8.getLength(), StreamMode::READ);
    rtl::Reference<utl::OStreamWrapper> pStreamWrapper = new utl::OStreamWrapper(aStream);

    // create the dom parser & create DomTree
    uno::Reference<xml::dom::XDocumentBuilder> xDomBuilder(xml::dom::DocumentBuilder::create(comphelper::getProcessComponentContext()));
    uno::Reference<xml::dom::XDocument> aDomTree(xDomBuilder->parse(pStreamWrapper->getInputStream()));

    // set DomTree locally
    setOOXDomValue(aDomMapFlag, cpo::uno::Any(aDomTree));
    return aDomTree;
}

css::uno::Reference<css::xml::dom::XDocument> SmartArtDiagram::convertAndSet(std::u16string_view rDOMData, svx::diagram::DomMapFlag aDomMapFlag, bool bAdd)
{
    // construct MemoryStream and OStreamWrapper
    const OString sUtf8(OUStringToOString(rDOMData, RTL_TEXTENCODING_UTF8));
    SvMemoryStream aStream(const_cast<char*>(sUtf8.getStr()), sUtf8.getLength(), StreamMode::READ);
    rtl::Reference<utl::OStreamWrapper> pStreamWrapper = new utl::OStreamWrapper(aStream);

    // create the dom parser & create DomTree
    uno::Reference<xml::dom::XDocumentBuilder> xDomBuilder(xml::dom::DocumentBuilder::create(comphelper::getProcessComponentContext()));
    uno::Reference<xml::dom::XDocument> aDomTree(xDomBuilder->parse(pStreamWrapper->getInputStream()));

    // set DomTree locally
    if (bAdd)
        setOOXDomValue(aDomMapFlag, cpo::uno::Any(aDomTree));
    return aDomTree;
}

SmartArtDiagram::SmartArtDiagram()
: maDiagramFontHeights()
, mpData(std::make_shared<DiagramData_oox>())
, mpLayout(std::make_shared<DiagramLayout>(*this))
, maStyles()
, maColors()
, maDiagramPRDomMap()
{
}

SmartArtDiagram::SmartArtDiagram(SmartArtDiagram const& rSource)
: maDiagramFontHeights()
, mpData(rSource.mpData ? new DiagramData_oox(*rSource.mpData) : nullptr)
, mpLayout(rSource.mpLayout)
, maStyles(rSource.maStyles)
, maColors(rSource.maColors)
, maDiagramPRDomMap(rSource.maDiagramPRDomMap)
{
}

SmartArtDiagram::SmartArtDiagram(const boost::property_tree::ptree& rDiagramModel)
: maDiagramFontHeights()
, mpData(std::make_shared<DiagramData_oox>(rDiagramModel))
, mpLayout(std::make_shared<DiagramLayout>(*this))
, maStyles()
, maColors()
, maDiagramPRDomMap()
{
#ifdef DBG_UTIL
    mpData->dump();
#endif
    const OUString aOOXLayoutDOM(OUString::fromUtf8(rDiagramModel.get("OOXLayout", "")));
    const OUString aOOXStyleDOM(OUString::fromUtf8(rDiagramModel.get("OOXStyle", "")));
    const OUString aOOXColorDOM(OUString::fromUtf8(rDiagramModel.get("OOXColor", "")));

    if (!aOOXLayoutDOM.isEmpty() || !aOOXStyleDOM.isEmpty() || !aOOXColorDOM.isEmpty())
    {
        // we need a PowerPointImport for the FragmentHandlers, so create a single
        // temporary one. Use this for all possible DomTrees
        rtl::Reference<oox::ppt::PowerPointImport> xPPTImport(new oox::ppt::PowerPointImport(comphelper::getProcessComponentContext()));

        if (!aOOXLayoutDOM.isEmpty())
        {
            // create and set DomTree locally
            uno::Reference<xml::dom::XDocument> xDom(convertAndSet(aOOXLayoutDOM, svx::diagram::DomMapFlag::OOXLayout));

            // import DomTree to mpLayout
            uno::Reference<xml::sax::XFastSAXSerializable> xSerializer(xDom, uno::UNO_QUERY_THROW);
            rtl::Reference< core::FragmentHandler > xRefLayout(new DiagramLayoutFragmentHandler(*this, *xPPTImport, u"internal"_ustr, mpLayout));
            xPPTImport->importFragment(xRefLayout, xSerializer);
        }

        if (!aOOXStyleDOM.isEmpty())
        {
            // create and set DomTree locally
            uno::Reference<xml::dom::XDocument> xDom(convertAndSet(aOOXStyleDOM, svx::diagram::DomMapFlag::OOXStyle));

            // import DomTree to maStyles
            uno::Reference<xml::sax::XFastSAXSerializable> xSerializer(xDom, uno::UNO_QUERY_THROW);
            rtl::Reference< core::FragmentHandler > xRefLayout(new DiagramQStylesFragmentHandler(*xPPTImport, u"internal"_ustr, maStyles));
            xPPTImport->importFragment(xRefLayout, xSerializer);
        }

        if (!aOOXColorDOM.isEmpty())
        {
            // create and set DomTree locally
            uno::Reference<xml::dom::XDocument> xDom(convertAndSet(aOOXColorDOM, svx::diagram::DomMapFlag::OOXColor));

            // import DomTree to maColors
            uno::Reference<xml::sax::XFastSAXSerializable> xSerializer(xDom, uno::UNO_QUERY_THROW);
            rtl::Reference< core::FragmentHandler > xRefLayout(new ColorFragmentHandler(*xPPTImport, u"internal"_ustr, maColors));
            xPPTImport->importFragment(xRefLayout, xSerializer);
        }
    }
}

SmartArtDiagram::SmartArtDiagram(std::u16string_view rLayout, std::u16string_view rData, std::u16string_view rColors, std::u16string_view rQuickstyle)
: maDiagramFontHeights()
, mpData(std::make_shared<DiagramData_oox>())
, mpLayout(std::make_shared<DiagramLayout>(*this))
, maStyles()
, maColors()
, maDiagramPRDomMap()
{
    if (rLayout.empty() || rData.empty() || rQuickstyle.empty() || rColors.empty())
        return;

    // we need a PowerPointImport for the FragmentHandlers, so create a single
    // temporary one. Use this for all possible DomTrees
    rtl::Reference<oox::ppt::PowerPointImport> xPPTImport(new oox::ppt::PowerPointImport(comphelper::getProcessComponentContext()));

    if (!rData.empty())
    {
        // create and set DomTree locally/do *not* add to DomTree holder, this is a temporary instance
        uno::Reference<xml::dom::XDocument> xDom(convertAndSet(rData, svx::diagram::DomMapFlag::OOXLayout, false));

        // import DomTree to mpLayout
        uno::Reference<xml::sax::XFastSAXSerializable> xSerializer(xDom, uno::UNO_QUERY_THROW);
        rtl::Reference< core::FragmentHandler > xRefLayout(new DiagramDataFragmentHandler(*xPPTImport, u"internal"_ustr, mpData));
        xPPTImport->importFragment(xRefLayout, xSerializer);
    }

    if (!rLayout.empty())
    {
        // create and set DomTree locally
        uno::Reference<xml::dom::XDocument> xDom(convertAndSet(rLayout, svx::diagram::DomMapFlag::OOXLayout, true));

        // import DomTree to mpLayout
        uno::Reference<xml::sax::XFastSAXSerializable> xSerializer(xDom, uno::UNO_QUERY_THROW);
        rtl::Reference< core::FragmentHandler > xRefLayout(new DiagramLayoutFragmentHandler(*this, *xPPTImport, u"internal"_ustr, mpLayout));
        xPPTImport->importFragment(xRefLayout, xSerializer);
    }

    if (!rQuickstyle.empty())
    {
        // create and set DomTree locally
        uno::Reference<xml::dom::XDocument> xDom(convertAndSet(rQuickstyle, svx::diagram::DomMapFlag::OOXStyle, true));

        // import DomTree to maStyles
        uno::Reference<xml::sax::XFastSAXSerializable> xSerializer(xDom, uno::UNO_QUERY_THROW);
        rtl::Reference< core::FragmentHandler > xRefLayout(new DiagramQStylesFragmentHandler(*xPPTImport, u"internal"_ustr, maStyles));
        xPPTImport->importFragment(xRefLayout, xSerializer);
    }

    if (!rColors.empty())
    {
        // create and set DomTree locally
        uno::Reference<xml::dom::XDocument> xDom(convertAndSet(rColors, svx::diagram::DomMapFlag::OOXColor, true));

        // import DomTree to maColors
        uno::Reference<xml::sax::XFastSAXSerializable> xSerializer(xDom, uno::UNO_QUERY_THROW);
        rtl::Reference< core::FragmentHandler > xRefLayout(new ColorFragmentHandler(*xPPTImport, u"internal"_ustr, maColors));
        xPPTImport->importFragment(xRefLayout, xSerializer);
    }
}

SmartArtDiagram::~SmartArtDiagram()
{
}

cpo::uno::Any SmartArtDiagram::getOOXDomValue(svx::diagram::DomMapFlag aDomMapFlag) const
{
    const DiagramPRDomMap::const_iterator aHit = maDiagramPRDomMap.find(aDomMapFlag);

    if (aHit != maDiagramPRDomMap.end())
        return aHit->second;

    return cpo::uno::Any();
}

void SmartArtDiagram::setOOXDomValue(svx::diagram::DomMapFlag aDomMapFlag, const cpo::uno::Any& rValue)
{
    maDiagramPRDomMap[aDomMapFlag] = rValue;
}

void SmartArtDiagram::resetOOXDomValues(svx::diagram::DomMapFlags aDomMapFlags)
{
    for (const auto& rEntry : aDomMapFlags)
    {
        maDiagramPRDomMap.erase(rEntry);

        if (maDiagramPRDomMap.empty())
            return;
    }
}

bool SmartArtDiagram::checkMinimalDataDoms() const
{
    // check if re-creation is activated
    if (!SdrObject::useAdvancedDiagramFeatures() && maDiagramPRDomMap.end() == maDiagramPRDomMap.find(svx::diagram::DomMapFlag::OOXData))
        return false;

    if (maDiagramPRDomMap.end() == maDiagramPRDomMap.find(svx::diagram::DomMapFlag::OOXLayout))
        return false;

    if (maDiagramPRDomMap.end() == maDiagramPRDomMap.find(svx::diagram::DomMapFlag::OOXStyle))
        return false;

    if (maDiagramPRDomMap.end() == maDiagramPRDomMap.find(svx::diagram::DomMapFlag::OOXColor))
        return false;

    return true;
}

void SmartArtDiagram::writeDiagramOOXData(DrawingML& rOriginalDrawingML, uno::Reference<io::XOutputStream>& xOutputStream, std::u16string_view rDrawingRelId) const
{
    if (!xOutputStream)
        return;

    // re-create OOXData DomFile from model data
    sax_fastparser::FSHelperPtr aFS = std::make_shared<sax_fastparser::FastSerializerHelper>(xOutputStream, true);
    getData()->writeDiagramData(rOriginalDrawingML, aFS, rDrawingRelId, false);

    // this call is *important*, without it xDocBuilder->parse below fails and some strange
    // and wrong assertion gets thrown in ~FastSerializerHelper that  shall get called
    xOutputStream->closeOutput();

#ifdef DBG_UTIL
    uno::Reference< embed::XRelationshipAccess > xRelations( xOutputStream, uno::UNO_QUERY );
    if( xRelations.is() )
    {
        const cpo::uno::Sequence<cpo::uno::Sequence<beans::StringPair>> aSeqs = xRelations->getAllRelationships();
        for (const cpo::uno::Sequence<beans::StringPair>& aSeq : aSeqs)
        {
            SAL_INFO("oox", "RelationData:");
            for (const beans::StringPair& aPair : aSeq)
                SAL_INFO("oox", "  Key: " << aPair.First << ", Value: " << aPair.Second);
        }
    }

    const OUString env(o3tl::getEnvironment(u"DIAGRAM_DUMP_PATH"_ustr));
    if(!env.isEmpty())
    {
        OUString url;
        ::osl::FileBase::getFileURLFromSystemPath(env, url);
        SvFileStream aOutStream(url + "data_T.xml", StreamMode::WRITE|StreamMode::TRUNC);
        uno::Reference<io::XStream> xOutStream(new utl::OStreamWrapper(aOutStream));
        uno::Reference<io::XStream> xInStream(xOutputStream, uno::UNO_QUERY);
        comphelper::OStorageHelper::CopyInputToOutput(xInStream->getInputStream(), xOutStream->getOutputStream());
    }
#endif
}

void SmartArtDiagram::writeDiagramReducedOOXData(css::uno::Reference<css::io::XOutputStream>& xOutputStream) const
{
    // need a XmlFilterBase for ShapeExport/DrawingML. All the 'big' exports classes for
    // this are in sd/sc/sw and we are not in an oox export here, so none exists. Use
    // a minimal one. It's main purpose is to host the XModel access. In this case we
    // will not write any Line/Fill/TextAttributes and/or text, so will be fine for the
    // reduced form.
    class LocalFilterBase final : public oox::core::XmlFilterBase
    {
    public:
        explicit LocalFilterBase(css::uno::Reference<css::uno::XComponentContext> const& rxContext)
        : XmlFilterBase(rxContext) {}
        // virtual ~LocalFilterBase() override;

        virtual const oox::drawingml::Theme* getCurrentTheme() const override { return mpTheme.get(); }
        virtual std::shared_ptr<oox::drawingml::Theme> getCurrentThemePtr() const override { return mpTheme; }

        virtual oox::vml::Drawing* getVmlDrawing() override { return nullptr; }
        virtual oox::drawingml::table::TableStyleListPtr getTableStyles() override { return oox::drawingml::table::TableStyleListPtr(); }
        virtual oox::drawingml::chart::ChartConverter* getChartConverter() override { return nullptr; }
        virtual oox::ole::VbaProject* implCreateVbaProject() const override { return nullptr; }

        virtual bool importDocument() override { return false; }
        virtual bool exportDocument() override { return true; }

    private:
        virtual OUString SAL_CALL getImplementationName() override { return EMPTY_OUSTRING; }
        oox::drawingml::ThemePtr mpTheme;
    };

    rtl::Reference<LocalFilterBase> xLocalFilterBase(new LocalFilterBase(comphelper::getProcessComponentContext()));
    xLocalFilterBase->setSourceDocument(getData()->accessRootModel());

    // need a sax_fastparser
    sax_fastparser::FSHelperPtr aFS = std::make_shared<sax_fastparser::FastSerializerHelper>(xOutputStream, true);

    // need a DrawingML, use a ShapeExport. Claim to be a DOCUMENT_PPTX for all ODF targets
    oox::drawingml::ShapeExport aShapeExport(XML_dsp, aFS, nullptr, xLocalFilterBase.get(), DOCUMENT_PPTX, nullptr, true);
    aShapeExport.setDiagaramExport(true);

    // write reduced DiagramData
    getData()->writeDiagramData(aShapeExport, aFS, EMPTY_OUSTRING, true);
}

void SmartArtDiagram::writeDiagramOOXDrawing(DrawingML& rOriginalDrawingML, uno::Reference<io::XOutputStream>& xOutputStream) const
{
    if (!xOutputStream)
        return;

    // re-create OOXDrawing DomFile from model data
    SAL_INFO("oox", "DiagramReCreate: creating DomMapFlag::OOXDrawing");
    sax_fastparser::FSHelperPtr aFS = std::make_shared<sax_fastparser::FastSerializerHelper>(xOutputStream, true);
    getData()->writeDiagramReplacement(rOriginalDrawingML, aFS);

    // this call is *important*, without it xDocBuilder->parse below fails and some strange
    // and wrong assertion gets thrown in ~FastSerializerHelper that  shall get called
    xOutputStream->closeOutput();

#ifdef DBG_UTIL
    uno::Reference< embed::XRelationshipAccess > xRelations( xOutputStream, uno::UNO_QUERY );
    if( xRelations.is() )
    {
        const cpo::uno::Sequence<cpo::uno::Sequence<beans::StringPair>> aSeqs = xRelations->getAllRelationships();
        for (const cpo::uno::Sequence<beans::StringPair>& aSeq : aSeqs)
        {
            SAL_INFO("oox", "RelationDrawing:");
            for (const beans::StringPair& aPair : aSeq)
                SAL_INFO("oox", "  Key: " << aPair.First << ", Value: " << aPair.Second);
        }
    }

    const OUString env(o3tl::getEnvironment(u"DIAGRAM_DUMP_PATH"_ustr));
    if(!env.isEmpty())
    {
        OUString url;
        ::osl::FileBase::getFileURLFromSystemPath(env, url);
        SvFileStream aOutStream(url + "drawing_T.xml", StreamMode::WRITE|StreamMode::TRUNC);
        uno::Reference<io::XStream> xOutStream(new utl::OStreamWrapper(aOutStream));
        uno::Reference<io::XStream> xInStream(xOutputStream, uno::UNO_QUERY);
        comphelper::OStorageHelper::CopyInputToOutput(xInStream->getInputStream(), xOutStream->getOutputStream());
    }
#endif
}

void SmartArtDiagram::addDomTreeToModelData(svx::diagram::DomMapFlag aId, std::u16string_view aName, boost::property_tree::ptree& rTarget) const
{
    uno::Reference<xml::dom::XDocument> aDomTree;
    getOOXDomValue(aId) >>= aDomTree;

    if (aDomTree)
    {
        // serialize DomTree to a MemoryStream
        SvMemoryStream aStream( 1024, 1024 );
        rtl::Reference<utl::OStreamWrapper> pStreamWrapper = new utl::OStreamWrapper( aStream );
        uno::Reference<xml::sax::XSAXSerializable> serializer;
        uno::Reference<xml::sax::XWriter> writer = xml::sax::Writer::create(comphelper::getProcessComponentContext());
        serializer.set(aDomTree, uno::UNO_QUERY);
        writer->setOutputStream(pStreamWrapper->getOutputStream());
        serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), cpo::uno::Sequence<beans::StringPair>());

        // put into string
        const OUString aContent(static_cast<const char*>(aStream.GetData()), aStream.TellEnd(), RTL_TEXTENCODING_UTF8);

        // add to ModelData
        const OString sUtf8(OUStringToOString(aName, RTL_TEXTENCODING_UTF8));
        rTarget.put(sUtf8.getStr(), aContent);
    }
}

void SmartArtDiagram::addDiagramModelData(boost::property_tree::ptree& rTarget) const
{
    // add Point and Connection data
    getData()->addDiagramModelData(rTarget);

    // What DomMaps are needed?
    //
    // With the above OOXData is covered. OOXDataImageRels/OOXDataHlinkRels also,
    // these may/will be re-created when OOX export and a new OOXDataDomTree
    // needs to be created.
    // Similar with OOXDrawing: This contains parts of ModelData, e.g. Text and
    // Attributes represented by the XShapes/Sdrobjects, so for internal formats
    // this is not needed to be saved. This also true for OOXDrawingImageRels
    // and OOXDrawingHlinkRels.
    // We *do* import OOXLayoutDomTree/ModelInfo and this is used in the layouting
    // mechanism (reLayout), but it is not changed. We could add an export of that
    // for internal formats, but since it's not changed it just needs to be
    // preserved, either for internal use or export to OOX formats.
    // OOXStyle and OOXColor are imported only on oox import side, partially held
    // for initial import at Diagram classes. Also never changed, but maybe needed
    // for export to OOX formats. Not sure about that since Style and Color is
    // part of XShape/SdrObject Model Hierarchy, so exports to OOX should be possible
    // without these, but maybe MSO wants that data...
    //
    // OOXLayout = 3,
    // OOXStyle = 4,
    // OOXColor = 5,
    addDomTreeToModelData(svx::diagram::DomMapFlag::OOXLayout, u"OOXLayout", rTarget);
    addDomTreeToModelData(svx::diagram::DomMapFlag::OOXStyle, u"OOXStyle", rTarget);
    addDomTreeToModelData(svx::diagram::DomMapFlag::OOXColor, u"OOXColor", rTarget);
}

using ShapePairs
    = std::map<std::shared_ptr<drawingml::Shape>, uno::Reference<drawing::XShape>>;

void SmartArtDiagram::syncDiagramFontHeights()
{
    // Each name represents a group of shapes, for which the font height should have the same
    // scaling.
    for (const auto& rNameAndPairs : maDiagramFontHeights)
    {
        // Find out the minimum scale within this group.
        const ShapePairs& rShapePairs = rNameAndPairs.second;
        double fMinFontScale = 100.0;
        double fMinSpacingScale = 100.0;
        for (const auto& rShapePair : rShapePairs)
        {
            uno::Reference<beans::XPropertySet> xPropertySet(rShapePair.second, uno::UNO_QUERY);
            if (xPropertySet.is())
            {
                double fFontScale = 0.0;
                double fSpacingScale = 0.0;
                xPropertySet->getPropertyValue(u"TextFitToSizeFontScale"_ustr) >>= fFontScale;
                xPropertySet->getPropertyValue(u"TextFitToSizeSpacingScale"_ustr) >>= fSpacingScale;

                if (fFontScale > 0 && fSpacingScale > 0
                    && (fFontScale < fMinFontScale || (fFontScale == fMinFontScale && fSpacingScale < fMinSpacingScale)))
                {
                    fMinFontScale = fFontScale;
                    fMinSpacingScale = fSpacingScale;
                }
            }
        }

        // Set that minimum scale for all members of the group.
        if (fMinFontScale < 100.0 || fMinSpacingScale < 100.0)
        {
            for (const auto& rShapePair : rShapePairs)
            {
                uno::Reference<beans::XPropertySet> xPropertySet(rShapePair.second, uno::UNO_QUERY);
                if (xPropertySet.is())
                {
                    xPropertySet->setPropertyValue(u"TextFitToSizeFontScale"_ustr, cpo::uno::Any(fMinFontScale));
                    xPropertySet->setPropertyValue(u"TextFitToSizeSpacingScale"_ustr, cpo::uno::Any(fMinSpacingScale));
                }
            }
        }
    }

    // no longer needed after processing
    maDiagramFontHeights.clear();
}

static uno::Reference<xml::dom::XDocument> loadFragment(
    core::XmlFilterBase& rFilter,
    const OUString& rFragmentPath )
{
    // load diagramming fragments into DOM representation, that later
    // gets serialized back to SAX events and parsed
    return rFilter.importFragment( rFragmentPath );
}

static uno::Reference<xml::dom::XDocument> loadFragment(
    core::XmlFilterBase& rFilter,
    const rtl::Reference< core::FragmentHandler >& rxHandler )
{
    return loadFragment( rFilter, rxHandler->getFragmentPath() );
}

static void importFragment( core::XmlFilterBase& rFilter,
                     const uno::Reference<xml::dom::XDocument>& rXDom,
                     svx::diagram::DomMapFlag aDomMapFlag,
                     const DiagramPtr& pDiagram,
                     const rtl::Reference< core::FragmentHandler >& rxHandler )
{
    pDiagram->setOOXDomValue(aDomMapFlag, cpo::uno::Any(rXDom));

    uno::Reference<xml::sax::XFastSAXSerializable> xSerializer(
        rXDom, uno::UNO_QUERY_THROW);

    // now serialize DOM tree into internal data structures
    rFilter.importFragment( rxHandler, xSerializer );
}

namespace
{
/**
 * A fragment handler that just counts the number of <dsp:sp> elements in a
 * fragment.
 */
class DiagramShapeCounter : public oox::core::FragmentHandler2
{
public:
    DiagramShapeCounter(oox::core::XmlFilterBase& rFilter, const OUString& rFragmentPath,
                        sal_Int32& nCounter);
    oox::core::ContextHandlerRef onCreateContext(sal_Int32 nElement,
                                                 const AttributeList& rAttribs) override;

private:
    sal_Int32& m_nCounter;
};

DiagramShapeCounter::DiagramShapeCounter(oox::core::XmlFilterBase& rFilter,
                                         const OUString& rFragmentPath, sal_Int32& nCounter)
    : FragmentHandler2(rFilter, rFragmentPath)
    , m_nCounter(nCounter)
{
}

oox::core::ContextHandlerRef DiagramShapeCounter::onCreateContext(sal_Int32 nElement,
                                                                  const AttributeList& /*rAttribs*/)
{
    switch (nElement)
    {
        case DSP_TOKEN(drawing):
        case DSP_TOKEN(spTree):
            return this;
        case DSP_TOKEN(sp):
            ++m_nCounter;
            break;
        default:
            break;
    }

    return nullptr;
}
}

void loadDiagram( ShapePtr const & pShape,
                  core::XmlFilterBase& rFilter,
                  const OUString& rDataModelPath,
                  const OUString& rLayoutPath,
                  const OUString& rQStylePath,
                  const OUString& rColorStylePath,
                  const oox::core::Relations& rRelations )
{
    DiagramPtr pDiagram = std::make_shared<SmartArtDiagram>();

    try
    {
        // set DiagramFontHeights at filter
        rFilter.setDiagramFontHeights(&pDiagram->getDiagramFontHeights());

        // data
        if( !rDataModelPath.isEmpty() )
        {
            rtl::Reference< core::FragmentHandler > xRefDataModel(
                    new DiagramDataFragmentHandler( rFilter, rDataModelPath, pDiagram->getData() ));

            importFragment(rFilter,
                           loadFragment(rFilter,xRefDataModel),
                           svx::diagram::DomMapFlag::OOXData,
                           pDiagram,
                           xRefDataModel);

            cpo::uno::Sequence<cpo::uno::Sequence<cpo::uno::Any>> aDataImageRelsMap(
                pShape->resolveRelationshipsOfTypeFromOfficeDoc(
                    rFilter, xRefDataModel->getFragmentPath(), u"image"));
            cpo::uno::Sequence<cpo::uno::Sequence<cpo::uno::Any>> aDataHlinkRelsMap(
                pShape->resolveRelationshipsOfTypeFromOfficeDoc(
                    rFilter, xRefDataModel->getFragmentPath(), u"hlink"));

            pDiagram->setOOXDomValue(svx::diagram::DomMapFlag::OOXDataImageRels,
                                     cpo::uno::Any(aDataImageRelsMap));
            pDiagram->setOOXDomValue(svx::diagram::DomMapFlag::OOXDataHlinkRels,
                                     cpo::uno::Any(aDataHlinkRelsMap));

            // Pass the info to pShape
            for (auto const& extDrawing : pDiagram->getData()->getExtDrawings())
            {
                OUString aFragmentPath = rRelations.getFragmentPathFromRelId(extDrawing);
                // Ignore RelIds which don't resolve to a fragment path.
                if (aFragmentPath.isEmpty())
                    continue;

                sal_Int32 nCounter = 0;
                rtl::Reference<core::FragmentHandler> xCounter(
                    new DiagramShapeCounter(rFilter, aFragmentPath, nCounter));
                rFilter.importFragment(xCounter);
                // Ignore ext drawings which don't actually have any shapes.
                if (nCounter == 0)
                    continue;

                pShape->addExtDrawingRelId(extDrawing);
            }
        }

        // Layout: always import to allow editing in the future. It's needed for
        // DiagramHelper_oox::reLayout to re-create the oox::Shape(s) for the
        // model. Without importing these the diagram model will be not complete.
        // NOTE: This also adds the DomMaps to rMainDomMap, so the lines
        //     DiagramDomMap& rMainDomMap = pDiagram->getDomMap();
        //     rMainDomMap[u"OOXLayout"_ustr] = loadFragment(rFilter,rLayoutPath);
        //     rMainDomMap[u"OOXStyle"_ustr] = loadFragment(rFilter,rQStylePath);
        // which were used before if !pShape->getExtDrawings().empty() are not
        // needed
        if (!rLayoutPath.isEmpty())
        {
            rtl::Reference< core::FragmentHandler > xRefLayout(
                    new DiagramLayoutFragmentHandler( *pDiagram, rFilter, rLayoutPath, pDiagram->getLayout()));

            importFragment(rFilter,
                    loadFragment(rFilter,xRefLayout),
                    svx::diagram::DomMapFlag::OOXLayout,
                    pDiagram,
                    xRefLayout);
        }

        // Style: same as for Layout (above)
        if( !rQStylePath.isEmpty() )
        {
            rtl::Reference< core::FragmentHandler > xRefQStyle(
                    new DiagramQStylesFragmentHandler( rFilter, rQStylePath, pDiagram->getStyles() ));

            importFragment(rFilter,
                    loadFragment(rFilter,xRefQStyle),
                    svx::diagram::DomMapFlag::OOXStyle,
                    pDiagram,
                    xRefQStyle);
        }

        // colors
        if( !rColorStylePath.isEmpty() )
        {
            rtl::Reference< core::FragmentHandler > xRefColorStyle(
                new ColorFragmentHandler( rFilter, rColorStylePath, pDiagram->getColors() ));

            importFragment(rFilter,
                loadFragment(rFilter,xRefColorStyle),
                svx::diagram::DomMapFlag::OOXColor,
                pDiagram,
                xRefColorStyle);
        }

        if( !pDiagram->getData()->getExtDrawings().empty() )
        {
            const DiagramColorMap::const_iterator aColor = pDiagram->getColors().find(u"node0"_ustr);
            if( aColor != pDiagram->getColors().end() && !aColor->second.maTextFillColors.empty())
            {
                // TODO(F1): well, actually, there might be *several* color
                // definitions in it, after all it's called list.
                pShape->setFontRefColorForNodes(DiagramColor::getColorByIndex(aColor->second.maTextFillColors, -1));
            }
        }

        // collect data, init maps
        // for Diagram import, do - for now - NOT clear all oox::drawingml::Shape
        pDiagram->getData()->buildDiagramDataModel(false);
#ifdef DBG_UTIL
        pDiagram->getData()->dump();
#endif

        // diagram loaded. now lump together & attach to shape
        // create own geometry if extLst is not present (no geometric
        // representation is available in file). This will - if false -
        // just create the BackgroundShape.
        // NOTE: Need to use pShape->getExtDrawings() here, this is the
        // already *filtered* version, see usage of DiagramShapeCounter
        // above. Moving to local bool, there might more conditions show
        // up
        static bool bIgnoreExtDrawings(nullptr != std::getenv("DIAGRAM_IGNORE_EXTDRAWINGS"));
        const bool bCreate(bIgnoreExtDrawings || pShape->getExtDrawings().empty());
        pDiagram->createShapeHierarchyFromModel(pShape, bCreate);

        // Get the oox::Theme definition and - if available - move/secure the
        // original ImportData directly to the Diagram ModelData
        std::shared_ptr<::oox::drawingml::Theme> aTheme(rFilter.getCurrentThemePtr());
        if(aTheme)
            pDiagram->getData()->setThemeDocument(aTheme->getFragment());

        // Prepare support for the advanced DiagramHelper using Diagram & Theme data
        // This is where pDiagram is moved to where it will stay, else it wil get
        // cleaned up (what is intended)
        pShape->prepareDiagramHelper(pDiagram, rFilter.getCurrentThemePtr());
    }
    catch (...)
    {
        // unset DiagramFontHeights at filter if there was a failure
        // to avoid dangling pointer
        rFilter.setDiagramFontHeights(nullptr);
        throw;
    }
}

const oox::drawingml::Color&
DiagramColor::getColorByIndex(const std::vector<oox::drawingml::Color>& rColors, sal_Int32 nIndex)
{
    assert(!rColors.empty());
    if (nIndex == -1)
    {
        return rColors[rColors.size() - 1];
    }

    return rColors[nIndex % rColors.size()];
}
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
