From 2a51dd6aca55a92d8751b67cdf7a5a6ab8c38237 Mon Sep 17 00:00:00 2001 From: Justin Luth Date: Fri, 3 Jul 2015 20:45:16 +0300 Subject: tdf#87348 enable docx exporting linked textboxes that LO can import Change-Id: I1f663e1a463919fc0662c94e03b801c7c58f1f5d Reviewed-on: https://gerrit.libreoffice.org/16745 Tested-by: Jenkins Reviewed-by: Miklos Vajna --- 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 +++++ 5 files changed, 149 insertions(+), 28 deletions(-) diff --git a/sw/source/filter/ww8/docxexport.cxx b/sw/source/filter/ww8/docxexport.cxx index 855be5730b10..58d9d0b61227 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 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 13c7bd0ab1e4..c6207a509bb7 100644 --- a/sw/source/filter/ww8/docxsdrexport.cxx +++ b/sw/source/filter/ww8/docxsdrexport.cxx @@ -144,8 +144,6 @@ struct DocxSdrExport::Impl sax_fastparser::FastAttributeList* m_pFlyWrapAttrList; sax_fastparser::FastAttributeList* m_pBodyPrAttrList; std::unique_ptr 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 m_aTextBoxes; @@ -167,8 +165,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) @@ -1476,48 +1472,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::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::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 a3779e4f08e3..ac7d9c31c027 100644 --- a/sw/source/filter/ww8/wrtw8nds.cxx +++ b/sw/source/filter/ww8/wrtw8nds.cxx @@ -562,6 +562,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(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::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 01a95cf8f837..bbf819a898c1 100644 --- a/sw/source/filter/ww8/wrtww8.cxx +++ b/sw/source/filter/ww8/wrtww8.cxx @@ -1728,6 +1728,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 957ab283dbd0..d2cb89d53144 100644 --- a/sw/source/filter/ww8/wrtww8.hxx +++ b/sw/source/filter/ww8/wrtww8.hxx @@ -497,6 +497,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 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 -- cgit v1.2.3