fdo#78333 : SdtContent and a Shape overlapping causes corruption

- Normally if there is a case where text/shape is overlapped with (another)
   shape then LO used to write the text and the AlternateContent in the same run.
- This is supported in MSO and there is no visual difference.
- But in case if the SdtContent(with text) is overlapped with the Shape then LO
   processes sdtContent as a text and ends up putting the alternateContent and the
   text in a single run. Ultimately it includes the entire run in a SdtContent,
   which is incorrect.
- The fix checks for the aforementioned scenario and puts them in a different run
   and also restricts the sdtContent being written in an invalid AlternateContent.

Change-Id: I36f4cdb1b583523dd8f717ae094bdf09c7a61f62
Reviewed-on: https://gerrit.libreoffice.org/9374
Reviewed-by: Miklos Vajna <vmiklos@collabora.co.uk>
Tested-by: Miklos Vajna <vmiklos@collabora.co.uk>
diff --git a/sw/qa/extras/ooxmlexport/data/ShapeOverlappingWithSdt.docx b/sw/qa/extras/ooxmlexport/data/ShapeOverlappingWithSdt.docx
new file mode 100644
index 0000000..e1ec074
--- /dev/null
+++ b/sw/qa/extras/ooxmlexport/data/ShapeOverlappingWithSdt.docx
Binary files differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport.cxx
index 7a69e67..16d13a2 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport.cxx
@@ -2052,6 +2052,15 @@ DECLARE_OOXMLEXPORT_TEST(testFileOpenInputOutputError,"floatingtbl_with_formula.
      assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:pPr/w:pStyle", "val", "Normal");
}

DECLARE_OOXMLEXPORT_TEST(testSdtAndShapeOverlapping,"ShapeOverlappingWithSdt.docx")
{
     xmlDocPtr pXmlDoc = parseExport("word/document.xml");
     if (!pXmlDoc)
         return;
      assertXPath(pXmlDoc, "/w:document/w:body/w:p/w:r[1]/mc:AlternateContent");
      assertXPath(pXmlDoc, "/w:document/w:body/w:p/w:sdt[1]/w:sdtContent[1]/w:r[1]/w:t[1]");
}

DECLARE_OOXMLEXPORT_TEST(testRelorientation, "relorientation.docx")
{
    uno::Reference<drawing::XShape> xShape = getShape(1);
diff --git a/sw/source/filter/ww8/attributeoutputbase.hxx b/sw/source/filter/ww8/attributeoutputbase.hxx
index 5a25c86..d2df9c0 100644
--- a/sw/source/filter/ww8/attributeoutputbase.hxx
+++ b/sw/source/filter/ww8/attributeoutputbase.hxx
@@ -309,6 +309,15 @@ public:
    /// Has different headers/footers for the title page.
    virtual void SectionTitlePage() = 0;

    /// If the node has an anchor linked.
    virtual void SetAnchorIsLinkedToNode( bool /*bAnchorLinkedToNode*/){};

    /// Is processing of fly postponed ?
    virtual bool IsFlyProcessingPostponed(){ return false; };

    /// Reset the flag for FlyProcessing
    virtual void ResetFlyProcessingFlag(){};

    /// Description of the page borders.
    virtual void SectionPageBorders( const SwFrmFmt* pFmt, const SwFrmFmt* pFirstPageFmt ) = 0;

diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx
index 96602c3..fb5933d 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -287,6 +287,20 @@ void DocxAttributeOutput::StartParagraph( ww8::WW8TableNodeInfo::Pointer_t pText
    m_bIsFirstParagraph = false;
}

static void lcl_deleteAndResetTheLists( ::sax_fastparser::FastAttributeList* &pSdtPrTokenChildren, ::sax_fastparser::FastAttributeList* &pSdtPrDataBindingAttrs)
{
    if( pSdtPrTokenChildren )
    {
        delete pSdtPrTokenChildren ;
        pSdtPrTokenChildren = NULL;
    }
    if( pSdtPrDataBindingAttrs )
    {
        delete pSdtPrDataBindingAttrs;
        pSdtPrDataBindingAttrs = NULL;
    }
}

void DocxAttributeOutput::EndParagraph( ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner )
{
    // write the paragraph properties + the run, already in the correct order
@@ -382,8 +396,16 @@ void DocxAttributeOutput::EndParagraph( ww8::WW8TableNodeInfoInner::Pointer_t pT
    }

    m_pSerializer->endElementNS( XML_w, XML_p );
    if( !m_bAnchorLinkedToNode )
        WriteSdtBlock( m_nParagraphSdtPrToken, m_pParagraphSdtPrTokenChildren, m_pParagraphSdtPrDataBindingAttrs );
    else
    {
        //These should be written out to the actual Node and not to the anchor.
        //Clear them as they will be repopulated when the node is processed.
        m_nParagraphSdtPrToken = 0;
        lcl_deleteAndResetTheLists( m_pParagraphSdtPrTokenChildren, m_pParagraphSdtPrDataBindingAttrs );
    }

    WriteSdtBlock( m_nParagraphSdtPrToken, m_pParagraphSdtPrTokenChildren, m_pParagraphSdtPrDataBindingAttrs );
    //sdtcontent is written so Set m_bParagraphHasDrawing to false
    m_rExport.SdrExporter().setParagraphHasDrawing( false );
    m_bRunTextIsOn = false;
@@ -701,6 +723,21 @@ void DocxAttributeOutput::EndParagraphProperties( const SfxItemSet* pParagraphMa
    m_pSerializer->mergeTopMarks( sax_fastparser::MERGE_MARKS_PREPEND );
}

void DocxAttributeOutput::SetAnchorIsLinkedToNode( bool bAnchorLinkedToNode )
{
    m_bAnchorLinkedToNode = bAnchorLinkedToNode ;
}

void DocxAttributeOutput::ResetFlyProcessingFlag()
{
    m_bPostponedProcessingFly = false ;
}

bool DocxAttributeOutput::IsFlyProcessingPostponed()
{
    return m_bPostponedProcessingFly;
}

void DocxAttributeOutput::StartRun( const SwRedlineData* pRedlineData, bool /*bSingleEmptyRun*/ )
{
    // Don't start redline data here, possibly there is a hyperlink later, and
@@ -858,7 +895,15 @@ void DocxAttributeOutput::EndRun()
    EndRedline( m_pRedlineData );

    // enclose in a sdt block, if necessary
    WriteSdtBlock( m_nRunSdtPrToken, m_pRunSdtPrTokenChildren, m_pRunSdtPrDataBindingAttrs );
    if ( !m_bAnchorLinkedToNode )
        WriteSdtBlock( m_nRunSdtPrToken, m_pRunSdtPrTokenChildren, m_pRunSdtPrDataBindingAttrs );
    else
    {
        //These should be written out to the actual Node and not to the anchor.
        //Clear them as they will be repopulated when the node is processed.
        m_nRunSdtPrToken = 0;
        lcl_deleteAndResetTheLists( m_pRunSdtPrTokenChildren, m_pRunSdtPrDataBindingAttrs );
    }
    m_pSerializer->mergeTopMarks();

    WritePostponedMath();
@@ -4270,9 +4315,13 @@ void DocxAttributeOutput::OutputFlyFrame_Impl( const sw::Frame &rFrame, const Po
                if ( pGrfNode )
                {
                    if( m_postponedGraphic == NULL )
                    {
                        m_bPostponedProcessingFly = false ;
                        FlyFrameGraphic( pGrfNode, rFrame.GetLayoutSize(), 0, 0, pSdrObj);
                    }
                    else // we are writing out attributes, but w:drawing should not be inside w:rPr,
                    {    // so write it out later
                        m_bPostponedProcessingFly = true ;
                        m_postponedGraphic->push_back( PostponedGraphic( pGrfNode, rFrame.GetLayoutSize(), 0, 0, pSdrObj));
                    }
                }
@@ -4286,9 +4335,13 @@ void DocxAttributeOutput::OutputFlyFrame_Impl( const sw::Frame &rFrame, const Po
                    if ( IsDiagram( pSdrObj ) )
                    {
                        if ( m_postponedDiagram == NULL )
                        {
                            m_bPostponedProcessingFly = false ;
                            m_rExport.SdrExporter().writeDiagram( pSdrObj, rFrame.GetFrmFmt(), m_anchorId++);
                        }
                        else // we are writing out attributes, but w:drawing should not be inside w:rPr,
                        {    // so write it out later
                            m_bPostponedProcessingFly = true ;
                            m_postponedDiagram->push_back( PostponedDiagram( pSdrObj, &(rFrame.GetFrmFmt()) ));
                        }
                    }
@@ -4302,6 +4355,8 @@ void DocxAttributeOutput::OutputFlyFrame_Impl( const sw::Frame &rFrame, const Po
                                m_rExport.SdrExporter().writeDMLDrawing( pSdrObj, &rFrame.GetFrmFmt(), m_anchorId++);
                            else
                                m_rExport.SdrExporter().writeDMLAndVMLDrawing( pSdrObj, rFrame.GetFrmFmt(), rNdTopLeft, m_anchorId++);

                            m_bPostponedProcessingFly = false ;
                        }
                        // IsAlternateContentChoiceOpen() : check is to ensure that only one object is getting added. Without this check, plus one obejct gets added
                        // m_bParagraphFrameOpen : Check if the frame is open.
@@ -4311,8 +4366,11 @@ void DocxAttributeOutput::OutputFlyFrame_Impl( const sw::Frame &rFrame, const Po
                            m_postponedCustomShape->push_back(PostponedDrawing(pSdrObj, &(rFrame.GetFrmFmt()), &rNdTopLeft));
                        }
                        else
                        {
                            // we are writing out attributes, but w:drawing should not be inside w:rPr, so write it out later
                            m_bPostponedProcessingFly = true ;
                            m_postponedDMLDrawing->push_back(PostponedDrawing(pSdrObj, &(rFrame.GetFrmFmt()), &rNdTopLeft));
                        }
                    }
                }
            }
@@ -4335,7 +4393,10 @@ void DocxAttributeOutput::OutputFlyFrame_Impl( const sw::Frame &rFrame, const Po
                }

                if( !bDuplicate )
                {
                    m_bPostponedProcessingFly = true ;
                    m_aFramesOfParagraph.push_back(sw::Frame(rFrame));
                }
            }
            break;
        case sw::Frame::eOle:
@@ -4347,6 +4408,7 @@ void DocxAttributeOutput::OutputFlyFrame_Impl( const sw::Frame &rFrame, const Po
                    SwNodeIndex aIdx(*rFrmFmt.GetCntnt().GetCntntIdx(), 1);
                    SwOLENode& rOLENd = *aIdx.GetNode().GetOLENode();
                    WriteOLE2Obj( pSdrObj, rOLENd, rFrame.GetLayoutSize(), dynamic_cast<const SwFlyFrmFmt*>( &rFrmFmt ));
                    m_bPostponedProcessingFly = false ;
                }
            }
            break;
@@ -4354,6 +4416,7 @@ void DocxAttributeOutput::OutputFlyFrame_Impl( const sw::Frame &rFrame, const Po
            {
                const SdrObject* pObject = rFrame.GetFrmFmt().FindRealSdrObject();
                m_aPostponedFormControls.push_back(pObject);
                m_bPostponedProcessingFly = true ;
            }
            break;
        default:
@@ -7432,6 +7495,7 @@ DocxAttributeOutput::DocxAttributeOutput( DocxExport &rExport, FSHelperPtr pSeri
      m_bOpenedSectPr( false ),
      m_bRunTextIsOn( false ),
      m_bWritingHeaderFooter( false ),
      m_bAnchorLinkedToNode(false),
      m_sFieldBkm( ),
      m_nNextBookmarkId( 0 ),
      m_nNextAnnotationMarkId( 0 ),
@@ -7440,6 +7504,7 @@ DocxAttributeOutput::DocxAttributeOutput( DocxExport &rExport, FSHelperPtr pSeri
      m_bParagraphFrameOpen( false ),
      m_bIsFirstParagraph( true ),
      m_bAlternateContentChoiceOpen( false ),
      m_bPostponedProcessingFly( false ),
      m_nColBreakStatus( COLBRK_NONE ),
      m_nTextFrameLevel( 0 ),
      m_closeHyperlinkInThisRun( false ),
diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx b/sw/source/filter/ww8/docxattributeoutput.hxx
index 2c0472d..ea609a6 100644
--- a/sw/source/filter/ww8/docxattributeoutput.hxx
+++ b/sw/source/filter/ww8/docxattributeoutput.hxx
@@ -210,6 +210,10 @@ public:
    /// End of the tag that encloses the run.
    void EndRedline( const SwRedlineData * pRedlineData );

    virtual void SetAnchorIsLinkedToNode( bool bAnchorLinkedToNode = false ) SAL_OVERRIDE;
    virtual bool IsFlyProcessingPostponed() SAL_OVERRIDE;
    virtual void ResetFlyProcessingFlag() SAL_OVERRIDE;

    virtual void FormatDrop( const SwTxtNode& rNode, const SwFmtDrop& rSwFmtDrop, sal_uInt16 nStyle, ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo, ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner ) SAL_OVERRIDE;

    /// Output style.
@@ -726,6 +730,7 @@ private:

    /// Flag indicating that the header \ footer are being written
    bool m_bWritingHeaderFooter;
    bool m_bAnchorLinkedToNode;

    /// Field data to remember in the text run
    std::vector< FieldInfos > m_Fields;
@@ -767,6 +772,7 @@ private:
    bool m_bParagraphFrameOpen;
    bool m_bIsFirstParagraph;
    bool m_bAlternateContentChoiceOpen;
    bool m_bPostponedProcessingFly;

    // Remember that a column break has to be opened at the
    // beginning of the next paragraph
diff --git a/sw/source/filter/ww8/wrtw8nds.cxx b/sw/source/filter/ww8/wrtw8nds.cxx
index 1601997..e176259 100644
--- a/sw/source/filter/ww8/wrtw8nds.cxx
+++ b/sw/source/filter/ww8/wrtw8nds.cxx
@@ -540,7 +540,26 @@ bool SwWW8AttrIter::IsWatermarkFrame()
    return false;
}

void SwWW8AttrIter::OutFlys(sal_Int32 nSwPos)
bool SwWW8AttrIter::IsAnchorLinkedToThisNode( sal_uLong nNodePos )
{
    sw::FrameIter aTmpFlyIter = maFlyIter ;

    while ( aTmpFlyIter != maFlyFrms.end() )
    {
        const SwPosition &rAnchor  = maFlyIter->GetPosition();
        sal_uLong nAnchorPos = rAnchor.nNode.GetIndex();
        /* if current node position and the anchor position are the same
           then the frame anchor is linked to this node
        */
        if ( nAnchorPos == nNodePos )
            return true ;

        ++aTmpFlyIter;
    }
    return false ;
}

sal_Int16 SwWW8AttrIter::OutFlys(sal_Int32 nSwPos)
{
    /*
     #i2916#
@@ -553,7 +572,7 @@ void SwWW8AttrIter::OutFlys(sal_Int32 nSwPos)
        const sal_Int32 nPos = rAnchor.nContent.GetIndex();

        if ( nPos != nSwPos )
            break;
            return FLY_NOT_PROCESSED ; //We havent processed the fly

        const SdrObject* pSdrObj = maFlyIter->GetFrmFmt().FindRealSdrObject();

@@ -585,6 +604,7 @@ void SwWW8AttrIter::OutFlys(sal_Int32 nSwPos)
        }
        ++maFlyIter;
    }
    return ( m_rExport.AttrOutput().IsFlyProcessingPostponed() ? FLY_POSTPONED : FLY_PROCESSED ) ;
}

bool SwWW8AttrIter::IsTxtAttr( sal_Int32 nSwPos )
@@ -2017,6 +2037,7 @@ void MSWordExportBase::OutputTextNode( const SwTxtNode& rNode )
    sal_Int32 const nEnd = aStr.getLength();
    bool bRedlineAtEnd = false;
    sal_Int32 nOpenAttrWithRange = 0;
    OUString aStringForImage("\001");

    ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner;
    if ( pTextNodeInfo.get() != NULL )
@@ -2024,18 +2045,33 @@ void MSWordExportBase::OutputTextNode( const SwTxtNode& rNode )

    do {
        const SwRedlineData* pRedlineData = aAttrIter.GetRunLevelRedline( nAktPos );
        sal_Int16 nStateOfFlyFrame   = 0;
        bool bPostponeWritingText    = false ;
        OUString aSavedSnippet ;

        sal_Int32 nNextAttr = GetNextPos( &aAttrIter, rNode, nAktPos );
        // Is this the only run in this paragraph and it's empty?
        bool bSingleEmptyRun = nAktPos == 0 && nNextAttr == 0;
        AttrOutput().StartRun( pRedlineData, bSingleEmptyRun );

        if( nTxtTyp == TXT_FTN || nTxtTyp == TXT_EDN )
            AttrOutput().FootnoteEndnoteRefTag();

        if( nNextAttr > nEnd )
            nNextAttr = nEnd;

        aAttrIter.OutFlys( nAktPos );
        /*
            1) If there is a text node and an overlapping anchor, then write them in two different
            runs and not as part of the same run.
            2) Ensure that it is a text node and not in a fly.
            3) If the anchor is associated with a text node with empty text then we ignore.
        */
        if ( rNode.IsTxtNode() && aStr != aStringForImage && aStr != ""  &&
            !rNode.GetFlyFmt() && aAttrIter.IsAnchorLinkedToThisNode(rNode.GetIndex()))
            bPostponeWritingText = true ;

        nStateOfFlyFrame = aAttrIter.OutFlys( nAktPos );
        AttrOutput().SetAnchorIsLinkedToNode( bPostponeWritingText && (FLY_POSTPONED != nStateOfFlyFrame) );
        // Append bookmarks in this range after flys, exclusive of final
        // position of this range
        AppendBookmarks( rNode, nAktPos, nNextAttr - nAktPos );
@@ -2152,7 +2188,17 @@ void MSWordExportBase::OutputTextNode( const SwTxtNode& rNode )
                if ( aSnippet[0] != 0x09 )
                    aSnippet = OUString( 0x09 ) + aSnippet;
            }
            AttrOutput().RunText( aSnippet, eChrSet );

            if ( bPostponeWritingText && ( FLY_POSTPONED != nStateOfFlyFrame ) )
            {
                bPostponeWritingText = true ;
                aSavedSnippet = aSnippet ;
            }
            else
            {
                bPostponeWritingText = false ;
                AttrOutput().RunText( aSnippet, eChrSet );
            }
        }

        if ( aAttrIter.IsDropCap( nNextAttr ) )
@@ -2179,7 +2225,7 @@ void MSWordExportBase::OutputTextNode( const SwTxtNode& rNode )
                else
                {
                    // insert final graphic anchors if any before CR
                    aAttrIter.OutFlys( nEnd );
                    nStateOfFlyFrame = aAttrIter.OutFlys( nEnd );
                    // insert final bookmarks if any before CR and after flys
                    AppendBookmarks( rNode, nEnd, 1 );
                    AppendAnnotationMarks( rNode, nEnd, 1 );
@@ -2228,7 +2274,7 @@ void MSWordExportBase::OutputTextNode( const SwTxtNode& rNode )
            if ( bTxtAtr || bAttrWithRange || bRedlineAtEnd )
            {
                // insert final graphic anchors if any before CR
                aAttrIter.OutFlys( nEnd );
                nStateOfFlyFrame = aAttrIter.OutFlys( nEnd );
                // insert final bookmarks if any before CR and after flys
                AppendBookmarks( rNode, nEnd, 1 );
                AppendAnnotationMarks( rNode, nEnd, 1 );
@@ -2256,7 +2302,25 @@ void MSWordExportBase::OutputTextNode( const SwTxtNode& rNode )

        AttrOutput().WritePostitFieldReference();

        AttrOutput().EndRun();
        if( bPostponeWritingText && FLY_PROCESSED == nStateOfFlyFrame )
        {
            AttrOutput().EndRun();
            //write the postponed text run
            bPostponeWritingText = false ;
            AttrOutput().StartRun( pRedlineData, bSingleEmptyRun );
            AttrOutput().SetAnchorIsLinkedToNode( false );
            AttrOutput().ResetFlyProcessingFlag();
            if (0 != nEnd)
            {
                AttrOutput().StartRunProperties();
                aAttrIter.OutAttr( nAktPos );
                AttrOutput().EndRunProperties( pRedlineData );
            }
            AttrOutput().RunText( aSavedSnippet, eChrSet );
            AttrOutput().EndRun();
        }
        else
            AttrOutput().EndRun();

        nAktPos = nNextAttr;
        UpdatePosition( &aAttrIter, nAktPos, nEnd );
diff --git a/sw/source/filter/ww8/wrtww8.hxx b/sw/source/filter/ww8/wrtww8.hxx
index 9229fa5..36b260f 100644
--- a/sw/source/filter/ww8/wrtww8.hxx
+++ b/sw/source/filter/ww8/wrtww8.hxx
@@ -145,6 +145,16 @@ enum TxtTypes  //enums for TextTypes
    TXT_EDN = 4, TXT_ATN = 5, TXT_TXTBOX = 6, TXT_HFTXTBOX= 7
};

/**
enum to state the present state of the fly
*/
enum FlyProcessingState
{
    FLY_PROCESSED,
    FLY_POSTPONED,
    FLY_NOT_PROCESSED
};

struct WW8_SepInfo
{
    const SwPageDesc* pPageDesc;
@@ -1533,7 +1543,7 @@ public:
    int OutAttrWithRange(sal_Int32 nPos);
    const SwRedlineData* GetParagraphLevelRedline( );
    const SwRedlineData* GetRunLevelRedline( sal_Int32 nPos );
    void OutFlys(sal_Int32 nSwPos);
    sal_Int16 OutFlys(sal_Int32 nSwPos);

    sal_Int32 WhereNext() const { return nAktSwPos; }
    sal_uInt16 GetScript() const { return mnScript; }
@@ -1545,6 +1555,7 @@ public:
    const SwFmtDrop& GetSwFmtDrop() const { return mrSwFmtDrop; }

    bool IsWatermarkFrame();
    bool IsAnchorLinkedToThisNode( sal_uLong nNodePos );
};

/// Class to collect and output the styles table.