/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include "docxattributeoutput.hxx" #include "docxhelper.hxx" #include "docxsdrexport.hxx" #include "docxexportfilter.hxx" #include "docxfootnotes.hxx" #include "writerwordglue.hxx" #include "ww8par.hxx" #include "fmtcntnt.hxx" #include "fmtftn.hxx" #include "fchrfmt.hxx" #include "tgrditem.hxx" #include "fmtruby.hxx" #include "fmtanchr.hxx" #include "breakit.hxx" #include "redline.hxx" #include "unocoll.hxx" #include "unoframe.hxx" #include "unodraw.hxx" #include "textboxhelper.hxx" #include "rdfhelper.hxx" #include "wrtww8.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using ::editeng::SvxBorderLine; using namespace oox; using namespace docx; using namespace sax_fastparser; using namespace nsSwDocInfoSubType; using namespace sw::util; using namespace ::com::sun::star; using namespace ::com::sun::star::drawing; static const sal_Int32 Tag_StartParagraph_1 = 1; static const sal_Int32 Tag_StartParagraph_2 = 2; static const sal_Int32 Tag_WriteSdtBlock = 3; static const sal_Int32 Tag_StartParagraphProperties = 4; static const sal_Int32 Tag_InitCollectedParagraphProperties = 5; static const sal_Int32 Tag_StartRun_1 = 6; static const sal_Int32 Tag_StartRun_2 = 7; static const sal_Int32 Tag_StartRun_3 = 8; static const sal_Int32 Tag_EndRun_1 = 9; static const sal_Int32 Tag_EndRun_2 = 10; static const sal_Int32 Tag_StartRunProperties = 11; static const sal_Int32 Tag_InitCollectedRunProperties = 12; static const sal_Int32 Tag_Redline_1 = 13; static const sal_Int32 Tag_Redline_2 = 14; static const sal_Int32 Tag_TableDefinition = 15; static const sal_Int32 Tag_OutputFlyFrame = 16; static const sal_Int32 Tag_StartSection = 17; class FFDataWriterHelper { ::sax_fastparser::FSHelperPtr m_pSerializer; void writeCommonStart( const OUString& rName ) { m_pSerializer->startElementNS( XML_w, XML_ffData, FSEND ); m_pSerializer->singleElementNS( XML_w, XML_name, FSNS( XML_w, XML_val ), OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ).getStr(), FSEND ); m_pSerializer->singleElementNS( XML_w, XML_enabled, FSEND ); m_pSerializer->singleElementNS( XML_w, XML_calcOnExit, FSNS( XML_w, XML_val ), "0", FSEND ); } void writeFinish() { m_pSerializer->endElementNS( XML_w, XML_ffData ); } public: explicit FFDataWriterHelper( const ::sax_fastparser::FSHelperPtr& rSerializer ) : m_pSerializer( rSerializer ){} void WriteFormCheckbox( const OUString& rName, bool bChecked ) { writeCommonStart( rName ); // Checkbox specific bits m_pSerializer->startElementNS( XML_w, XML_checkBox, FSEND ); // currently hardcoding autosize // #TODO check if this defaulted m_pSerializer->startElementNS( XML_w, XML_sizeAuto, FSEND ); m_pSerializer->endElementNS( XML_w, XML_sizeAuto ); if ( bChecked ) m_pSerializer->singleElementNS( XML_w, XML_checked, FSEND ); m_pSerializer->endElementNS( XML_w, XML_checkBox ); writeFinish(); } void WriteFormText( const OUString& rName ) { writeCommonStart( rName ); writeFinish(); } }; class FieldMarkParamsHelper { const sw::mark::IFieldmark& mrFieldmark; public: explicit FieldMarkParamsHelper( const sw::mark::IFieldmark& rFieldmark ) : mrFieldmark( rFieldmark ) {} OUString getName() { return mrFieldmark.GetName(); } template < typename T > bool extractParam( const OUString& rKey, T& rResult ) { bool bResult = false; if ( mrFieldmark.GetParameters() ) { sw::mark::IFieldmark::parameter_map_t::const_iterator it = mrFieldmark.GetParameters()->find( rKey ); if ( it != mrFieldmark.GetParameters()->end() ) bResult = ( it->second >>= rResult ); } return bResult; } }; void DocxAttributeOutput::RTLAndCJKState( bool bIsRTL, sal_uInt16 /*nScript*/ ) { if (bIsRTL) m_pSerializer->singleElementNS( XML_w, XML_rtl, FSNS( XML_w, XML_val ), "true", FSEND ); } /// Are multiple paragraphs disallowed inside this type of SDT? static bool lcl_isOnelinerSdt(const OUString& rName) { return rName == "Title" || rName == "Subtitle" || rName == "Company"; } void DocxAttributeOutput::StartParagraph( ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo ) { if ( m_nColBreakStatus == COLBRK_POSTPONE ) m_nColBreakStatus = COLBRK_WRITE; // Output table/table row/table cell starts if needed if ( pTextNodeInfo.get() ) { // New cell/row? if ( m_tableReference->m_nTableDepth > 0 && !m_tableReference->m_bTableCellOpen ) { ww8::WW8TableNodeInfoInner::Pointer_t pDeepInner( pTextNodeInfo->getInnerForDepth( m_tableReference->m_nTableDepth ) ); if ( pDeepInner->getCell() == 0 ) StartTableRow( pDeepInner ); const sal_uInt32 nCell = pDeepInner->getCell(); const sal_uInt32 nRow = pDeepInner->getRow(); SyncNodelessCells(pDeepInner, nCell, nRow); StartTableCell(pDeepInner, nCell, nRow); } sal_uInt32 nRow = pTextNodeInfo->getRow(); sal_uInt32 nCell = pTextNodeInfo->getCell(); if (nCell == 0) { // Do we have to start the table? // [If we are at the right depth already, it means that we // continue the table cell] sal_uInt32 nCurrentDepth = pTextNodeInfo->getDepth(); if ( nCurrentDepth > m_tableReference->m_nTableDepth ) { // Start all the tables that begin here for ( sal_uInt32 nDepth = m_tableReference->m_nTableDepth + 1; nDepth <= nCurrentDepth; ++nDepth ) { ww8::WW8TableNodeInfoInner::Pointer_t pInner( pTextNodeInfo->getInnerForDepth( nDepth ) ); StartTable( pInner ); StartTableRow( pInner ); StartTableCell(pInner, 0, nDepth == nCurrentDepth ? nRow : 0); } m_tableReference->m_nTableDepth = nCurrentDepth; } } } // Look up the "sdt end before this paragraph" property early, when it // would normally arrive, it would be too late (would be after the // paragraph start has been written). bool bEndParaSdt = false; SwTextNode* pTextNode = m_rExport.m_pCurPam->GetNode().GetTextNode(); if (pTextNode && pTextNode->GetpSwAttrSet()) { const SfxItemSet* pSet = pTextNode->GetpSwAttrSet(); if (const SfxPoolItem* pItem = pSet->GetItem(RES_PARATR_GRABBAG)) { const SfxGrabBagItem& rParaGrabBag = static_cast(*pItem); const std::map& rMap = rParaGrabBag.GetGrabBag(); bEndParaSdt = m_bStartedParaSdt && rMap.find("ParaSdtEndBefore") != rMap.end(); } } // TODO also avoid multiline paragraphs in those SDT types for shape text bool bOneliner = m_bStartedParaSdt && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen() && lcl_isOnelinerSdt(m_aStartedParagraphSdtPrAlias); if (bEndParaSdt || (m_bStartedParaSdt && m_bHadSectPr) || bOneliner) { // This is the common case: "close sdt before the current paragraph" was requrested by the next paragraph. EndSdtBlock(); m_bStartedParaSdt = false; m_aStartedParagraphSdtPrAlias.clear(); } m_bHadSectPr = false; // this mark is used to be able to enclose the paragraph inside a sdr tag. // We will only know if we have to do that later. m_pSerializer->mark(Tag_StartParagraph_1); m_pSerializer->startElementNS( XML_w, XML_p, FSEND ); // postpone the output of the run (we get it before the paragraph // properties, but must write it after them) m_pSerializer->mark(Tag_StartParagraph_2); // no section break in this paragraph yet; can be set in SectionBreak() m_pSectionInfo.reset(); m_bParagraphOpened = true; m_bIsFirstParagraph = false; } static void lcl_deleteAndResetTheLists( rtl::Reference& pSdtPrTokenChildren, rtl::Reference& pSdtPrDataBindingAttrs, OUString& rSdtPrAlias) { if( pSdtPrTokenChildren.is() ) pSdtPrTokenChildren.clear(); if( pSdtPrDataBindingAttrs.is() ) pSdtPrDataBindingAttrs.clear(); if (!rSdtPrAlias.isEmpty()) rSdtPrAlias.clear(); } void DocxAttributeOutput::PopulateFrameProperties(const SwFrameFormat* pFrameFormat, const Size& rSize) { sax_fastparser::FastAttributeList* attrList = FastSerializerHelper::createAttrList(); awt::Point aPos(pFrameFormat->GetHoriOrient().GetPos(), pFrameFormat->GetVertOrient().GetPos()); attrList->add( FSNS( XML_w, XML_w), OString::number(rSize.Width())); attrList->add( FSNS( XML_w, XML_h), OString::number(rSize.Height())); attrList->add( FSNS( XML_w, XML_x), OString::number(aPos.X)); attrList->add( FSNS( XML_w, XML_y), OString::number(aPos.Y)); const char* relativeFromH; const char* relativeFromV; switch (pFrameFormat->GetVertOrient().GetRelationOrient()) { case text::RelOrientation::PAGE_PRINT_AREA: relativeFromV = "margin"; break; case text::RelOrientation::PAGE_FRAME: relativeFromV = "page"; break; case text::RelOrientation::FRAME: case text::RelOrientation::TEXT_LINE: default: relativeFromV = "text"; break; } switch (pFrameFormat->GetHoriOrient().GetRelationOrient()) { case text::RelOrientation::PAGE_PRINT_AREA: relativeFromH = "margin"; break; case text::RelOrientation::PAGE_FRAME: relativeFromH = "page"; break; case text::RelOrientation::CHAR: case text::RelOrientation::PAGE_RIGHT: case text::RelOrientation::FRAME: default: relativeFromH = "text"; break; } switch (pFrameFormat->GetSurround().GetValue()) { case css::text::WrapTextMode_NONE: attrList->add( FSNS( XML_w, XML_wrap), "none"); break; case css::text::WrapTextMode_THROUGH: attrList->add( FSNS( XML_w, XML_wrap), "through"); break; case css::text::WrapTextMode_PARALLEL: attrList->add( FSNS( XML_w, XML_wrap), "notBeside"); break; case css::text::WrapTextMode_DYNAMIC: default: attrList->add( FSNS( XML_w, XML_wrap), "auto"); break; } attrList->add( FSNS( XML_w, XML_vAnchor), relativeFromV); attrList->add( FSNS( XML_w, XML_hAnchor), relativeFromH); attrList->add( FSNS( XML_w, XML_hRule), "exact"); sax_fastparser::XFastAttributeListRef xAttrList(attrList); m_pSerializer->singleElementNS( XML_w, XML_framePr, xAttrList ); } bool DocxAttributeOutput::TextBoxIsFramePr(const SwFrameFormat& rFrameFormat) { uno::Reference< drawing::XShape > xShape; const SdrObject* pSdrObj = rFrameFormat.FindRealSdrObject(); if (pSdrObj) xShape.set(const_cast(pSdrObj)->getUnoShape(), uno::UNO_QUERY); uno::Reference< beans::XPropertySet > xPropertySet(xShape, uno::UNO_QUERY); uno::Reference< beans::XPropertySetInfo > xPropSetInfo; if (xPropertySet.is()) xPropSetInfo = xPropertySet->getPropertySetInfo(); uno::Any aFrameProperties ; if (xPropSetInfo.is() && xPropSetInfo->hasPropertyByName("FrameInteropGrabBag")) { uno::Sequence< beans::PropertyValue > propList; xPropertySet->getPropertyValue("FrameInteropGrabBag") >>= propList; for (sal_Int32 nProp=0; nProp < propList.getLength(); ++nProp) { OUString propName = propList[nProp].Name; if (propName == "ParaFrameProperties") { aFrameProperties = propList[nProp].Value ; break; } } } bool bFrameProperties = false; aFrameProperties >>= bFrameProperties; return bFrameProperties; } void DocxAttributeOutput::EndParagraph( ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner ) { // write the paragraph properties + the run, already in the correct order m_pSerializer->mergeTopMarks(Tag_StartParagraph_2); std::vector< std::shared_ptr > aFramePrTextbox; // Write the anchored frame if any // Word can't handle nested text boxes, so write them on the same level. ++m_nTextFrameLevel; if( m_nTextFrameLevel == 1 && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen() ) { comphelper::FlagRestorationGuard aStartedParaSdtGuard(m_bStartedParaSdt, false); assert(!m_pPostponedCustomShape); m_pPostponedCustomShape.reset(new std::vector); for (size_t nIndex = 0; nIndex < m_aFramesOfParagraph.size(); ++nIndex) { m_bParagraphFrameOpen = true; ww8::Frame aFrame = m_aFramesOfParagraph[nIndex]; const SwFrameFormat& rFrameFormat = aFrame.GetFrameFormat(); if (!TextBoxIsFramePr(rFrameFormat) || m_bWritingHeaderFooter) { if (m_bStartedCharSdt) { // Run-level SDT still open? Close it before AlternateContent. EndSdtBlock(); m_bStartedCharSdt = false; } m_pSerializer->startElementNS( XML_w, XML_r, FSEND ); m_pSerializer->startElementNS(XML_mc, XML_AlternateContent, FSEND); m_pSerializer->startElementNS(XML_mc, XML_Choice, XML_Requires, "wps", FSEND); /** This is to avoid AlternateContent within another AlternateContent. So when Choice is Open, only write the DML Drawing instead of both DML and VML Drawing in another AlternateContent. **/ SetAlternateContentChoiceOpen( true ); /** Save the table info's before writing the shape as there might be a new table that might get spawned from within the VML & DML block and alter the contents. */ ww8::WW8TableInfo::Pointer_t pOldTableInfo = m_rExport.m_pTableInfo; //Reset the table infos after saving. m_rExport.m_pTableInfo = std::make_shared(); /** FDO#71834 : Save the table reference attributes before calling WriteDMLTextFrame, otherwise the StartParagraph function will use the previous existing table reference attributes since the variable is being shared. */ DocxTableExportContext aDMLTableExportContext; pushToTableExportContext(aDMLTableExportContext); m_rExport.SdrExporter().writeDMLTextFrame(&aFrame, m_anchorId++); popFromTableExportContext(aDMLTableExportContext); m_pSerializer->endElementNS(XML_mc, XML_Choice); SetAlternateContentChoiceOpen( false ); // Reset table infos, otherwise the depth of the cells will be incorrect, // in case the text frame had table(s) and we try to export the // same table second time. m_rExport.m_pTableInfo = std::make_shared(); //reset the tableReference. m_pSerializer->startElementNS(XML_mc, XML_Fallback, FSEND); DocxTableExportContext aVMLTableExportContext; pushToTableExportContext(aVMLTableExportContext); m_rExport.SdrExporter().writeVMLTextFrame(&aFrame); popFromTableExportContext(aVMLTableExportContext); m_rExport.m_pTableInfo = pOldTableInfo; m_pSerializer->endElementNS(XML_mc, XML_Fallback); m_pSerializer->endElementNS(XML_mc, XML_AlternateContent); m_pSerializer->endElementNS( XML_w, XML_r ); m_bParagraphFrameOpen = false; } else { std::shared_ptr pFramePr; pFramePr.reset(new ww8::Frame(aFrame)); aFramePrTextbox.push_back(pFramePr); } } if (!m_pPostponedCustomShape->empty()) { m_pSerializer->startElementNS( XML_w, XML_r, FSEND ); WritePostponedCustomShape(); m_pSerializer->endElementNS( XML_w, XML_r ); } m_pPostponedCustomShape.reset(nullptr); m_aFramesOfParagraph.clear(); } --m_nTextFrameLevel; /* If m_nHyperLinkCount > 0 that means hyperlink tag is not yet closed. * This is due to nested hyperlink tags. So close it before end of paragraph. */ if(m_nHyperLinkCount > 0) { for(sal_Int32 nHyperLinkToClose = 0; nHyperLinkToClose < m_nHyperLinkCount; ++nHyperLinkToClose) m_pSerializer->endElementNS( XML_w, XML_hyperlink ); m_nHyperLinkCount = 0; } if (m_bStartedCharSdt) { // Run-level SDT still open? Close it now. EndSdtBlock(); m_bStartedCharSdt = false; } m_pSerializer->endElementNS( XML_w, XML_p ); // on export sdt blocks are never nested ATM if( !m_bAnchorLinkedToNode && !m_bStartedParaSdt ) WriteSdtBlock( m_nParagraphSdtPrToken, m_pParagraphSdtPrTokenChildren, m_pParagraphSdtPrTokenAttributes, m_pParagraphSdtPrDataBindingAttrs, m_aParagraphSdtPrAlias, /*bPara=*/true ); 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; m_bParagraphSdtHasId = false; lcl_deleteAndResetTheLists( m_pParagraphSdtPrTokenChildren, m_pParagraphSdtPrDataBindingAttrs, m_aParagraphSdtPrAlias ); } //sdtcontent is written so Set m_bParagraphHasDrawing to false m_rExport.SdrExporter().setParagraphHasDrawing( false ); m_bRunTextIsOn = false; m_pSerializer->mergeTopMarks(Tag_StartParagraph_1); // Write framePr if(!aFramePrTextbox.empty()) { for ( const auto & pFrame : aFramePrTextbox ) { DocxTableExportContext aTableExportContext; pushToTableExportContext(aTableExportContext); m_pCurrentFrame = pFrame.get(); m_rExport.SdrExporter().writeOnlyTextOfFrame(pFrame.get()); m_pCurrentFrame = nullptr; popFromTableExportContext(aTableExportContext); } aFramePrTextbox.clear(); } // Check for end of cell, rows, tables here FinishTableRowCell( pTextNodeInfoInner ); if( !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen() ) m_bParagraphOpened = false; } void DocxAttributeOutput::WriteSdtBlock( sal_Int32& nSdtPrToken, rtl::Reference& pSdtPrTokenChildren, rtl::Reference& pSdtPrTokenAttributes, rtl::Reference& pSdtPrDataBindingAttrs, OUString& rSdtPrAlias, bool bPara ) { if( nSdtPrToken > 0 || pSdtPrDataBindingAttrs.is() ) { // sdt start mark m_pSerializer->mark(Tag_WriteSdtBlock); m_pSerializer->startElementNS( XML_w, XML_sdt, FSEND ); // output sdt properties m_pSerializer->startElementNS( XML_w, XML_sdtPr, FSEND ); if( nSdtPrToken > 0 && pSdtPrTokenChildren.is() ) { if (!pSdtPrTokenAttributes.is()) m_pSerializer->startElement( nSdtPrToken, FSEND ); else { XFastAttributeListRef xAttrList(pSdtPrTokenAttributes.get()); pSdtPrTokenAttributes.clear(); m_pSerializer->startElement(nSdtPrToken, xAttrList); } if (nSdtPrToken == FSNS( XML_w, XML_date ) || nSdtPrToken == FSNS( XML_w, XML_docPartObj ) || nSdtPrToken == FSNS( XML_w, XML_docPartList ) || nSdtPrToken == FSNS( XML_w14, XML_checkbox )) { uno::Sequence aChildren = pSdtPrTokenChildren->getFastAttributes(); for( sal_Int32 i=0; i < aChildren.getLength(); ++i ) m_pSerializer->singleElement( aChildren[i].Token, FSNS(XML_w, XML_val), OUStringToOString( aChildren[i].Value, RTL_TEXTENCODING_UTF8 ).getStr(), FSEND ); } m_pSerializer->endElement( nSdtPrToken ); } else if( (nSdtPrToken > 0) && nSdtPrToken != FSNS( XML_w, XML_id ) && !(m_bRunTextIsOn && m_rExport.SdrExporter().IsParagraphHasDrawing())) { if (!pSdtPrTokenAttributes.is()) m_pSerializer->singleElement( nSdtPrToken, FSEND ); else { XFastAttributeListRef xAttrList(pSdtPrTokenAttributes.get()); pSdtPrTokenAttributes.clear(); m_pSerializer->singleElement(nSdtPrToken, xAttrList); } } if( nSdtPrToken == FSNS( XML_w, XML_id ) || ( bPara && m_bParagraphSdtHasId ) ) //Word won't open a document with an empty id tag, we fill it with a random number m_pSerializer->singleElementNS(XML_w, XML_id, FSNS(XML_w, XML_val), OString::number(comphelper::rng::uniform_int_distribution(0, std::numeric_limits::max())), FSEND); if( pSdtPrDataBindingAttrs.is() && !m_rExport.SdrExporter().IsParagraphHasDrawing()) { XFastAttributeListRef xAttrList( pSdtPrDataBindingAttrs.get() ); pSdtPrDataBindingAttrs.clear(); m_pSerializer->singleElementNS( XML_w, XML_dataBinding, xAttrList ); } if (!rSdtPrAlias.isEmpty()) m_pSerializer->singleElementNS(XML_w, XML_alias, FSNS(XML_w, XML_val), OUStringToOString(rSdtPrAlias, RTL_TEXTENCODING_UTF8).getStr(), FSEND); m_pSerializer->endElementNS( XML_w, XML_sdtPr ); // sdt contents start tag m_pSerializer->startElementNS( XML_w, XML_sdtContent, FSEND ); // prepend the tags since the sdt start mark before the paragraph m_pSerializer->mergeTopMarks(Tag_WriteSdtBlock, sax_fastparser::MergeMarks::PREPEND); // write the ending tags after the paragraph if (bPara) { m_bStartedParaSdt = true; if (m_tableReference->m_bTableCellOpen) m_tableReference->m_bTableCellParaSdtOpen = true; if (m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen()) m_rExport.SdrExporter().setParagraphSdtOpen(true); } else // Support multiple runs inside a run-level SDT: don't close the SDT block yet. m_bStartedCharSdt = true; // clear sdt status nSdtPrToken = 0; pSdtPrTokenChildren.clear(); pSdtPrDataBindingAttrs.clear(); rSdtPrAlias.clear(); } } void DocxAttributeOutput::EndSdtBlock() { m_pSerializer->endElementNS( XML_w, XML_sdtContent ); m_pSerializer->endElementNS( XML_w, XML_sdt ); } #define MAX_CELL_IN_WORD 62 void DocxAttributeOutput::SyncNodelessCells(ww8::WW8TableNodeInfoInner::Pointer_t const & pInner, sal_Int32 nCell, sal_uInt32 nRow) { sal_Int32 nOpenCell = lastOpenCell.back(); if (nOpenCell != -1 && nOpenCell != nCell && nOpenCell < MAX_CELL_IN_WORD) EndTableCell(nOpenCell); sal_Int32 nClosedCell = lastClosedCell.back(); for (sal_Int32 i = nClosedCell+1; i < nCell; ++i) { if (i >= MAX_CELL_IN_WORD) break; if (i == 0) StartTableRow(pInner); StartTableCell(pInner, i, nRow); m_pSerializer->singleElementNS( XML_w, XML_p, FSEND ); EndTableCell(i); } } void DocxAttributeOutput::FinishTableRowCell( ww8::WW8TableNodeInfoInner::Pointer_t const & pInner, bool bForceEmptyParagraph ) { if ( pInner.get() ) { // Where are we in the table sal_uInt32 nRow = pInner->getRow(); sal_Int32 nCell = pInner->getCell(); InitTableHelper( pInner ); // HACK // msoffice seems to have an internal limitation of 63 columns for tables // and refuses to load .docx with more, even though the spec seems to allow that; // so simply if there are more columns, don't close the last one msoffice will handle // and merge the contents of the remaining ones into it (since we don't close the cell // here, following ones will not be opened) const bool limitWorkaround = (nCell >= MAX_CELL_IN_WORD && !pInner->isEndOfLine()); const bool bEndCell = pInner->isEndOfCell() && !limitWorkaround; const bool bEndRow = pInner->isEndOfLine(); if (bEndCell) { while (pInner->getDepth() < m_tableReference->m_nTableDepth) { //we expect that the higher depth row was closed, and //we are just missing the table close assert(lastOpenCell.back() == -1 && lastClosedCell.back() == -1); EndTable(); } SyncNodelessCells(pInner, nCell, nRow); sal_Int32 nClosedCell = lastClosedCell.back(); if (nCell == nClosedCell) { //Start missing trailing cell ++nCell; StartTableCell(pInner, nCell, nRow); } if (bForceEmptyParagraph) { m_pSerializer->singleElementNS( XML_w, XML_p, FSEND ); } EndTableCell(nCell); } // This is a line end if (bEndRow) EndTableRow(); // This is the end of the table if (pInner->isFinalEndOfLine()) EndTable(); } } void DocxAttributeOutput::EmptyParagraph() { m_pSerializer->singleElementNS( XML_w, XML_p, FSEND ); } void DocxAttributeOutput::SectionBreaks(const SwNode& rNode) { // output page/section breaks // Writer can have them at the beginning of a paragraph, or at the end, but // in docx, we have to output them in the paragraph properties of the last // paragraph in a section. To get it right, we have to switch to the next // paragraph, and detect the section breaks there. SwNodeIndex aNextIndex( rNode, 1 ); if (rNode.IsTextNode()) { if (aNextIndex.GetNode().IsTextNode()) { const SwTextNode* pTextNode = static_cast(&aNextIndex.GetNode()); m_rExport.OutputSectionBreaks(pTextNode->GetpSwAttrSet(), *pTextNode, m_tableReference->m_bTableCellOpen, pTextNode->GetText().isEmpty()); } else if (aNextIndex.GetNode().IsTableNode()) { const SwTableNode* pTableNode = static_cast(&aNextIndex.GetNode()); const SwFrameFormat *pFormat = pTableNode->GetTable().GetFrameFormat(); m_rExport.OutputSectionBreaks(&(pFormat->GetAttrSet()), *pTableNode); } } else if (rNode.IsEndNode()) { if (aNextIndex.GetNode().IsTextNode()) { // Handle section break between a table and a text node following it. // Also handle section endings const SwTextNode* pTextNode = aNextIndex.GetNode().GetTextNode(); if (rNode.StartOfSectionNode()->IsTableNode() || rNode.StartOfSectionNode()->IsSectionNode()) m_rExport.OutputSectionBreaks(pTextNode->GetpSwAttrSet(), *pTextNode, m_tableReference->m_bTableCellOpen, pTextNode->GetText().isEmpty()); } } } void DocxAttributeOutput::StartParagraphProperties() { m_pSerializer->mark(Tag_StartParagraphProperties); m_pSerializer->startElementNS( XML_w, XML_pPr, FSEND ); // and output the section break now (if it appeared) if ( m_pSectionInfo && (!m_setFootnote)) { m_rExport.SectionProperties( *m_pSectionInfo ); m_pSectionInfo.reset(); } InitCollectedParagraphProperties(); } void DocxAttributeOutput::InitCollectedParagraphProperties() { m_pParagraphSpacingAttrList.clear(); // Write the elements in the spec order static const sal_Int32 aOrder[] = { FSNS( XML_w, XML_pStyle ), FSNS( XML_w, XML_keepNext ), FSNS( XML_w, XML_keepLines ), FSNS( XML_w, XML_pageBreakBefore ), FSNS( XML_w, XML_framePr ), FSNS( XML_w, XML_widowControl ), FSNS( XML_w, XML_numPr ), FSNS( XML_w, XML_suppressLineNumbers ), FSNS( XML_w, XML_pBdr ), FSNS( XML_w, XML_shd ), FSNS( XML_w, XML_tabs ), FSNS( XML_w, XML_suppressAutoHyphens ), FSNS( XML_w, XML_kinsoku ), FSNS( XML_w, XML_wordWrap ), FSNS( XML_w, XML_overflowPunct ), FSNS( XML_w, XML_topLinePunct ), FSNS( XML_w, XML_autoSpaceDE ), FSNS( XML_w, XML_autoSpaceDN ), FSNS( XML_w, XML_bidi ), FSNS( XML_w, XML_adjustRightInd ), FSNS( XML_w, XML_snapToGrid ), FSNS( XML_w, XML_spacing ), FSNS( XML_w, XML_ind ), FSNS( XML_w, XML_contextualSpacing ), FSNS( XML_w, XML_mirrorIndents ), FSNS( XML_w, XML_suppressOverlap ), FSNS( XML_w, XML_jc ), FSNS( XML_w, XML_textDirection ), FSNS( XML_w, XML_textAlignment ), FSNS( XML_w, XML_textboxTightWrap ), FSNS( XML_w, XML_outlineLvl ), FSNS( XML_w, XML_divId ), FSNS( XML_w, XML_cnfStyle ), FSNS( XML_w, XML_rPr ), FSNS( XML_w, XML_sectPr ), FSNS( XML_w, XML_pPrChange ) }; // postpone the output so that we can later [in EndParagraphProperties()] // prepend the properties before the run m_pSerializer->mark(Tag_InitCollectedParagraphProperties, comphelper::containerToSequence(aOrder)); } void DocxAttributeOutput::WriteCollectedParagraphProperties() { if ( m_rExport.SdrExporter().getFlyAttrList().is() ) { XFastAttributeListRef xAttrList( m_rExport.SdrExporter().getFlyAttrList().get() ); m_rExport.SdrExporter().getFlyAttrList().clear(); m_pSerializer->singleElementNS( XML_w, XML_framePr, xAttrList ); } if ( m_pParagraphSpacingAttrList.is() ) { XFastAttributeListRef xAttrList( m_pParagraphSpacingAttrList.get() ); m_pParagraphSpacingAttrList.clear(); m_pSerializer->singleElementNS( XML_w, XML_spacing, xAttrList ); } if ( m_pBackgroundAttrList.is() ) { XFastAttributeListRef xAttrList( m_pBackgroundAttrList.get() ); m_pBackgroundAttrList.clear(); m_pSerializer->singleElementNS( XML_w, XML_shd, xAttrList ); } } namespace { /// Outputs an item set, that contains the formatting of the paragraph marker. void lcl_writeParagraphMarkerProperties(DocxAttributeOutput& rAttributeOutput, const SfxItemSet& rParagraphMarkerProperties) { SfxWhichIter aIter(rParagraphMarkerProperties); sal_uInt16 nWhichId = aIter.FirstWhich(); const SfxPoolItem* pItem = nullptr; // Did we already produce a element? bool bFontSizeWritten = false; while (nWhichId) { if (rParagraphMarkerProperties.GetItemState(nWhichId, true, &pItem) == SfxItemState::SET) { if (isCHRATR(nWhichId) || nWhichId == RES_TXTATR_CHARFMT) { // Will this item produce a element? bool bFontSizeItem = nWhichId == RES_CHRATR_FONTSIZE || nWhichId == RES_CHRATR_CJK_FONTSIZE; if (!bFontSizeWritten || !bFontSizeItem) rAttributeOutput.OutputItem(*pItem); if (bFontSizeItem) bFontSizeWritten = true; } else if (nWhichId == RES_TXTATR_AUTOFMT) { const SwFormatAutoFormat* pAutoFormat = static_cast(pItem); lcl_writeParagraphMarkerProperties(rAttributeOutput, *pAutoFormat->GetStyleHandle()); } } nWhichId = aIter.NextWhich(); } } } void DocxAttributeOutput::EndParagraphProperties(const SfxItemSet& rParagraphMarkerProperties, const SwRedlineData* pRedlineData, const SwRedlineData* pRedlineParagraphMarkerDeleted, const SwRedlineData* pRedlineParagraphMarkerInserted) { // Call the 'Redline' function. This will add redline (change-tracking) information that regards to paragraph properties. // This includes changes like 'Bold', 'Underline', 'Strikethrough' etc. // If there is RedlineData present, call WriteCollectedParagraphProperties() for writing pPr before calling Redline(). // As there will be another pPr for redline and LO might mix both. if(pRedlineData) WriteCollectedParagraphProperties(); Redline( pRedlineData ); WriteCollectedParagraphProperties(); // Merge the marks for the ordered elements m_pSerializer->mergeTopMarks(Tag_InitCollectedParagraphProperties); // Write 'Paragraph Mark' properties m_pSerializer->startElementNS( XML_w, XML_rPr, FSEND ); // mark() before paragraph mark properties child elements. InitCollectedRunProperties(); // The 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList' are used to hold information // that should be collected by different properties in the core, and are all flushed together // to the DOCX when the function 'WriteCollectedRunProperties' gets called. // So we need to store the current status of these lists, so that we can revert back to them when // we are done exporting the redline attributes. rtl::Reference pFontsAttrList_Original(m_pFontsAttrList); m_pFontsAttrList.clear(); rtl::Reference pEastAsianLayoutAttrList_Original(m_pEastAsianLayoutAttrList); m_pEastAsianLayoutAttrList.clear(); rtl::Reference pCharLangAttrList_Original(m_pCharLangAttrList); m_pCharLangAttrList.clear(); lcl_writeParagraphMarkerProperties(*this, rParagraphMarkerProperties); // Write the collected run properties that are stored in 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList' WriteCollectedRunProperties(); // Revert back the original values that were stored in 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList' m_pFontsAttrList = pFontsAttrList_Original.get(); m_pEastAsianLayoutAttrList = pEastAsianLayoutAttrList_Original.get(); m_pCharLangAttrList = pCharLangAttrList_Original.get(); if ( pRedlineParagraphMarkerDeleted ) { StartRedline( pRedlineParagraphMarkerDeleted ); EndRedline( pRedlineParagraphMarkerDeleted ); } if ( pRedlineParagraphMarkerInserted ) { StartRedline( pRedlineParagraphMarkerInserted ); EndRedline( pRedlineParagraphMarkerInserted ); } // mergeTopMarks() after paragraph mark properties child elements. m_pSerializer->mergeTopMarks(Tag_InitCollectedRunProperties); m_pSerializer->endElementNS( XML_w, XML_rPr ); if (!m_bWritingHeaderFooter && m_pCurrentFrame) { const SwFrameFormat& rFrameFormat = m_pCurrentFrame->GetFrameFormat(); if (TextBoxIsFramePr(rFrameFormat)) { const Size aSize = m_pCurrentFrame->GetSize(); PopulateFrameProperties(&rFrameFormat, aSize); } } m_pSerializer->endElementNS( XML_w, XML_pPr ); // RDF metadata for this text node. SwTextNode* pTextNode = m_rExport.m_pCurPam->GetNode().GetTextNode(); std::map aStatements = SwRDFHelper::getTextNodeStatements("urn:bails", *pTextNode); if (!aStatements.empty()) { m_pSerializer->startElementNS(XML_w, XML_smartTag, FSNS(XML_w, XML_uri), "http://www.w3.org/1999/02/22-rdf-syntax-ns#", FSNS(XML_w, XML_element), "RDF", FSEND); m_pSerializer->startElementNS(XML_w, XML_smartTagPr, FSEND); for (const auto& rStatement : aStatements) m_pSerializer->singleElementNS(XML_w, XML_attr, FSNS(XML_w, XML_name), rStatement.first.toUtf8(), FSNS(XML_w, XML_val), rStatement.second.toUtf8(), FSEND); m_pSerializer->endElementNS(XML_w, XML_smartTagPr); m_pSerializer->endElementNS(XML_w, XML_smartTag); } if ( m_nColBreakStatus == COLBRK_WRITE ) { m_pSerializer->startElementNS( XML_w, XML_r, FSEND ); m_pSerializer->singleElementNS( XML_w, XML_br, FSNS( XML_w, XML_type ), "column", FSEND ); m_pSerializer->endElementNS( XML_w, XML_r ); m_nColBreakStatus = COLBRK_NONE; } if ( m_bPostponedPageBreak ) { m_pSerializer->startElementNS( XML_w, XML_r, FSEND ); m_pSerializer->singleElementNS( XML_w, XML_br, FSNS( XML_w, XML_type ), "page", FSEND ); m_pSerializer->endElementNS( XML_w, XML_r ); m_bPostponedPageBreak = false; } // merge the properties _before_ the run (strictly speaking, just // after the start of the paragraph) m_pSerializer->mergeTopMarks(Tag_StartParagraphProperties, sax_fastparser::MergeMarks::PREPEND); } void DocxAttributeOutput::SetStateOfFlyFrame( FlyProcessingState nStateOfFlyFrame ) { m_nStateOfFlyFrame = nStateOfFlyFrame; } 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 // that has to be started first. m_pRedlineData = pRedlineData; // this mark is used to be able to enclose the run inside a sdr tag. m_pSerializer->mark(Tag_StartRun_1); // postpone the output of the start of a run (there are elements that need // to be written before the start of the run, but we learn which they are // _inside_ of the run) m_pSerializer->mark(Tag_StartRun_2); // let's call it "postponed run start" // postpone the output of the text (we get it before the run properties, // but must write it after them) m_pSerializer->mark(Tag_StartRun_3); // let's call it "postponed text" } void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos) { int nFieldsInPrevHyperlink = m_nFieldsInHyperlink; // Reset m_nFieldsInHyperlink if a new hyperlink is about to start if ( m_pHyperlinkAttrList.is() ) { m_nFieldsInHyperlink = 0; } // Write field starts for ( std::vector::iterator pIt = m_Fields.begin() + nFieldsInPrevHyperlink; pIt != m_Fields.end(); ) { // Add the fields starts for all but hyperlinks and TOCs if ( pIt->bOpen && pIt->pField ) { StartField_Impl( pNode, nPos, *pIt ); // Remove the field from the stack if only the start has to be written // Unknown fields should be removed too if ( !pIt->bClose || ( pIt->eType == ww::eUNKNOWN ) ) { pIt = m_Fields.erase( pIt ); continue; } if (m_startedHyperlink || m_pHyperlinkAttrList.is()) { ++m_nFieldsInHyperlink; } } ++pIt; } // write the run properties + the text, already in the correct order m_pSerializer->mergeTopMarks(Tag_StartRun_3); // merges with "postponed text", see above // level down, to be able to prepend the actual run start attribute (just // before "postponed run start") m_pSerializer->mark(Tag_EndRun_1); // let's call it "actual run start" bool bCloseEarlierSDT = false; if (m_bEndCharSdt) { // This is the common case: "close sdt before the current run" was requrested by the next run. // if another sdt starts in this run, then wait // as closing the sdt now, might cause nesting of sdts if (m_nRunSdtPrToken > 0) bCloseEarlierSDT = true; else EndSdtBlock(); m_bEndCharSdt = false; m_bStartedCharSdt = false; } if ( m_closeHyperlinkInPreviousRun ) { if ( m_startedHyperlink ) { for ( int i = 0; i < nFieldsInPrevHyperlink; i++ ) { // If fields begin before hyperlink then // it should end before hyperlink close EndField_Impl( pNode, nPos, m_Fields.back( ) ); m_Fields.pop_back(); } m_pSerializer->endElementNS( XML_w, XML_hyperlink ); m_startedHyperlink = false; m_endPageRef = false; m_nHyperLinkCount--; } m_closeHyperlinkInPreviousRun = false; } // Write the hyperlink and toc fields starts for ( std::vector::iterator pIt = m_Fields.begin(); pIt != m_Fields.end(); ) { // Add the fields starts for hyperlinks, TOCs and index marks if ( pIt->bOpen && !pIt->pField ) { StartField_Impl( pNode, nPos, *pIt, true ); if (m_startedHyperlink) ++m_nFieldsInHyperlink; // Remove the field if no end needs to be written if ( !pIt->bClose ) { pIt = m_Fields.erase( pIt ); continue; } } ++pIt; } // Start the hyperlink after the fields separators or we would generate invalid file bool newStartedHyperlink(false); if ( m_pHyperlinkAttrList.is() ) { // if we are ending a hyperlink and there's another one starting here, // don't do this, so that the fields are closed further down when // the end hyperlink is handled, which is more likely to put the end in // the right place, as far as i can tell (not very far in this muck) if (!m_closeHyperlinkInThisRun) { // end ToX fields that want to end _before_ starting the hyperlink for (auto it = m_Fields.rbegin(); it != m_Fields.rend(); ) { if (it->bClose && !it->pField) { EndField_Impl( pNode, nPos, *it ); it = decltype(m_Fields)::reverse_iterator(m_Fields.erase(it.base() - 1)); } else { ++it; } } } newStartedHyperlink = true; XFastAttributeListRef xAttrList ( m_pHyperlinkAttrList.get() ); m_pHyperlinkAttrList.clear(); m_pSerializer->startElementNS( XML_w, XML_hyperlink, xAttrList ); m_startedHyperlink = true; m_nHyperLinkCount++; } // if there is some redlining in the document, output it StartRedline( m_pRedlineData ); // XML_r node should be surrounded with bookmark-begin and bookmark-end nodes if it has bookmarks. // The same is applied for permission ranges. // But due to unit test "testFdo85542" let's output bookmark-begin with bookmark-end. DoWriteBookmarksStart(); DoWriteBookmarksEnd(); DoWritePermissionsStart(); DoWriteAnnotationMarks(); if( m_closeHyperlinkInThisRun && m_startedHyperlink && !m_hyperLinkAnchor.isEmpty() && m_hyperLinkAnchor.startsWith("_Toc")) { OUString sToken; m_pSerializer->startElementNS( XML_w, XML_r, FSEND ); m_pSerializer->startElementNS( XML_w, XML_rPr, FSEND ); m_pSerializer->singleElementNS( XML_w, XML_webHidden, FSEND ); m_pSerializer->endElementNS( XML_w, XML_rPr ); m_pSerializer->startElementNS( XML_w, XML_fldChar, FSNS( XML_w, XML_fldCharType ), "begin", FSEND ); m_pSerializer->endElementNS( XML_w, XML_fldChar ); m_pSerializer->endElementNS( XML_w, XML_r ); m_pSerializer->startElementNS( XML_w, XML_r, FSEND ); m_pSerializer->startElementNS( XML_w, XML_rPr, FSEND ); m_pSerializer->singleElementNS( XML_w, XML_webHidden, FSEND ); m_pSerializer->endElementNS( XML_w, XML_rPr ); sToken = "PAGEREF " + m_hyperLinkAnchor + " \\h"; // '\h' Creates a hyperlink to the bookmarked paragraph. DoWriteCmd( sToken ); m_pSerializer->endElementNS( XML_w, XML_r ); // Write the Field separator m_pSerializer->startElementNS( XML_w, XML_r, FSEND ); m_pSerializer->startElementNS( XML_w, XML_rPr, FSEND ); m_pSerializer->singleElementNS( XML_w, XML_webHidden, FSEND ); m_pSerializer->endElementNS( XML_w, XML_rPr ); m_pSerializer->singleElementNS( XML_w, XML_fldChar, FSNS( XML_w, XML_fldCharType ), "separate", FSEND ); m_pSerializer->endElementNS( XML_w, XML_r ); // At start of every "PAGEREF" field m_endPageRef value should be true. m_endPageRef = true; } m_pSerializer->startElementNS( XML_w, XML_r, FSEND ); if(GetExport().m_bTabInTOC && m_pHyperlinkAttrList.is()) { RunText("\t") ; } m_pSerializer->mergeTopMarks(Tag_EndRun_1, sax_fastparser::MergeMarks::PREPEND); // merges with "postponed run start", see above // write the run start + the run content m_pSerializer->mergeTopMarks(Tag_StartRun_2); // merges the "actual run start" // append the actual run end m_pSerializer->endElementNS( XML_w, XML_r ); // if there is some redlining in the document, output it EndRedline( m_pRedlineData ); // enclose in a sdt block, if necessary: if one is already started, then don't do it for now // (so on export sdt blocks are never nested ATM) if ( !m_bAnchorLinkedToNode && !m_bStartedCharSdt ) { rtl::Reference pRunSdtPrTokenAttributes; WriteSdtBlock( m_nRunSdtPrToken, m_pRunSdtPrTokenChildren, pRunSdtPrTokenAttributes, m_pRunSdtPrDataBindingAttrs, m_aRunSdtPrAlias, /*bPara=*/false ); } 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_aRunSdtPrAlias ); } if (bCloseEarlierSDT) { m_pSerializer->mark(Tag_EndRun_2); EndSdtBlock(); m_pSerializer->mergeTopMarks(Tag_EndRun_2, sax_fastparser::MergeMarks::PREPEND); } m_pSerializer->mergeTopMarks(Tag_StartRun_1); // XML_r node should be surrounded with permission-begin and permission-end nodes if it has permission. DoWritePermissionsEnd(); for (std::vector::iterator it = m_aPostponedMaths.begin(); it != m_aPostponedMaths.end(); ++it) WritePostponedMath(*it); m_aPostponedMaths.clear(); for (std::vector::iterator it = m_aPostponedFormControls.begin(); it != m_aPostponedFormControls.end(); ++it) WritePostponedFormControl(*it); m_aPostponedFormControls.clear(); WritePostponedActiveXControl(false); WritePendingPlaceholder(); m_pRedlineData = nullptr; if ( m_closeHyperlinkInThisRun ) { if ( m_startedHyperlink ) { if( m_endPageRef ) { // Hyperlink is started and fldchar "end" needs to be written for PAGEREF m_pSerializer->startElementNS( XML_w, XML_r, FSEND ); m_pSerializer->startElementNS( XML_w, XML_rPr, FSEND ); m_pSerializer->singleElementNS( XML_w, XML_webHidden, FSEND ); m_pSerializer->endElementNS( XML_w, XML_rPr ); m_pSerializer->singleElementNS( XML_w, XML_fldChar, FSNS( XML_w, XML_fldCharType ), "end", FSEND ); m_pSerializer->endElementNS( XML_w, XML_r ); m_endPageRef = false; m_hyperLinkAnchor.clear(); } for ( int i = 0; i < m_nFieldsInHyperlink; i++ ) { // If fields begin after hyperlink start then // it should end before hyperlink close EndField_Impl( pNode, nPos, m_Fields.back( ) ); m_Fields.pop_back(); } m_nFieldsInHyperlink = 0; m_pSerializer->endElementNS( XML_w, XML_hyperlink ); m_startedHyperlink = false; m_nHyperLinkCount--; } m_closeHyperlinkInThisRun = false; } if (!newStartedHyperlink) { while ( m_Fields.begin() != m_Fields.end() ) { EndField_Impl( pNode, nPos, m_Fields.front( ) ); m_Fields.erase( m_Fields.begin( ) ); } m_nFieldsInHyperlink = 0; } } void DocxAttributeOutput::DoWriteBookmarkTagStart(const OUString & bookmarkName) { const OString rId = OString::number(m_nNextBookmarkId); const OString rName = OUStringToOString(BookmarkToWord(bookmarkName), RTL_TEXTENCODING_UTF8).getStr(); m_pSerializer->singleElementNS(XML_w, XML_bookmarkStart, FSNS(XML_w, XML_id), rId.getStr(), FSNS(XML_w, XML_name), rName.getStr(), FSEND); } void DocxAttributeOutput::DoWriteBookmarkTagEnd(const OUString & bookmarkName) { const auto nameToIdIter = m_rOpenedBookmarksIds.find(bookmarkName); if (nameToIdIter != m_rOpenedBookmarksIds.end()) { const sal_Int32 nId = nameToIdIter->second; const OString rId = OString::number(nId); m_pSerializer->singleElementNS(XML_w, XML_bookmarkEnd, FSNS(XML_w, XML_id), rId.getStr(), FSEND); } } /// Write the start bookmarks void DocxAttributeOutput::DoWriteBookmarksStart() { for (const OUString & bookmarkName : m_rBookmarksStart) { // Output the bookmark DoWriteBookmarkTagStart(bookmarkName); m_rOpenedBookmarksIds[bookmarkName] = m_nNextBookmarkId; m_sLastOpenedBookmark = OUStringToOString(BookmarkToWord(bookmarkName), RTL_TEXTENCODING_UTF8).getStr(); m_nNextBookmarkId++; } m_rBookmarksStart.clear(); } /// export the end bookmarks void DocxAttributeOutput::DoWriteBookmarksEnd() { for (const OUString & bookmarkName : m_rBookmarksEnd) { // Get the id of the bookmark auto pPos = m_rOpenedBookmarksIds.find(bookmarkName); if (pPos != m_rOpenedBookmarksIds.end()) { // Output the bookmark DoWriteBookmarkTagEnd(bookmarkName); m_rOpenedBookmarksIds.erase(bookmarkName); } } m_rBookmarksEnd.clear(); } // For construction of the special bookmark name template for permissions: // see, PermInsertPosition::createBookmarkName() // // Syntax: // - "permission-for-user::" // - "permission-for-group::" // void DocxAttributeOutput::DoWritePermissionTagStart(const OUString & permission) { OUString permissionIdAndName; if (permission.startsWith("permission-for-group:", &permissionIdAndName)) { const sal_Int32 sparatorIndex = permissionIdAndName.indexOf(':'); const OUString permissionId = permissionIdAndName.copy(0, sparatorIndex); const OUString permissionName = permissionIdAndName.copy(sparatorIndex + 1); const OString rId = OUStringToOString(BookmarkToWord(permissionId), RTL_TEXTENCODING_UTF8).getStr(); const OString rName = OUStringToOString(BookmarkToWord(permissionName), RTL_TEXTENCODING_UTF8).getStr(); m_pSerializer->singleElementNS(XML_w, XML_permStart, FSNS(XML_w, XML_id), rId.getStr(), FSNS(XML_w, XML_edGrp), rName.getStr(), FSEND); } else // if (permission.startsWith("permission-for-user:", &permissionIdAndName)) { const sal_Int32 sparatorIndex = permissionIdAndName.indexOf(':'); const OUString permissionId = permissionIdAndName.copy(0, sparatorIndex); const OUString permissionName = permissionIdAndName.copy(sparatorIndex + 1); const OString rId = OUStringToOString(BookmarkToWord(permissionId), RTL_TEXTENCODING_UTF8).getStr(); const OString rName = OUStringToOString(BookmarkToWord(permissionName), RTL_TEXTENCODING_UTF8).getStr(); m_pSerializer->singleElementNS(XML_w, XML_permStart, FSNS(XML_w, XML_id), rId.getStr(), FSNS(XML_w, XML_ed), rName.getStr(), FSEND); } } // For construction of the special bookmark name template for permissions: // see, PermInsertPosition::createBookmarkName() // // Syntax: // - "permission-for-user::" // - "permission-for-group::" // void DocxAttributeOutput::DoWritePermissionTagEnd(const OUString & permission) { OUString permissionIdAndName; if (permission.startsWith("permission-for-group:", &permissionIdAndName)) { const sal_Int32 sparatorIndex = permissionIdAndName.indexOf(':'); const OUString permissionId = permissionIdAndName.copy(0, sparatorIndex); const OString rId = OUStringToOString(BookmarkToWord(permissionId), RTL_TEXTENCODING_UTF8).getStr(); m_pSerializer->singleElementNS(XML_w, XML_permEnd, FSNS(XML_w, XML_id), rId.getStr(), FSEND); } else // if (permission.startsWith("permission-for-user:", &permissionIdAndName)) { const sal_Int32 sparatorIndex = permissionIdAndName.indexOf(':'); const OUString permissionId = permissionIdAndName.copy(0, sparatorIndex); const OString rId = OUStringToOString(BookmarkToWord(permissionId), RTL_TEXTENCODING_UTF8).getStr(); m_pSerializer->singleElementNS(XML_w, XML_permEnd, FSNS(XML_w, XML_id), rId.getStr(), FSEND); } } /// Write the start permissions void DocxAttributeOutput::DoWritePermissionsStart() { for (const OUString & permission : m_rPermissionsStart) { DoWritePermissionTagStart(permission); } m_rPermissionsStart.clear(); } /// export the end permissions void DocxAttributeOutput::DoWritePermissionsEnd() { for (const OUString & permission : m_rPermissionsEnd) { DoWritePermissionTagEnd(permission); } m_rPermissionsEnd.clear(); } void DocxAttributeOutput::DoWriteAnnotationMarks() { // Write the start annotation marks for ( const auto & rName : m_rAnnotationMarksStart ) { // Output the annotation mark /* Ensure that the existing Annotation Marks are not overwritten as it causes discrepancy when DocxAttributeOutput::PostitField refers to this map & while mapping comment id's in document.xml & comment.xml. */ if ( m_rOpenedAnnotationMarksIds.end() == m_rOpenedAnnotationMarksIds.find( rName ) ) { const sal_Int32 nId = m_nNextAnnotationMarkId++; m_rOpenedAnnotationMarksIds[rName] = nId; m_pSerializer->singleElementNS( XML_w, XML_commentRangeStart, FSNS( XML_w, XML_id ), OString::number( nId ).getStr( ), FSEND ); m_sLastOpenedAnnotationMark = rName; } } m_rAnnotationMarksStart.clear(); // export the end annotation marks for ( const auto & rName : m_rAnnotationMarksEnd ) { // Get the id of the annotation mark std::map< OString, sal_Int32 >::iterator pPos = m_rOpenedAnnotationMarksIds.find( rName ); if ( pPos != m_rOpenedAnnotationMarksIds.end( ) ) { const sal_Int32 nId = ( *pPos ).second; m_pSerializer->singleElementNS( XML_w, XML_commentRangeEnd, FSNS( XML_w, XML_id ), OString::number( nId ).getStr( ), FSEND ); m_rOpenedAnnotationMarksIds.erase( rName ); m_pSerializer->startElementNS(XML_w, XML_r, FSEND); m_pSerializer->singleElementNS( XML_w, XML_commentReference, FSNS( XML_w, XML_id ), OString::number( nId ).getStr(), FSEND ); m_pSerializer->endElementNS(XML_w, XML_r); } } m_rAnnotationMarksEnd.clear(); } void DocxAttributeOutput::WriteFFData( const FieldInfos& rInfos ) { const ::sw::mark::IFieldmark& rFieldmark = *rInfos.pFieldmark; if ( rInfos.eType == ww::eFORMDROPDOWN ) { uno::Sequence< OUString> vListEntries; OUString sName, sSelected; FieldMarkParamsHelper params( rFieldmark ); params.extractParam( ODF_FORMDROPDOWN_LISTENTRY, vListEntries ); sName = params.getName(); sal_Int32 nSelectedIndex = 0; if ( params.extractParam( ODF_FORMDROPDOWN_RESULT, nSelectedIndex ) ) { if (nSelectedIndex < vListEntries.getLength() ) sSelected = vListEntries[ nSelectedIndex ]; } GetExport().DoComboBox( sName, OUString(), OUString(), sSelected, vListEntries ); } else if ( rInfos.eType == ww::eFORMCHECKBOX ) { OUString sName; bool bChecked = false; FieldMarkParamsHelper params( rFieldmark ); params.extractParam( ODF_FORMCHECKBOX_NAME, sName ); const sw::mark::ICheckboxFieldmark* pCheckboxFm = dynamic_cast(&rFieldmark); if ( pCheckboxFm && pCheckboxFm->IsChecked() ) bChecked = true; FFDataWriterHelper ffdataOut( m_pSerializer ); ffdataOut.WriteFormCheckbox( sName, bChecked ); } else if ( rInfos.eType == ww::eFORMTEXT ) { FieldMarkParamsHelper params( rFieldmark ); FFDataWriterHelper ffdataOut( m_pSerializer ); ffdataOut.WriteFormText( params.getName() ); } } void DocxAttributeOutput::StartField_Impl( const SwTextNode* pNode, sal_Int32 nPos, FieldInfos const & rInfos, bool bWriteRun ) { if ( rInfos.pField && rInfos.eType == ww::eUNKNOWN ) { // Expand unsupported fields RunText( rInfos.pField->GetFieldName() ); } else if ( rInfos.eType != ww::eNONE ) // HYPERLINK fields are just commands { if ( bWriteRun ) m_pSerializer->startElementNS( XML_w, XML_r, FSEND ); if ( rInfos.eType == ww::eFORMDROPDOWN ) { m_pSerializer->startElementNS( XML_w, XML_fldChar, FSNS( XML_w, XML_fldCharType ), "begin", FSEND ); if ( rInfos.pFieldmark && !rInfos.pField ) WriteFFData( rInfos ); if ( rInfos.pField ) { const SwDropDownField& rField2 = *static_cast(rInfos.pField.get()); uno::Sequence aItems = rField2.GetItemSequence(); GetExport().DoComboBox(rField2.GetName(), rField2.GetHelp(), rField2.GetToolTip(), rField2.GetSelectedItem(), aItems); } m_pSerializer->endElementNS( XML_w, XML_fldChar ); if ( bWriteRun ) m_pSerializer->endElementNS( XML_w, XML_r ); if ( !rInfos.pField ) CmdField_Impl( pNode, nPos, rInfos, bWriteRun ); } else { // Write the field start if ( rInfos.pField && rInfos.pField->GetSubType() & FIXEDFLD ) { m_pSerializer->startElementNS( XML_w, XML_fldChar, FSNS( XML_w, XML_fldCharType ), "begin", FSNS( XML_w, XML_fldLock ), "true", FSEND ); } else { m_pSerializer->startElementNS( XML_w, XML_fldChar, FSNS( XML_w, XML_fldCharType ), "begin", FSEND ); } if ( rInfos.pFieldmark ) WriteFFData( rInfos ); m_pSerializer->endElementNS( XML_w, XML_fldChar ); if ( bWriteRun ) m_pSerializer->endElementNS( XML_w, XML_r ); // The hyperlinks fields can't be expanded: the value is // normally in the text run if ( !rInfos.pField ) CmdField_Impl( pNode, nPos, rInfos, bWriteRun ); } } } void DocxAttributeOutput::DoWriteCmd( const OUString& rCmd ) { OUString sCmd = rCmd.trim(); if (sCmd.startsWith("SEQ")) { OUString sSeqName = msfilter::util::findQuotedText(sCmd, "SEQ ", '\\').trim(); m_aSeqBookmarksNames[sSeqName].push_back(m_sLastOpenedBookmark); } // Write the Field command m_pSerializer->startElementNS( XML_w, XML_instrText, FSEND ); m_pSerializer->writeEscaped( rCmd ); m_pSerializer->endElementNS( XML_w, XML_instrText ); } void DocxAttributeOutput::CmdField_Impl( const SwTextNode* pNode, sal_Int32 nPos, FieldInfos const & rInfos, bool bWriteRun ) { // Write the Field instruction { if ( bWriteRun ) { m_pSerializer->startElementNS( XML_w, XML_r, FSEND ); DoWriteFieldRunProperties( pNode, nPos ); } sal_Int32 nNbToken = comphelper::string::getTokenCount(rInfos.sCmd, '\t'); for ( sal_Int32 i = 0; i < nNbToken; i++ ) { OUString sToken = rInfos.sCmd.getToken( i, '\t' ); if ( rInfos.eType == ww::eCREATEDATE || rInfos.eType == ww::eSAVEDATE || rInfos.eType == ww::ePRINTDATE || rInfos.eType == ww::eDATE || rInfos.eType == ww::eTIME ) { sToken = sToken.replaceAll("NNNN", "dddd"); sToken = sToken.replaceAll("NN", "ddd"); } // Write the Field command DoWriteCmd( sToken ); // Replace tabs by if ( i < ( nNbToken - 1 ) ) RunText( "\t" ); } if ( bWriteRun ) { m_pSerializer->endElementNS( XML_w, XML_r ); } } // Write the Field separator { if ( bWriteRun ) { m_pSerializer->startElementNS( XML_w, XML_r, FSEND ); DoWriteFieldRunProperties( pNode, nPos ); } m_pSerializer->singleElementNS( XML_w, XML_fldChar, FSNS( XML_w, XML_fldCharType ), "separate", FSEND ); if ( bWriteRun ) { m_pSerializer->endElementNS( XML_w, XML_r ); } } } /// Writes properties for run that is used to separate field implementation. /// There are several runs are used: /// /// ///