/* -*- 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 <com/sun/star/awt/Point.hpp>
#include <com/sun/star/awt/Size.hpp>
#include <com/sun/star/xml/dom/XDocument.hpp>
#include <com/sun/star/xml/sax/XFastSAXSerializable.hpp>
#include <rtl/ustrbuf.hxx>
#include <sal/log.hxx>
#include <editeng/unoprnms.hxx>
#include <drawingml/textbody.hxx>
#include <drawingml/textparagraph.hxx>
#include <drawingml/textrun.hxx>
#include <drawingml/diagram/diagram.hxx>
#include <drawingml/fillproperties.hxx>
#include <oox/ppt/pptshapegroupcontext.hxx>
#include <oox/ppt/pptshape.hxx>
 
#include "diagramlayoutatoms.hxx"
#include "layoutatomvisitors.hxx"
#include "diagramfragmenthandler.hxx"
 
#include <iostream>
#include <fstream>
 
using namespace ::com::sun::star;
 
namespace oox { namespace drawingml {
 
namespace dgm {
 
void Connection::dump() const
{
    SAL_INFO(
        "oox.drawingml",
        "cnx modelId " << msModelId << ", srcId " << msSourceId << ", dstId "
            << msDestId << ", parTransId " << msParTransId << ", presId "
            << msPresId << ", sibTransId " << msSibTransId << ", srcOrd "
            << mnSourceOrder << ", dstOrd " << mnDestOrder);
}
 
void Point::dump() const
{
    SAL_INFO(
        "oox.drawingml",
        "pt text " << mpShape.get() << ", cnxId " << msCnxId << ", modelId "
            << msModelId << ", type " << mnType);
}
 
} // dgm namespace
 
DiagramData::DiagramData() :
    mpFillProperties( new FillProperties ),
    mnMaxDepth(0)
{
}
 
const dgm::Point* DiagramData::getRootPoint() const
{
    for (const auto & aCurrPoint : maPoints)
        if (aCurrPoint.mnType == XML_doc)
            return &aCurrPoint;
 
    SAL_WARN("oox.drawingml", "No root point");
    return nullptr;
}
 
void DiagramData::dump() const
{
    SAL_INFO("oox.drawingml", "Dgm: DiagramData # of cnx: " << maConnections.size() );
    for (const auto& rConnection : maConnections)
        rConnection.dump();
 
    SAL_INFO("oox.drawingml", "Dgm: DiagramData # of pt: " << maPoints.size() );
    for (const auto& rPoint : maPoints)
        rPoint.dump();
}
 
#ifdef DEBUG_OOX_DIAGRAM
OString normalizeDotName( const OUString& rStr )
{
    OUStringBuffer aBuf;
    aBuf.append('N');
 
    const sal_Int32 nLen(rStr.getLength());
    sal_Int32 nCurrIndex(0);
    while( nCurrIndex < nLen )
    {
        const sal_Int32 aChar=rStr.iterateCodePoints(&nCurrIndex);
        if( aChar != '-' && aChar != '{' && aChar != '}' )
            aBuf.append((sal_Unicode)aChar);
    }
 
    return OUStringToOString(aBuf.makeStringAndClear(),
                                  RTL_TEXTENCODING_UTF8);
}
#endif
 
static sal_Int32 calcDepth( const OUString& rNodeName,
                            const dgm::Connections& rCnx )
{
    // find length of longest path in 'isChild' graph, ending with rNodeName
    for (auto const& elem : rCnx)
    {
        if( !elem.msParTransId.isEmpty() &&
            !elem.msSibTransId.isEmpty() &&
            !elem.msSourceId.isEmpty() &&
            !elem.msDestId.isEmpty() &&
            elem.mnType == XML_parOf &&
            rNodeName == elem.msDestId )
        {
            return calcDepth(elem.msSourceId, rCnx) + 1;
        }
    }
 
    return 0;
}
 
void Diagram::build(  )
{
    // build name-object maps
#ifdef DEBUG_OOX_DIAGRAM
    std::ofstream output("/tmp/tree.dot");
 
    output << "digraph datatree {" << std::endl;
#endif
    dgm::Points& rPoints = getData()->getPoints();
    for (auto & point : rPoints)
    {
#ifdef DEBUG_OOX_DIAGRAM
        output << "\t"
               << normalizeDotName(point.msModelId).getStr()
               << "[";
 
        if( !point.msPresentationLayoutName.isEmpty() )
            output << "label=\""
                   << OUStringToOString(
                       point.msPresentationLayoutName,
                       RTL_TEXTENCODING_UTF8).getStr() << "\", ";
        else
            output << "label=\""
                   << OUStringToOString(
                       point.msModelId,
                       RTL_TEXTENCODING_UTF8).getStr() << "\", ";
 
        switch( point.mnType )
        {
            case XML_doc: output << "style=filled, color=red"; break;
            case XML_asst: output << "style=filled, color=green"; break;
            default:
            case XML_node: output << "style=filled, color=blue"; break;
            case XML_pres: output << "style=filled, color=yellow"; break;
            case XML_parTrans: output << "color=grey"; break;
            case XML_sibTrans: output << " "; break;
        }
 
        output << "];" << std::endl;
#endif
 
        // does currpoint have any text set?
        if( point.mpShape &&
            point.mpShape->getTextBody() &&
            !point.mpShape->getTextBody()->getParagraphs().empty() &&
            !point.mpShape->getTextBody()->getParagraphs().front()->getRuns().empty() )
        {
#ifdef DEBUG_OOX_DIAGRAM
            static sal_Int32 nCount=0;
            output << "\t"
                   << "textNode" << nCount
                   << " ["
                   << "label=\""
                   << OUStringToOString(
                       point.mpShape->getTextBody()->getParagraphs().front()->getRuns().front()->getText(),
                       RTL_TEXTENCODING_UTF8).getStr()
                   << "\"" << "];" << std::endl;
            output << "\t"
                   << normalizeDotName(point.msModelId).getStr()
                   << " -> "
                   << "textNode" << nCount++
                   << ";" << std::endl;
#endif
        }
 
        const bool bInserted1=getData()->getPointNameMap().insert(
            std::make_pair(point.msModelId,&point)).second;
 
        SAL_WARN_IF(!bInserted1, "oox.drawingml", "Diagram::build(): non-unique point model id");
 
        if( !point.msPresentationLayoutName.isEmpty() )
        {
            DiagramData::PointsNameMap::value_type::second_type& rVec=
                getData()->getPointsPresNameMap()[point.msPresentationLayoutName];
            rVec.push_back(&point);
        }
    }
 
    const dgm::Connections& rConnections = getData()->getConnections();
    for (auto const& connection : rConnections)
    {
#ifdef DEBUG_OOX_DIAGRAM
        if( !connection.msParTransId.isEmpty() ||
            !connection.msSibTransId.isEmpty() )
        {
            if( !connection.msSourceId.isEmpty() ||
                !connection.msDestId.isEmpty() )
            {
                output << "\t"
                       << normalizeDotName(connection.msSourceId).getStr()
                       << " -> "
                       << normalizeDotName(connection.msParTransId).getStr()
                       << " -> "
                       << normalizeDotName(connection.msSibTransId).getStr()
                       << " -> "
                       << normalizeDotName(connection.msDestId).getStr()
                       << " [style=dotted,"
                       << ((connection.mnType == XML_presOf) ? " color=red, " : ((connection.mnType == XML_presParOf) ? " color=green, " : " "))
                       << "label=\""
                       << OUStringToOString(connection.msModelId,
                                                 RTL_TEXTENCODING_UTF8 ).getStr()
                       << "\"];" << std::endl;
            }
            else
            {
                output << "\t"
                       << normalizeDotName(connection.msParTransId).getStr()
                       << " -> "
                       << normalizeDotName(connection.msSibTransId).getStr()
                       << " ["
                       << ((connection.mnType == XML_presOf) ? " color=red, " : ((connection.mnType == XML_presParOf) ? " color=green, " : " "))
                       << "label=\""
                       << OUStringToOString(connection.msModelId,
                                                 RTL_TEXTENCODING_UTF8 ).getStr()
                       << "\"];" << std::endl;
            }
        }
        else if( !connection.msSourceId.isEmpty() ||
                 !connection.msDestId.isEmpty() )
            output << "\t"
                   << normalizeDotName(connection.msSourceId).getStr()
                   << " -> "
                   << normalizeDotName(connection.msDestId).getStr()
                   << " [label=\""
                   << OUStringToOString(connection.msModelId,
                                             RTL_TEXTENCODING_UTF8 ).getStr()
                   << ((connection.mnType == XML_presOf) ? "\", color=red]" : ((connection.mnType == XML_presParOf) ? "\", color=green]" : "\"]"))
                   << ";" << std::endl;
#endif
 
        const bool bInserted1=getData()->getConnectionNameMap().insert(
            std::make_pair(connection.msModelId,&connection)).second;
 
        SAL_WARN_IF(!bInserted1, "oox.drawingml", "Diagram::build(): non-unique connection model id");
 
        if( connection.mnType == XML_presOf )
        {
            DiagramData::StringMap::value_type::second_type& rVec=getData()->getPresOfNameMap()[connection.msDestId];
            rVec.emplace_back(
                    connection.msSourceId,sal_Int32(0));
        }
    }
 
    // assign outline levels
    DiagramData::StringMap& rStringMap = getData()->getPresOfNameMap();
    for (auto & elemPresOf : rStringMap)
    {
        for (auto & elem : elemPresOf.second)
        {
            const sal_Int32 nDepth=calcDepth(elem.first,
                                             getData()->getConnections());
            elem.second = nDepth != 0 ? nDepth : -1;
            if (nDepth > getData()->getMaxDepth())
                getData()->setMaxDepth(nDepth);
        }
    }
#ifdef DEBUG_OOX_DIAGRAM
    output << "}" << std::endl;
#endif
}
 
void Diagram::addTo( const ShapePtr & pParentShape )
{
    // collect data, init maps
    build( );
 
    if (pParentShape->getSize().Width == 0 || pParentShape->getSize().Height == 0)
        SAL_WARN("oox.drawingml", "Diagram cannot be correctly laid out. Size: "
            << pParentShape->getSize().Width << "x" << pParentShape->getSize().Height);
 
    pParentShape->setChildSize(pParentShape->getSize());
 
    if( mpLayout->getNode() )
    {
        // create Shape hierarchy
        ShapeCreationVisitor aCreationVisitor(pParentShape, *this);
        mpLayout->getNode()->setExistingShape(pParentShape);
        mpLayout->getNode()->accept(aCreationVisitor);
 
        // layout shapes - now all shapes are created
        ShapeLayoutingVisitor aLayoutingVisitor;
        mpLayout->getNode()->accept(aLayoutingVisitor);
    }
    pParentShape->setDiagramDoms( getDomsAsPropertyValues() );
}
 
uno::Sequence<beans::PropertyValue> Diagram::getDomsAsPropertyValues() const
{
    sal_Int32 length = maMainDomMap.size();
 
    if (maDataRelsMap.hasElements())
        ++length;
 
    uno::Sequence<beans::PropertyValue> aValue(length);
    beans::PropertyValue* pValue = aValue.getArray();
    for (auto const& mainDom : maMainDomMap)
    {
        pValue->Name = mainDom.first;
        pValue->Value <<= mainDom.second;
        ++pValue;
    }
 
    if (maDataRelsMap.hasElements())
    {
        pValue->Name = "OOXDiagramDataRels";
        pValue->Value <<= maDataRelsMap;
        ++pValue;
    }
 
    return aValue;
}
 
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 );
}
 
uno::Reference<xml::dom::XDocument> loadFragment(
    core::XmlFilterBase& rFilter,
    const rtl::Reference< core::FragmentHandler >& rxHandler )
{
    return loadFragment( rFilter, rxHandler->getFragmentPath() );
}
 
void importFragment( core::XmlFilterBase& rFilter,
                     const uno::Reference<xml::dom::XDocument>& rXDom,
                     const char* pDocName,
                     const DiagramPtr& pDiagram,
                     const rtl::Reference< core::FragmentHandler >& rxHandler )
{
    DiagramDomMap& rMainDomMap = pDiagram->getDomMap();
    rMainDomMap[OUString::createFromAscii(pDocName)] = rXDom;
 
    uno::Reference<xml::sax::XFastSAXSerializable> xSerializer(
        rXDom, uno::UNO_QUERY_THROW);
 
    // now serialize DOM tree into internal data structures
    rFilter.importFragment( rxHandler, xSerializer );
}
 
void loadDiagram( ShapePtr const & pShape,
                  core::XmlFilterBase& rFilter,
                  const OUString& rDataModelPath,
                  const OUString& rLayoutPath,
                  const OUString& rQStylePath,
                  const OUString& rColorStylePath )
{
    DiagramPtr pDiagram( new Diagram );
 
    DiagramDataPtr pData( new DiagramData() );
    pDiagram->setData( pData );
 
    DiagramLayoutPtr pLayout( new DiagramLayout(*pDiagram) );
    pDiagram->setLayout( pLayout );
 
    // data
    if( !rDataModelPath.isEmpty() )
    {
        rtl::Reference< core::FragmentHandler > xRefDataModel(
                new DiagramDataFragmentHandler( rFilter, rDataModelPath, pData ));
 
        importFragment(rFilter,
                       loadFragment(rFilter,xRefDataModel),
                       "OOXData",
                       pDiagram,
                       xRefDataModel);
 
        pDiagram->getDataRelsMap() = pShape->resolveRelationshipsOfTypeFromOfficeDoc( rFilter,
                xRefDataModel->getFragmentPath(), "image" );
 
        // Pass the info to pShape
        for (auto const& extDrawing : pData->getExtDrawings())
                pShape->addExtDrawingRelId(extDrawing);
    }
 
    // extLst is present, lets bet on that and ignore the rest of the data from here
    if( pData->getExtDrawings().empty() )
    {
        // layout
        if( !rLayoutPath.isEmpty() )
        {
            rtl::Reference< core::FragmentHandler > xRefLayout(
                    new DiagramLayoutFragmentHandler( rFilter, rLayoutPath, pLayout ));
 
            importFragment(rFilter,
                    loadFragment(rFilter,xRefLayout),
                    "OOXLayout",
                    pDiagram,
                    xRefLayout);
        }
 
        // style
        if( !rQStylePath.isEmpty() )
        {
            rtl::Reference< core::FragmentHandler > xRefQStyle(
                    new DiagramQStylesFragmentHandler( rFilter, rQStylePath, pDiagram->getStyles() ));
 
            importFragment(rFilter,
                    loadFragment(rFilter,xRefQStyle),
                    "OOXStyle",
                    pDiagram,
                    xRefQStyle);
        }
    }
    else
    {
        // We still want to add the XDocuments to the DiagramDomMap
        DiagramDomMap& rMainDomMap = pDiagram->getDomMap();
        rMainDomMap[OUString("OOXLayout")] = loadFragment(rFilter,rLayoutPath);
        rMainDomMap[OUString("OOXStyle")] = loadFragment(rFilter,rQStylePath);
    }
 
    // colors
    if( !rColorStylePath.isEmpty() )
    {
        rtl::Reference< core::FragmentHandler > xRefColorStyle(
            new ColorFragmentHandler( rFilter, rColorStylePath, pDiagram->getColors() ));
 
        importFragment(rFilter,
            loadFragment(rFilter,xRefColorStyle),
            "OOXColor",
            pDiagram,
            xRefColorStyle);
    }
 
    if( !pData->getExtDrawings().empty() )
    {
        const DiagramColorMap::const_iterator aColor = pDiagram->getColors().find("node0");
        if( aColor != pDiagram->getColors().end() )
        {
            pShape->setFontRefColorForNodes(aColor->second.maTextFillColor);
        }
    }
 
    // diagram loaded. now lump together & attach to shape
    pDiagram->addTo(pShape);
}
 
void loadDiagram( const ShapePtr& pShape,
                  core::XmlFilterBase& rFilter,
                  const uno::Reference<xml::dom::XDocument>& rXDataModelDom,
                  const uno::Reference<xml::dom::XDocument>& rXLayoutDom,
                  const uno::Reference<xml::dom::XDocument>& rXQStyleDom,
                  const uno::Reference<xml::dom::XDocument>& rXColorStyleDom )
{
    DiagramPtr pDiagram( new Diagram );
 
    DiagramDataPtr pData( new DiagramData() );
    pDiagram->setData( pData );
 
    DiagramLayoutPtr pLayout( new DiagramLayout(*pDiagram) );
    pDiagram->setLayout( pLayout );
 
    // data
    if( rXDataModelDom.is() )
        importFragment(rFilter,
                       rXDataModelDom,
                       "OOXData",
                       pDiagram,
                       new DiagramDataFragmentHandler( rFilter, "", pData ));
 
    // layout
    if( rXLayoutDom.is() )
        importFragment(rFilter,
                       rXLayoutDom,
                       "OOXLayout",
                       pDiagram,
                       new DiagramLayoutFragmentHandler( rFilter, "", pLayout ));
 
    // style
    if( rXQStyleDom.is() )
        importFragment(rFilter,
                       rXQStyleDom,
                       "OOXStyle",
                       pDiagram,
                       new DiagramQStylesFragmentHandler( rFilter, "", pDiagram->getStyles() ));
 
    // colors
    if( rXColorStyleDom.is() )
        importFragment(rFilter,
                       rXColorStyleDom,
                       "OOXColor",
                       pDiagram,
                       new ColorFragmentHandler( rFilter, "", pDiagram->getColors() ));
 
    // diagram loaded. now lump together & attach to shape
    pDiagram->addTo(pShape);
}
 
} }
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V758 The 'rVec' reference becomes invalid when temporary object returned by a function is destroyed.

V758 The 'rVec' reference becomes invalid when temporary object returned by a function is destroyed.