tdf143222 Handle alternate content of graphicData element.

Handle alternate content and make true choice.

According to ooxml spec ole object requires exactly one pic
element. (ECMA-376 Part 1, Annex A, CT_OleObject). In the
current case first choice has not pic element and we should
allow fallback processing.

Change-Id: I30b7de703b8c2f00d6bf286e05eea505ac3627f2
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/118539
Tested-by: Jenkins
Reviewed-by: Gülşah Köse <gulsah.kose@collabora.com>
diff --git a/include/oox/core/contexthandler2.hxx b/include/oox/core/contexthandler2.hxx
index 4e25608..3a75aff 100644
--- a/include/oox/core/contexthandler2.hxx
+++ b/include/oox/core/contexthandler2.hxx
@@ -72,7 +72,7 @@ struct ElementInfo;
class OOX_DLLPUBLIC ContextHandler2Helper
{
public:
    explicit            ContextHandler2Helper( bool bEnableTrimSpace );
    explicit            ContextHandler2Helper( bool bEnableTrimSpace, XmlFilterBase& rFilter );
    explicit            ContextHandler2Helper( const ContextHandler2Helper& rParent );
    virtual             ~ContextHandler2Helper();

@@ -201,6 +201,21 @@ protected:
    /** Must be called from endRecord() in derived classes. */
    void                implEndRecord( sal_Int32 nRecId );

    bool                prepareMceContext( sal_Int32 nElement, const AttributeList& rAttribs );
    XmlFilterBase&      getDocFilter() const { return mrFilter; }

    enum class MCE_STATE
    {
        Started,
        FoundChoice
    };

    MCE_STATE           getMCEState() const { return aMceState.back(); }
    void                setMCEState( MCE_STATE aState ) { aMceState.back() = aState; }
    void                addMCEState( MCE_STATE aState ) { aMceState.push_back( aState ); }
    void                removeMCEState() { aMceState.pop_back(); }
    bool                isMCEStateEmpty() { return aMceState.empty(); }

private:
    ContextHandler2Helper& operator=( const ContextHandler2Helper& ) = delete;

@@ -214,9 +229,11 @@ private:

    ContextStackRef     mxContextStack;     ///< Stack of all processed elements.
    size_t              mnRootStackSize;    ///< Stack size on construction time.
    std::vector<MCE_STATE> aMceState;

protected:
    bool                mbEnableTrimSpace;  ///< True = trim whitespace in characters().
    XmlFilterBase&      mrFilter;
};

class OOX_DLLPUBLIC ContextHandler2 : public ContextHandler, public ContextHandler2Helper
diff --git a/include/oox/core/fragmenthandler2.hxx b/include/oox/core/fragmenthandler2.hxx
index 86d1453..598426e 100644
--- a/include/oox/core/fragmenthandler2.hxx
+++ b/include/oox/core/fragmenthandler2.hxx
@@ -47,17 +47,6 @@ class XmlFilterBase;

class OOX_DLLPUBLIC FragmentHandler2 : public FragmentHandler, public ContextHandler2Helper
{
protected:
    enum class MCE_STATE
    {
        Started,
        FoundChoice
    };
    ::std::vector<MCE_STATE>           aMceState;

    bool                prepareMceContext( sal_Int32 nElement, const AttributeList& rAttribs );


public:
    explicit            FragmentHandler2(
                            XmlFilterBase& rFilter,
diff --git a/include/oox/drawingml/graphicshapecontext.hxx b/include/oox/drawingml/graphicshapecontext.hxx
index 4813d5f..ffd579f 100644
--- a/include/oox/drawingml/graphicshapecontext.hxx
+++ b/include/oox/drawingml/graphicshapecontext.hxx
@@ -62,6 +62,7 @@ public:
    OleObjectGraphicDataContext( ::oox::core::ContextHandler2Helper const & rParent, const ShapePtr& pShapePtr );
    virtual ~OleObjectGraphicDataContext() override;
    virtual ::oox::core::ContextHandlerRef onCreateContext( ::sal_Int32 Element, const ::oox::AttributeList& rAttribs ) override;
    virtual void onEndElement() override;

private:
    ::oox::vml::OleObjectInfo& mrOleObjectInfo;
diff --git a/include/oox/ole/oleobjecthelper.hxx b/include/oox/ole/oleobjecthelper.hxx
index d2506f3..5b792f2 100644
--- a/include/oox/ole/oleobjecthelper.hxx
+++ b/include/oox/ole/oleobjecthelper.hxx
@@ -47,6 +47,7 @@ struct OleObjectInfo
    bool                mbLinked;           ///< True = linked OLE object, false = embedded OLE object.
    bool                mbShowAsIcon;       ///< True = show as icon, false = show contents.
    bool                mbAutoUpdate;
    bool                mbHasPicture;       ///<Ole object requires a picture element according to spec.>

    explicit            OleObjectInfo();
};
diff --git a/oox/source/core/contexthandler2.cxx b/oox/source/core/contexthandler2.cxx
index 8ce9784b..1613a3d 100644
--- a/oox/source/core/contexthandler2.cxx
+++ b/oox/source/core/contexthandler2.cxx
@@ -18,16 +18,20 @@
 */

#include <oox/core/contexthandler2.hxx>
#include <oox/core/xmlfilterbase.hxx>
#include <oox/helper/attributelist.hxx>
#include <oox/token/namespaces.hxx>
#include <oox/token/tokens.hxx>
#include <rtl/ustrbuf.hxx>
#include <o3tl/safeint.hxx>
#include <osl/diagnose.h>
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>

namespace oox::core {

using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::xml::sax;

/** Information about a processed element. */
@@ -40,10 +44,11 @@ struct ElementInfo
    explicit     ElementInfo() : maChars( 0), mnElement( XML_TOKEN_INVALID ), mbTrimSpaces( false ) {}
};

ContextHandler2Helper::ContextHandler2Helper( bool bEnableTrimSpace ) :
ContextHandler2Helper::ContextHandler2Helper( bool bEnableTrimSpace, XmlFilterBase& rFilter ) :
    mxContextStack( std::make_shared<ContextStack>() ),
    mnRootStackSize( 0 ),
    mbEnableTrimSpace( bEnableTrimSpace )
    mbEnableTrimSpace( bEnableTrimSpace ),
    mrFilter( rFilter )
{
    pushElementInfo( XML_ROOT_CONTEXT );
}
@@ -51,7 +56,8 @@ ContextHandler2Helper::ContextHandler2Helper( bool bEnableTrimSpace ) :
ContextHandler2Helper::ContextHandler2Helper( const ContextHandler2Helper& rParent ) :
    mxContextStack( rParent.mxContextStack ),
    mnRootStackSize( rParent.mxContextStack->size() ),
    mbEnableTrimSpace( rParent.mbEnableTrimSpace )
    mbEnableTrimSpace( rParent.mbEnableTrimSpace ),
    mrFilter(rParent.mrFilter)
{
}

@@ -188,6 +194,13 @@ ContextHandler2::~ContextHandler2()
Reference< XFastContextHandler > SAL_CALL ContextHandler2::createFastChildContext(
        sal_Int32 nElement, const Reference< XFastAttributeList >& rxAttribs )
{
    if( getNamespace( nElement ) == NMSP_mce ) // TODO for checking 'Ignorable'
    {
        if( prepareMceContext( nElement, AttributeList( rxAttribs ) ) )
            return this;
        return nullptr;
    }

    return implCreateChildContext( nElement, rxAttribs );
}

@@ -207,6 +220,72 @@ void SAL_CALL ContextHandler2::endFastElement( sal_Int32 nElement )
    implEndElement( nElement );
}

bool ContextHandler2Helper::prepareMceContext( sal_Int32 nElement, const AttributeList& rAttribs )
{
    switch( nElement )
    {
        case MCE_TOKEN( AlternateContent ):
            addMCEState( MCE_STATE::Started );
            break;

        case MCE_TOKEN( Choice ):
            {
                if (isMCEStateEmpty() || getMCEState() != MCE_STATE::Started)
                    return false;

                OUString aRequires = rAttribs.getString( XML_Requires, "none" );

                // At this point we can't access namespaces as the correct xml filter
                // is long gone. For now let's decide depending on a list of supported
                // namespaces like we do in writerfilter

                std::vector<OUString> aSupportedNS =
                {
                    "a14", // Impress needs this to import math formulas.
                    "p14",
                    "p15",
                    "x12ac",
                    "v"
                };

                Reference<XServiceInfo> xModel(getDocFilter().getModel(), UNO_QUERY);
                if (xModel.is() && xModel->supportsService("com.sun.star.sheet.SpreadsheetDocument"))
                {
                    // No a14 for Calc documents, it would cause duplicated shapes as-is.
                    auto it = std::find(aSupportedNS.begin(), aSupportedNS.end(), "a14");
                    if (it != aSupportedNS.end())
                    {
                        aSupportedNS.erase(it);
                    }
                }

                if (std::find(aSupportedNS.begin(), aSupportedNS.end(), aRequires) != aSupportedNS.end())
                    setMCEState( MCE_STATE::FoundChoice ) ;
                else
                    return false;
            }
            break;

        case MCE_TOKEN( Fallback ):
            if( !isMCEStateEmpty() && getMCEState() == MCE_STATE::Started )
                break;
            return false;
        default:
            {
                OUString str = rAttribs.getString( MCE_TOKEN( Ignorable ), OUString() );
                if( !str.isEmpty() )
                {
                    // Sequence< css::xml::FastAttribute > attrs = rAttribs.getFastAttributeList()->getFastAttributes();
                    // printf("MCE: %s\n", OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() );
                    // TODO: Check & Get the namespaces in "Ignorable"
                    // printf("NS: %d : %s\n", attrs.getLength(), OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() );
                }
            }
            return false;
    }
    return true;
}

// oox.core.RecordContext interface -------------------------------------------

ContextHandlerRef ContextHandler2::createRecordContext( sal_Int32 nRecId, SequenceInputStream& rStrm )
diff --git a/oox/source/core/fragmenthandler2.cxx b/oox/source/core/fragmenthandler2.cxx
index 121f344..3ee410f 100644
--- a/oox/source/core/fragmenthandler2.cxx
+++ b/oox/source/core/fragmenthandler2.cxx
@@ -34,7 +34,7 @@ using namespace ::com::sun::star::xml::sax;

FragmentHandler2::FragmentHandler2( XmlFilterBase& rFilter, const OUString& rFragmentPath, bool bEnableTrimSpace ) :
    FragmentHandler( rFilter, rFragmentPath ),
    ContextHandler2Helper( bEnableTrimSpace )
    ContextHandler2Helper( bEnableTrimSpace, rFilter )
{
}

@@ -54,72 +54,6 @@ void SAL_CALL FragmentHandler2::endDocument()
    finalizeImport();
}

bool FragmentHandler2::prepareMceContext( sal_Int32 nElement, const AttributeList& rAttribs )
{
    switch( nElement )
    {
        case MCE_TOKEN( AlternateContent ):
            aMceState.push_back( MCE_STATE::Started );
            break;

        case MCE_TOKEN( Choice ):
            {
                if (aMceState.empty() || aMceState.back() != MCE_STATE::Started)
                    return false;

                OUString aRequires = rAttribs.getString( XML_Requires, "none" );

                // At this point we can't access namespaces as the correct xml filter
                // is long gone. For now let's decide depending on a list of supported
                // namespaces like we do in writerfilter

                std::vector<OUString> aSupportedNS =
                {
                    "a14", // Impress needs this to import math formulas.
                    "p14",
                    "p15",
                    "x12ac",
                    "v",
                };

                uno::Reference<lang::XServiceInfo> xModel(getFilter().getModel(), uno::UNO_QUERY);
                if (xModel.is() && xModel->supportsService("com.sun.star.sheet.SpreadsheetDocument"))
                {
                    // No a14 for Calc documents, it would cause duplicated shapes as-is.
                    auto it = std::find(aSupportedNS.begin(), aSupportedNS.end(), "a14");
                    if (it != aSupportedNS.end())
                    {
                        aSupportedNS.erase(it);
                    }
                }

                if (std::find(aSupportedNS.begin(), aSupportedNS.end(), aRequires) != aSupportedNS.end())
                    aMceState.back() = MCE_STATE::FoundChoice;
                else
                    return false;
            }
            break;

        case MCE_TOKEN( Fallback ):
            if( !aMceState.empty() && aMceState.back() == MCE_STATE::Started )
                break;
            return false;
        default:
            {
                OUString str = rAttribs.getString( MCE_TOKEN( Ignorable ), OUString() );
                if( !str.isEmpty() )
                {
                    // Sequence< css::xml::FastAttribute > attrs = rAttribs.getFastAttributeList()->getFastAttributes();
                    // printf("MCE: %s\n", OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() );
                    // TODO: Check & Get the namespaces in "Ignorable"
                    // printf("NS: %d : %s\n", attrs.getLength(), OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() );
                }
            }
            return false;
    }
    return true;
}

// com.sun.star.xml.sax.XFastContextHandler interface -------------------------

Reference< XFastContextHandler > SAL_CALL FragmentHandler2::createFastChildContext(
@@ -151,7 +85,7 @@ void SAL_CALL FragmentHandler2::endFastElement( sal_Int32 nElement )
    switch( nElement )
    {
        case MCE_TOKEN( AlternateContent ):
            aMceState.pop_back();
            removeMCEState();
            break;
    }

diff --git a/oox/source/drawingml/graphicshapecontext.cxx b/oox/source/drawingml/graphicshapecontext.cxx
index 113e549..3ed00ed 100644
--- a/oox/source/drawingml/graphicshapecontext.cxx
+++ b/oox/source/drawingml/graphicshapecontext.cxx
@@ -214,6 +214,7 @@ ContextHandlerRef OleObjectGraphicDataContext::onCreateContext( sal_Int32 nEleme
            mrOleObjectInfo.maName = rAttribs.getXString( XML_name, OUString() );
            mrOleObjectInfo.maProgId = rAttribs.getXString( XML_progId, OUString() );
            mrOleObjectInfo.mbShowAsIcon = rAttribs.getBool( XML_showAsIcon, false );
            mrOleObjectInfo.mbHasPicture = false; // Initialize as false
            return this;
        }
        break;
@@ -227,6 +228,7 @@ ContextHandlerRef OleObjectGraphicDataContext::onCreateContext( sal_Int32 nEleme
            mrOleObjectInfo.mbAutoUpdate = rAttribs.getBool( XML_updateAutomatic, false );
        break;
        case PPT_TOKEN( pic ):
            mrOleObjectInfo.mbHasPicture = true; // Set true if ole object has picture element.
            return new GraphicShapeContext( *this, mpMasterShapePtr, mpShapePtr );
    }
    SAL_WARN("oox", "OleObjectGraphicDataContext::onCreateContext: unhandled element: "
@@ -234,6 +236,15 @@ ContextHandlerRef OleObjectGraphicDataContext::onCreateContext( sal_Int32 nEleme
    return nullptr;
}

void OleObjectGraphicDataContext::onEndElement()
{
    if( getCurrentElement() == PPT_TOKEN( oleObj ) && !isMCEStateEmpty() )
    {
        if( getMCEState() == MCE_STATE::FoundChoice && !mrOleObjectInfo.mbHasPicture )
            setMCEState( MCE_STATE::Started );
    }
}

DiagramGraphicDataContext::DiagramGraphicDataContext( ContextHandler2Helper const & rParent, const ShapePtr& pShapePtr )
: ShapeContext( rParent, ShapePtr(), pShapePtr )
{
diff --git a/sc/source/filter/oox/worksheetfragment.cxx b/sc/source/filter/oox/worksheetfragment.cxx
index 20e802d..a0e01c9 100644
--- a/sc/source/filter/oox/worksheetfragment.cxx
+++ b/sc/source/filter/oox/worksheetfragment.cxx
@@ -466,17 +466,17 @@ ContextHandlerRef WorksheetFragment::onCreateContext( sal_Int32 nElement, const 
        case XLS_TOKEN( oleObjects ):
            if ( getCurrentElement() == XLS_TOKEN( controls ) )
            {
                if( aMceState.empty() || aMceState.back() == MCE_STATE::Started )
                if( isMCEStateEmpty() || getMCEState() == MCE_STATE::Started )
                {
                    if ( getCurrentElement() == XLS_TOKEN( oleObjects ) ) importOleObject( rAttribs );
                    else
                        importControl( rAttribs );
                }
                else if ( !aMceState.empty() && aMceState.back() == MCE_STATE::FoundChoice )
                else if ( !isMCEStateEmpty() && getMCEState() == MCE_STATE::FoundChoice )
                {
                    // reset the handling within 'Choice'
                    // this will force attempted handling in Fallback
                    aMceState.back() = MCE_STATE::Started;
                    setMCEState( MCE_STATE::Started );
                }
            }
        break;
diff --git a/sd/qa/unit/data/pptx/tdf143222.pptx b/sd/qa/unit/data/pptx/tdf143222.pptx
new file mode 100644
index 0000000..63938d1
--- /dev/null
+++ b/sd/qa/unit/data/pptx/tdf143222.pptx
Binary files differ
diff --git a/sd/qa/unit/export-tests-ooxml3.cxx b/sd/qa/unit/export-tests-ooxml3.cxx
index 6632934..f773c36 100644
--- a/sd/qa/unit/export-tests-ooxml3.cxx
+++ b/sd/qa/unit/export-tests-ooxml3.cxx
@@ -22,6 +22,7 @@
#include <svx/svdotable.hxx>
#include <svx/xlineit0.hxx>
#include <svx/xlndsit.hxx>
#include <svx/svdoole2.hxx>
#include <rtl/ustring.hxx>

#include <com/sun/star/drawing/XDrawPage.hpp>
@@ -116,6 +117,7 @@ public:
    void testTdf125560_textDeflate();
    void testTdf125560_textInflateTop();
    void testTdf96061_textHighlight();
    void testTdf143222_embeddedWorksheet();
    void testTdf142235_TestPlaceholderTextAlignment();

    CPPUNIT_TEST_SUITE(SdOOXMLExportTest3);
@@ -185,6 +187,7 @@ public:
    CPPUNIT_TEST(testTdf125560_textDeflate);
    CPPUNIT_TEST(testTdf125560_textInflateTop);
    CPPUNIT_TEST(testTdf96061_textHighlight);
    CPPUNIT_TEST(testTdf143222_embeddedWorksheet);
    CPPUNIT_TEST(testTdf142235_TestPlaceholderTextAlignment);
    CPPUNIT_TEST_SUITE_END();

@@ -1743,6 +1746,33 @@ void SdOOXMLExportTest3::testTdf96061_textHighlight()
    CPPUNIT_ASSERT_EQUAL(sal_Int32(-1), aColor);
}

void SdOOXMLExportTest3::testTdf143222_embeddedWorksheet()
{
    // Check import of embedded worksheet in slide.
    ::sd::DrawDocShellRef xDocShRef
        = loadURL(m_directories.getURLFromSrc(u"sd/qa/unit/data/pptx/tdf143222.pptx"), PPTX);

    const SdrPage* pPage = GetPage(1, xDocShRef.get());
    const SdrOle2Obj* pOleObj = static_cast<SdrOle2Obj*>(pPage->GetObj(0));
    CPPUNIT_ASSERT_MESSAGE("no object", pOleObj != nullptr);

    // Without the fix we lost the graphic of ole object.
    const Graphic* pGraphic = pOleObj->GetGraphic();
    CPPUNIT_ASSERT_MESSAGE("no graphic", pGraphic != nullptr);

    // Check export of embedded worksheet in slide.
    xDocShRef = saveAndReload(xDocShRef.get(), PPTX);

    pPage = GetPage(1, xDocShRef.get());
    pOleObj = static_cast<SdrOle2Obj*>(pPage->GetObj(0));
    CPPUNIT_ASSERT_MESSAGE("no object after the export", pOleObj != nullptr);

    pGraphic = pOleObj->GetGraphic();
    CPPUNIT_ASSERT_MESSAGE("no graphic after the export", pGraphic != nullptr);

    xDocShRef->DoClose();
}

CPPUNIT_TEST_SUITE_REGISTRATION(SdOOXMLExportTest3);

CPPUNIT_PLUGIN_IMPLEMENT();