[Libreoffice-commits] core.git: sw/qa sw/source

Justin Luth justin_luth at sil.org
Fri Jul 3 09:41:22 PDT 2015


 sw/qa/extras/ooxmlexport/data/tdf87348_linkedTextboxes.docx |binary
 sw/qa/extras/ooxmlexport/ooxmlexport7.cxx                   |   64 ++++++++
 sw/source/filter/ww8/docxexport.cxx                         |    8 +
 sw/source/filter/ww8/docxsdrexport.cxx                      |   91 ++++++++----
 sw/source/filter/ww8/wrtw8nds.cxx                           |   63 ++++++++
 sw/source/filter/ww8/wrtww8.cxx                             |    3 
 sw/source/filter/ww8/wrtww8.hxx                             |   12 +
 7 files changed, 213 insertions(+), 28 deletions(-)

New commits:
commit 4802bf32b84655f6e1d3389070c76371ede8fbce
Author: Justin Luth <justin_luth at sil.org>
Date:   Wed Jun 24 23:02:57 2015 +0300

    tdf#87348 enable docx exporting linked textboxes that LO can import
    
    Exporting linked textboxes to docx format is in terrible condition.
    Spacing, textboxes instead of frames, duplicate links and orphaned
    shapes lying around, not to mention being being unlinked in MSWord...
    This fix resolves this situation slightly: what LO saves can be
    re-imported and re-saved without breaking the links - an incremental
    improvement.
    
    Change-Id: I8f0aef39eeed88a2b3dfc673a565fb1d8f4713b0
    Reviewed-on: https://gerrit.libreoffice.org/16516
    Tested-by: Jenkins <ci at libreoffice.org>
    Reviewed-by: Miklos Vajna <vmiklos at collabora.co.uk>

diff --git a/sw/qa/extras/ooxmlexport/data/tdf87348_linkedTextboxes.docx b/sw/qa/extras/ooxmlexport/data/tdf87348_linkedTextboxes.docx
new file mode 100644
index 0000000..8f9eb97
Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/tdf87348_linkedTextboxes.docx differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport7.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport7.cxx
index ac7ba3c..6354f42 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport7.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport7.cxx
@@ -999,6 +999,70 @@ DECLARE_OOXMLEXPORT_TEST(testExportAdjustmentValue, "tdf91429.docx")
 
     assertXPath(pXmlDoc,"/w:document/w:body/w:p/w:r[1]/mc:AlternateContent/mc:Choice/w:drawing/wp:anchor/a:graphic/a:graphicData/wps:wsp/wps:spPr/a:prstGeom/a:avLst/a:gd", "fmla", "val 50000");
 }
+
+DECLARE_OOXMLEXPORT_TEST(testTDF87348, "tdf87348_linkedTextboxes.docx")
+{
+int followCount=0;
+int precedeCount=0;
+if( !parseDump("/root/page/body/txt/anchored/fly[1]/txt","follow").isEmpty() )
+  followCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[1]/txt","precede").isEmpty() )
+    precedeCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[2]/txt","follow").isEmpty() )
+  followCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[2]/txt","precede").isEmpty() )
+    precedeCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[3]/txt","follow").isEmpty() )
+  followCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[3]/txt","precede").isEmpty() )
+    precedeCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[4]/txt","follow").isEmpty() )
+  followCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[4]/txt","precede").isEmpty() )
+    precedeCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[5]/txt","follow").isEmpty() )
+  followCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[5]/txt","precede").isEmpty() )
+    precedeCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[6]/txt","follow").isEmpty() )
+  followCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[6]/txt","precede").isEmpty() )
+    precedeCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[7]/txt","follow").isEmpty() )
+  followCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[7]/txt","precede").isEmpty() )
+    precedeCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[8]/txt","follow").isEmpty() )
+  followCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[8]/txt","precede").isEmpty() )
+    precedeCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[9]/txt","follow").isEmpty() )
+  followCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[9]/txt","precede").isEmpty() )
+    precedeCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[10]/txt","follow").isEmpty() )
+  followCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[10]/txt","precede").isEmpty() )
+    precedeCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[11]/txt","follow").isEmpty() )
+  followCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[11]/txt","precede").isEmpty() )
+    precedeCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[12]/txt","follow").isEmpty() )
+  followCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[12]/txt","precede").isEmpty() )
+    precedeCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[13]/txt","follow").isEmpty() )
+  followCount++;
+if( !parseDump("/root/page/body/txt/anchored/fly[13]/txt","precede").isEmpty() )
+    precedeCount++;
+    //there should be 4 chains/13 linked textboxes (set of 5, set of 3, set of 3, set of 2)
+    //that means 9 NEXT links and 9 PREV links.
+    //however, the current implementation adds leftover shapes, so can't go on exact numbers
+    //  (unknown number of flys, unknown order of leftovers)
+    CPPUNIT_ASSERT ( (followCount >= 6) && (precedeCount >= 6) );
+}
+
 #endif
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sw/source/filter/ww8/docxexport.cxx b/sw/source/filter/ww8/docxexport.cxx
index e91bea8..de23a27 100644
--- a/sw/source/filter/ww8/docxexport.cxx
+++ b/sw/source/filter/ww8/docxexport.cxx
@@ -453,6 +453,7 @@ void DocxExport::ExportDocument_Impl()
 
     WriteEmbeddings();
 
+    m_aLinkedTextboxesHelper.clear();   //final cleanup
     delete m_pStyles, m_pStyles = NULL;
     delete m_pSections, m_pSections = NULL;
 }
@@ -1314,6 +1315,10 @@ void DocxExport::WriteMainText()
     // setup the namespaces
     m_pDocumentFS->startElementNS( XML_w, XML_document, MainXmlNamespaces());
 
+    // reset the incrementing linked-textboxes chain ID before re-saving.
+    m_nLinkedTextboxesChainId=0;
+    m_aLinkedTextboxesHelper.clear();
+
     // Write background page color
     if (boost::optional<SvxBrushItem> oBrush = getBackground())
     {
@@ -1331,6 +1336,9 @@ void DocxExport::WriteMainText()
     // the text
     WriteText();
 
+    // clear linked textboxes since old ones can't be linked to frames in a different section (correct?)
+    m_aLinkedTextboxesHelper.clear();
+
     // the last section info
     m_pAttrOutput->EndParaSdtBlock();
     const WW8_SepInfo *pSectionInfo = m_pSections? m_pSections->CurrentSectionInfo(): NULL;
diff --git a/sw/source/filter/ww8/docxsdrexport.cxx b/sw/source/filter/ww8/docxsdrexport.cxx
index 44d9ebb..b59de52 100644
--- a/sw/source/filter/ww8/docxsdrexport.cxx
+++ b/sw/source/filter/ww8/docxsdrexport.cxx
@@ -143,8 +143,6 @@ struct DocxSdrExport::Impl
     sax_fastparser::FastAttributeList* m_pFlyWrapAttrList;
     sax_fastparser::FastAttributeList* m_pBodyPrAttrList;
     std::unique_ptr<sax_fastparser::FastAttributeList> m_pDashLineStyleAttr;
-    sal_Int32 m_nId ;
-    sal_Int32 m_nSeq ;
     bool m_bDMLAndVMLDrawingOpen;
     /// List of TextBoxes in this document: they are exported as part of their shape, never alone.
     std::set<const SwFrameFormat*> m_aTextBoxes;
@@ -166,8 +164,6 @@ struct DocxSdrExport::Impl
           m_bFlyFrameGraphic(false),
           m_pFlyWrapAttrList(0),
           m_pBodyPrAttrList(0),
-          m_nId(0),
-          m_nSeq(0),
           m_bDMLAndVMLDrawingOpen(false),
           m_aTextBoxes(SwTextBoxHelper::findTextBoxes(m_rExport.m_pDoc)),
           m_nDMLandVMLTextFrameRotation(0)
@@ -1475,48 +1471,87 @@ void DocxSdrExport::writeDMLTextFrame(sw::Frame* pParentFrame, int nAnchorId, bo
         pFS->endElementNS(XML_wps, XML_spPr);
     }
 
+    //first, loop through ALL of the chained textboxes to identify a unique ID for each chain, and sequence number for each textbox in that chain.
+    std::map<OUString, MSWordExportBase::LinkedTextboxInfo>::iterator linkedTextboxesIter;
+    if( !m_pImpl->m_rExport.m_bLinkedTextboxesHelperInitialized )
+    {
+        sal_Int32 nSeq=0;
+        linkedTextboxesIter = m_pImpl->m_rExport.m_aLinkedTextboxesHelper.begin();
+        while ( linkedTextboxesIter != m_pImpl->m_rExport.m_aLinkedTextboxesHelper.end() )
+        {
+            //find the start of a textbox chain: has no PREVIOUS link, but does have NEXT link
+            if ( linkedTextboxesIter->second.sPrevChain.isEmpty() && !linkedTextboxesIter->second.sNextChain.isEmpty() )
+            {
+                //assign this chain a unique ID and start a new sequence
+                nSeq = 0;
+                linkedTextboxesIter->second.nId = ++m_pImpl->m_rExport.m_nLinkedTextboxesChainId;
+                linkedTextboxesIter->second.nSeq = nSeq;
+
+                OUString sCheckForBrokenChains = linkedTextboxesIter->first;
+
+                //follow the chain and assign the same id, and incremental sequence numbers.
+                std::map<OUString, MSWordExportBase::LinkedTextboxInfo>::iterator  followChainIter;
+                followChainIter = m_pImpl->m_rExport.m_aLinkedTextboxesHelper.find(linkedTextboxesIter->second.sNextChain);
+                while ( followChainIter != m_pImpl->m_rExport.m_aLinkedTextboxesHelper.end() )
+                {
+                    //verify that the NEXT textbox also points to me as the PREVIOUS.
+                    // A broken link indicates a leftover remnant that can be ignored.
+                    if( followChainIter->second.sPrevChain != sCheckForBrokenChains )
+                        break;
+
+                    followChainIter->second.nId = m_pImpl->m_rExport.m_nLinkedTextboxesChainId;
+                    followChainIter->second.nSeq = ++nSeq;
+
+                    //empty next chain indicates the end of the linked chain.
+                    if ( followChainIter->second.sNextChain.isEmpty() )
+                        break;
+
+                    sCheckForBrokenChains = followChainIter->first;
+                    followChainIter = m_pImpl->m_rExport.m_aLinkedTextboxesHelper.find(followChainIter->second.sNextChain);
+                }
+            }
+            ++linkedTextboxesIter;
+        }
+        m_pImpl->m_rExport.m_bLinkedTextboxesHelperInitialized = true;
+    }
+
     m_pImpl->m_rExport.m_pParentFrame = NULL;
     bool skipTxBxContent = false ;
     bool isTxbxLinked = false ;
 
-    /* Check if the text box is linked and then decides whether
-       to write the tag txbx or linkedTxbx
-    */
-    if (xPropSetInfo.is() && xPropSetInfo->hasPropertyByName("ChainPrevName") &&
-            xPropSetInfo->hasPropertyByName("ChainNextName"))
+    OUString sLinkChainName;
+    if ( xPropSetInfo.is() )
     {
-        OUString sChainPrevName;
-        OUString sChainNextName;
-
-        xPropertySet->getPropertyValue("ChainPrevName") >>= sChainPrevName ;
-        xPropertySet->getPropertyValue("ChainNextName") >>= sChainNextName ;
+        if ( xPropSetInfo->hasPropertyByName("LinkDisplayName") )
+            xPropertySet->getPropertyValue("LinkDisplayName") >>= sLinkChainName;
+        else if ( xPropSetInfo->hasPropertyByName("ChainName") )
+            xPropertySet->getPropertyValue("ChainName") >>= sLinkChainName;
+    }
 
-        if (!sChainPrevName.isEmpty())
+    // second, check if THIS textbox is linked and then decide whether to write the tag txbx or linkedTxbx
+    linkedTextboxesIter = m_pImpl->m_rExport.m_aLinkedTextboxesHelper.find(sLinkChainName);
+    if ( linkedTextboxesIter != m_pImpl->m_rExport.m_aLinkedTextboxesHelper.end() )
+    {
+        if( (linkedTextboxesIter->second.nId !=0) && (linkedTextboxesIter->second.nSeq != 0) )
         {
+            //not the first in the chain, so write the tag as linkedTxbx
+            pFS->singleElementNS(XML_wps, XML_linkedTxbx,
+                                 XML_id,  I32S(linkedTextboxesIter->second.nId),
+                                 XML_seq, I32S(linkedTextboxesIter->second.nSeq),
+                                 FSEND);
             /* no text content should be added to this tag,
                since the textbox is linked, the entire content
                is written in txbx block
             */
-            ++m_pImpl->m_nSeq ;
-            pFS->singleElementNS(XML_wps, XML_linkedTxbx,
-                                 XML_id,  I32S(m_pImpl->m_nId),
-                                 XML_seq, I32S(m_pImpl->m_nSeq),
-                                 FSEND);
             skipTxBxContent = true ;
-
-            //Text box chaining for a group of textboxes ends here,
-            //therefore reset the seq.
-            if (sChainNextName.isEmpty())
-                m_pImpl->m_nSeq = 0 ;
         }
-        else if (sChainPrevName.isEmpty() && !sChainNextName.isEmpty())
+        else if( (linkedTextboxesIter->second.nId != 0) && (linkedTextboxesIter->second.nSeq == 0) )
         {
             /* this is the first textbox in the chaining, we add the text content
                to this block*/
-            ++m_pImpl->m_nId ;
             //since the text box is linked, it needs an id.
             pFS->startElementNS(XML_wps, XML_txbx,
-                                XML_id, I32S(m_pImpl->m_nId),
+                                XML_id,  I32S(linkedTextboxesIter->second.nId),
                                 FSEND);
             isTxbxLinked = true ;
         }
diff --git a/sw/source/filter/ww8/wrtw8nds.cxx b/sw/source/filter/ww8/wrtw8nds.cxx
index 608d2b3..1cc2803 100644
--- a/sw/source/filter/ww8/wrtw8nds.cxx
+++ b/sw/source/filter/ww8/wrtw8nds.cxx
@@ -549,6 +549,69 @@ bool SwWW8AttrIter::IsAnchorLinkedToThisNode( sal_uLong nNodePos )
 
 FlyProcessingState SwWW8AttrIter::OutFlys(sal_Int32 nSwPos)
 {
+    // collection point to first gather info about all of the potentially linked textboxes: to be analyzed later.
+    OUString sLinkChainName;
+    sw::FrameIter linkedTextboxesIter = maFlyIter;
+    while ( linkedTextboxesIter != maFlyFrms.end() )
+    {
+        uno::Reference< drawing::XShape > xShape;
+        sw::Frame xFrame = *linkedTextboxesIter;
+        const SdrObject* pSdrObj = xFrame.GetFrameFormat().FindRealSdrObject();
+        if( pSdrObj )
+            xShape = uno::Reference< drawing::XShape >(const_cast<SdrObject*>(pSdrObj)->getUnoShape(), uno::UNO_QUERY);
+        uno::Reference< beans::XPropertySet > xPropertySet(xShape, uno::UNO_QUERY);
+        uno::Reference< beans::XPropertySetInfo > xPropertySetInfo;
+        if( xPropertySet.is() )
+            xPropertySetInfo = xPropertySet->getPropertySetInfo();
+        if( xPropertySetInfo.is() )
+        {
+            MSWordExportBase::LinkedTextboxInfo aLinkedTextboxInfo = MSWordExportBase::LinkedTextboxInfo();
+
+            if( xPropertySetInfo->hasPropertyByName("LinkDisplayName") )
+                xPropertySet->getPropertyValue("LinkDisplayName") >>= sLinkChainName;
+            else if( xPropertySetInfo->hasPropertyByName("ChainName") )
+                xPropertySet->getPropertyValue("ChainName") >>= sLinkChainName;
+
+            if( xPropertySetInfo->hasPropertyByName("ChainNextName") )
+                xPropertySet->getPropertyValue("ChainNextName") >>= aLinkedTextboxInfo.sNextChain;
+            if( xPropertySetInfo->hasPropertyByName("ChainPrevName") )
+                xPropertySet->getPropertyValue("ChainPrevName") >>= aLinkedTextboxInfo.sPrevChain;
+
+            //collect a list of linked textboxes: those with a NEXT or PREVIOUS link
+            if( !aLinkedTextboxInfo.sNextChain.isEmpty() || !aLinkedTextboxInfo.sPrevChain.isEmpty() )
+            {
+                assert( !sLinkChainName.isEmpty() );
+
+                //there are many discarded duplicates in documents - no duplicates allowed in the list, so try to find the real one.
+                //if this LinkDisplayName/ChainName already exists on a different shape...
+                //  the earlier processed duplicates are thrown out unless this one can be proved as bad. (last processed duplicate usually is stored)
+                std::map<OUString,MSWordExportBase::LinkedTextboxInfo>::iterator linkFinder;
+                linkFinder = m_rExport.m_aLinkedTextboxesHelper.find(sLinkChainName);
+                if( linkFinder != m_rExport.m_aLinkedTextboxesHelper.end() )
+                {
+                    //If my NEXT/PREV targets have already been discovered, but don't match me, then assume I'm an abandoned remnant
+                    //    (this logic fails if both me and one of my links are duplicated, and the remnants were added first.)
+                    linkFinder = m_rExport.m_aLinkedTextboxesHelper.find(aLinkedTextboxInfo.sNextChain);
+                    if( (linkFinder != m_rExport.m_aLinkedTextboxesHelper.end()) && (linkFinder->second.sPrevChain != sLinkChainName) )
+                    {
+                        ++linkedTextboxesIter;
+                        break;
+                    }
+
+                    linkFinder = m_rExport.m_aLinkedTextboxesHelper.find(aLinkedTextboxInfo.sPrevChain);
+                    if( (linkFinder != m_rExport.m_aLinkedTextboxesHelper.end()) && (linkFinder->second.sNextChain != sLinkChainName) )
+                    {
+                        ++linkedTextboxesIter;
+                        break;
+                    }
+                }
+                m_rExport.m_bLinkedTextboxesHelperInitialized = false;
+                m_rExport.m_aLinkedTextboxesHelper[sLinkChainName] = aLinkedTextboxInfo;
+            }
+        }
+        ++linkedTextboxesIter;
+    }
+
     /*
      #i2916#
      May have an anchored graphic to be placed, loop through sorted array
diff --git a/sw/source/filter/ww8/wrtww8.cxx b/sw/source/filter/ww8/wrtww8.cxx
index 564381d..a5efd2e 100644
--- a/sw/source/filter/ww8/wrtww8.cxx
+++ b/sw/source/filter/ww8/wrtww8.cxx
@@ -1682,6 +1682,9 @@ void MSWordExportBase::WriteSpecialText( sal_uLong nStart, sal_uLong nEnd, sal_u
                                     // bOutKF was setted / stored in WriteKF1
     SetCurPam(nStart, nEnd);
 
+    // clear linked textboxes since old ones can't be linked to frames in this section
+    m_aLinkedTextboxesHelper.clear();
+
     WriteText();
 
     m_bOutPageDescs = bOldPageDescs;
diff --git a/sw/source/filter/ww8/wrtww8.hxx b/sw/source/filter/ww8/wrtww8.hxx
index 8691bfa..34f0412 100644
--- a/sw/source/filter/ww8/wrtww8.hxx
+++ b/sw/source/filter/ww8/wrtww8.hxx
@@ -490,6 +490,18 @@ public:
     WW8_WrPlcAnnotations* m_pAtn;
     WW8_WrPlcTextBoxes *m_pTextBxs, *m_pHFTextBxs;
 
+    struct LinkedTextboxInfo        //help analyze textbox flow links
+    {
+        sal_Int32 nId;
+        sal_Int32 nSeq;
+        OUString sNextChain;
+        OUString sPrevChain;
+        LinkedTextboxInfo(): nId(0), nSeq(0) {}
+    };
+    std::map<OUString,LinkedTextboxInfo> m_aLinkedTextboxesHelper;
+    bool m_bLinkedTextboxesHelperInitialized = false;
+    sal_Int32 m_nLinkedTextboxesChainId=0;
+
     const sw::Frame *m_pParentFrame; // If set we are exporting content inside
                                     // a frame, e.g. a graphic node
 


More information about the Libreoffice-commits mailing list