Kaydet (Commit) 4802bf32 authored tarafından Justin Luth's avatar Justin Luth Kaydeden (comit) Miklos Vajna

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/16516Tested-by: 's avatarJenkins <ci@libreoffice.org>
Reviewed-by: 's avatarMiklos Vajna <vmiklos@collabora.co.uk>
üst e83cb37c
...@@ -999,6 +999,70 @@ DECLARE_OOXMLEXPORT_TEST(testExportAdjustmentValue, "tdf91429.docx") ...@@ -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"); 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 #endif
CPPUNIT_PLUGIN_IMPLEMENT(); CPPUNIT_PLUGIN_IMPLEMENT();
......
...@@ -453,6 +453,7 @@ void DocxExport::ExportDocument_Impl() ...@@ -453,6 +453,7 @@ void DocxExport::ExportDocument_Impl()
WriteEmbeddings(); WriteEmbeddings();
m_aLinkedTextboxesHelper.clear(); //final cleanup
delete m_pStyles, m_pStyles = NULL; delete m_pStyles, m_pStyles = NULL;
delete m_pSections, m_pSections = NULL; delete m_pSections, m_pSections = NULL;
} }
...@@ -1314,6 +1315,10 @@ void DocxExport::WriteMainText() ...@@ -1314,6 +1315,10 @@ void DocxExport::WriteMainText()
// setup the namespaces // setup the namespaces
m_pDocumentFS->startElementNS( XML_w, XML_document, MainXmlNamespaces()); 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 // Write background page color
if (boost::optional<SvxBrushItem> oBrush = getBackground()) if (boost::optional<SvxBrushItem> oBrush = getBackground())
{ {
...@@ -1331,6 +1336,9 @@ void DocxExport::WriteMainText() ...@@ -1331,6 +1336,9 @@ void DocxExport::WriteMainText()
// the text // the text
WriteText(); 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 // the last section info
m_pAttrOutput->EndParaSdtBlock(); m_pAttrOutput->EndParaSdtBlock();
const WW8_SepInfo *pSectionInfo = m_pSections? m_pSections->CurrentSectionInfo(): NULL; const WW8_SepInfo *pSectionInfo = m_pSections? m_pSections->CurrentSectionInfo(): NULL;
......
...@@ -143,8 +143,6 @@ struct DocxSdrExport::Impl ...@@ -143,8 +143,6 @@ struct DocxSdrExport::Impl
sax_fastparser::FastAttributeList* m_pFlyWrapAttrList; sax_fastparser::FastAttributeList* m_pFlyWrapAttrList;
sax_fastparser::FastAttributeList* m_pBodyPrAttrList; sax_fastparser::FastAttributeList* m_pBodyPrAttrList;
std::unique_ptr<sax_fastparser::FastAttributeList> m_pDashLineStyleAttr; std::unique_ptr<sax_fastparser::FastAttributeList> m_pDashLineStyleAttr;
sal_Int32 m_nId ;
sal_Int32 m_nSeq ;
bool m_bDMLAndVMLDrawingOpen; bool m_bDMLAndVMLDrawingOpen;
/// List of TextBoxes in this document: they are exported as part of their shape, never alone. /// List of TextBoxes in this document: they are exported as part of their shape, never alone.
std::set<const SwFrameFormat*> m_aTextBoxes; std::set<const SwFrameFormat*> m_aTextBoxes;
...@@ -166,8 +164,6 @@ struct DocxSdrExport::Impl ...@@ -166,8 +164,6 @@ struct DocxSdrExport::Impl
m_bFlyFrameGraphic(false), m_bFlyFrameGraphic(false),
m_pFlyWrapAttrList(0), m_pFlyWrapAttrList(0),
m_pBodyPrAttrList(0), m_pBodyPrAttrList(0),
m_nId(0),
m_nSeq(0),
m_bDMLAndVMLDrawingOpen(false), m_bDMLAndVMLDrawingOpen(false),
m_aTextBoxes(SwTextBoxHelper::findTextBoxes(m_rExport.m_pDoc)), m_aTextBoxes(SwTextBoxHelper::findTextBoxes(m_rExport.m_pDoc)),
m_nDMLandVMLTextFrameRotation(0) m_nDMLandVMLTextFrameRotation(0)
...@@ -1475,48 +1471,87 @@ void DocxSdrExport::writeDMLTextFrame(sw::Frame* pParentFrame, int nAnchorId, bo ...@@ -1475,48 +1471,87 @@ void DocxSdrExport::writeDMLTextFrame(sw::Frame* pParentFrame, int nAnchorId, bo
pFS->endElementNS(XML_wps, XML_spPr); 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; m_pImpl->m_rExport.m_pParentFrame = NULL;
bool skipTxBxContent = false ; bool skipTxBxContent = false ;
bool isTxbxLinked = false ; bool isTxbxLinked = false ;
/* Check if the text box is linked and then decides whether OUString sLinkChainName;
to write the tag txbx or linkedTxbx if ( xPropSetInfo.is() )
*/
if (xPropSetInfo.is() && xPropSetInfo->hasPropertyByName("ChainPrevName") &&
xPropSetInfo->hasPropertyByName("ChainNextName"))
{ {
OUString sChainPrevName; if ( xPropSetInfo->hasPropertyByName("LinkDisplayName") )
OUString sChainNextName; xPropertySet->getPropertyValue("LinkDisplayName") >>= sLinkChainName;
else if ( xPropSetInfo->hasPropertyByName("ChainName") )
xPropertySet->getPropertyValue("ChainPrevName") >>= sChainPrevName ; xPropertySet->getPropertyValue("ChainName") >>= sLinkChainName;
xPropertySet->getPropertyValue("ChainNextName") >>= sChainNextName ; }
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, /* no text content should be added to this tag,
since the textbox is linked, the entire content since the textbox is linked, the entire content
is written in txbx block 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 ; 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 /* this is the first textbox in the chaining, we add the text content
to this block*/ to this block*/
++m_pImpl->m_nId ;
//since the text box is linked, it needs an id. //since the text box is linked, it needs an id.
pFS->startElementNS(XML_wps, XML_txbx, pFS->startElementNS(XML_wps, XML_txbx,
XML_id, I32S(m_pImpl->m_nId), XML_id, I32S(linkedTextboxesIter->second.nId),
FSEND); FSEND);
isTxbxLinked = true ; isTxbxLinked = true ;
} }
......
...@@ -549,6 +549,69 @@ bool SwWW8AttrIter::IsAnchorLinkedToThisNode( sal_uLong nNodePos ) ...@@ -549,6 +549,69 @@ bool SwWW8AttrIter::IsAnchorLinkedToThisNode( sal_uLong nNodePos )
FlyProcessingState SwWW8AttrIter::OutFlys(sal_Int32 nSwPos) 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# #i2916#
May have an anchored graphic to be placed, loop through sorted array May have an anchored graphic to be placed, loop through sorted array
......
...@@ -1682,6 +1682,9 @@ void MSWordExportBase::WriteSpecialText( sal_uLong nStart, sal_uLong nEnd, sal_u ...@@ -1682,6 +1682,9 @@ void MSWordExportBase::WriteSpecialText( sal_uLong nStart, sal_uLong nEnd, sal_u
// bOutKF was setted / stored in WriteKF1 // bOutKF was setted / stored in WriteKF1
SetCurPam(nStart, nEnd); SetCurPam(nStart, nEnd);
// clear linked textboxes since old ones can't be linked to frames in this section
m_aLinkedTextboxesHelper.clear();
WriteText(); WriteText();
m_bOutPageDescs = bOldPageDescs; m_bOutPageDescs = bOldPageDescs;
......
...@@ -490,6 +490,18 @@ public: ...@@ -490,6 +490,18 @@ public:
WW8_WrPlcAnnotations* m_pAtn; WW8_WrPlcAnnotations* m_pAtn;
WW8_WrPlcTextBoxes *m_pTextBxs, *m_pHFTextBxs; 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 const sw::Frame *m_pParentFrame; // If set we are exporting content inside
// a frame, e.g. a graphic node // a frame, e.g. a graphic node
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment