summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Luth <justin_luth@sil.org>2015-06-24 23:02:57 +0300
committerMiklos Vajna <vmiklos@collabora.co.uk>2015-07-03 16:40:52 +0000
commit4802bf32b84655f6e1d3389070c76371ede8fbce (patch)
tree72d4bb14ffbb440e710a6e332b3a8896455437d2
parente83cb37cf7546e8bc46d0d49b487dcd352b67093 (diff)
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@libreoffice.org> Reviewed-by: Miklos Vajna <vmiklos@collabora.co.uk>
-rw-r--r--sw/qa/extras/ooxmlexport/data/tdf87348_linkedTextboxes.docxbin0 -> 18697 bytes
-rw-r--r--sw/qa/extras/ooxmlexport/ooxmlexport7.cxx64
-rw-r--r--sw/source/filter/ww8/docxexport.cxx8
-rw-r--r--sw/source/filter/ww8/docxsdrexport.cxx91
-rw-r--r--sw/source/filter/ww8/wrtw8nds.cxx63
-rw-r--r--sw/source/filter/ww8/wrtww8.cxx3
-rw-r--r--sw/source/filter/ww8/wrtww8.hxx12
7 files changed, 213 insertions, 28 deletions
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 000000000000..8f9eb97d83f4
--- /dev/null
+++ b/sw/qa/extras/ooxmlexport/data/tdf87348_linkedTextboxes.docx
Binary files differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport7.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport7.cxx
index ac7ba3c6bb3f..6354f422d9e1 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 e91bea85b15f..de23a27ee345 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 44d9ebb831fe..b59de527e7b0 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 608d2b377d18..1cc28037c404 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 564381d5ced2..a5efd2eaedb3 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 8691bfa291f0..34f04129be47 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