The bugdoc contains a format redline that changes the character style
from Strong to Quote and the font size from 24 to 36. Exporting to ODT,
loading the ODT and rejecting the redline, the font size is set to 24,
but the character style is set to default instead of Strong.
What happens is that the item set of the format redline contains the
necessary SvxFontHeightItem, but contains no SwFormatCharFormat. This is
because the ODT export writes no parents for char automatic styles,
because char formatting outside the redline table has 2 pool items for
such char formatting: a dedicated SwFormatCharFormat for the char style
and a separate SwFormatAutoFormat for other char props, so the ODT
export "knows" to ignore the autoformat parent and creates this markup:
<text:span text:style-name="...named style..."><text:span text:style-name="...automatic style...">t</text:span></text:span>
Fix the problem by explicitly exporting the parent style name when we
write an automatic style that is referred from the redline table (and
not from the body text). This is similar to what e.g. "frame" autostyles
already do, which also have parents. The result is this markup:
<style:style style:name="..." style:family="text" style:parent-style-name="Strong_20_Emphasis">
<style:text-properties fo:font-size="24pt"/>
...
<text:format-change loext:style-name="...">
which contains both the named character style name and the automatic
style, describing the direct formatting.
The ODT import side still needs fixing.
Change-Id: I19a67cf5e2c538cfcfa1b13097241954667d2c89
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/190181
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins
711 lines
22 KiB
C++
711 lines
22 KiB
C++
/* -*- 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 "XMLRedlineExport.hxx"
|
|
#include <o3tl/any.hxx>
|
|
#include <tools/debug.hxx>
|
|
#include <rtl/ustring.hxx>
|
|
#include <rtl/ustrbuf.hxx>
|
|
#include <sal/log.hxx>
|
|
#include <osl/diagnose.h>
|
|
#include <com/sun/star/frame/XModel.hpp>
|
|
#include <com/sun/star/beans/XPropertySet.hpp>
|
|
#include <com/sun/star/beans/UnknownPropertyException.hpp>
|
|
#include <com/sun/star/container/XEnumerationAccess.hpp>
|
|
|
|
#include <com/sun/star/container/XEnumeration.hpp>
|
|
#include <com/sun/star/document/XRedlinesSupplier.hpp>
|
|
#include <com/sun/star/text/XText.hpp>
|
|
#include <com/sun/star/text/XTextContent.hpp>
|
|
#include <com/sun/star/text/XTextSection.hpp>
|
|
#include <com/sun/star/util/DateTime.hpp>
|
|
|
|
#include <sax/tools/converter.hxx>
|
|
|
|
#include <xmloff/xmltoken.hxx>
|
|
#include <xmloff/xmlnamespace.hxx>
|
|
#include <xmloff/xmlexp.hxx>
|
|
#include <xmloff/xmluconv.hxx>
|
|
#include <unotools/securityoptions.hxx>
|
|
#include <comphelper/sequenceashashmap.hxx>
|
|
|
|
|
|
using namespace ::com::sun::star;
|
|
using namespace ::xmloff::token;
|
|
|
|
using ::com::sun::star::beans::PropertyValue;
|
|
using ::com::sun::star::beans::XPropertySet;
|
|
using ::com::sun::star::beans::UnknownPropertyException;
|
|
using ::com::sun::star::document::XRedlinesSupplier;
|
|
using ::com::sun::star::container::XEnumerationAccess;
|
|
using ::com::sun::star::container::XEnumeration;
|
|
using ::com::sun::star::text::XText;
|
|
using ::com::sun::star::text::XTextContent;
|
|
using ::com::sun::star::text::XTextSection;
|
|
using ::com::sun::star::uno::Any;
|
|
using ::com::sun::star::uno::Reference;
|
|
using ::com::sun::star::uno::Sequence;
|
|
|
|
|
|
XMLRedlineExport::XMLRedlineExport(SvXMLExport& rExp)
|
|
: sDeletion(GetXMLToken(XML_DELETION))
|
|
, sFormatChange(GetXMLToken(XML_FORMAT_CHANGE))
|
|
, sInsertion(GetXMLToken(XML_INSERTION))
|
|
, rExport(rExp)
|
|
, pCurrentChangesList(nullptr)
|
|
{
|
|
}
|
|
|
|
|
|
XMLRedlineExport::~XMLRedlineExport()
|
|
{
|
|
}
|
|
|
|
|
|
void XMLRedlineExport::ExportChange(
|
|
const Reference<XPropertySet> & rPropSet,
|
|
bool bAutoStyle)
|
|
{
|
|
if (bAutoStyle)
|
|
{
|
|
// For the headers/footers, we have to collect the autostyles
|
|
// here. For the general case, however, it's better to collect
|
|
// the autostyles by iterating over the global redline
|
|
// list. So that's what we do: Here, we collect autostyles
|
|
// only if we have no current list of changes. For the
|
|
// main-document case, the autostyles are collected in
|
|
// ExportChangesListAutoStyles().
|
|
if (pCurrentChangesList != nullptr)
|
|
ExportChangeAutoStyle(rPropSet);
|
|
}
|
|
else
|
|
{
|
|
ExportChangeInline(rPropSet);
|
|
}
|
|
}
|
|
|
|
|
|
void XMLRedlineExport::ExportChangesList(bool bAutoStyles)
|
|
{
|
|
if (bAutoStyles)
|
|
{
|
|
ExportChangesListAutoStyles();
|
|
}
|
|
else
|
|
{
|
|
ExportChangesListElements();
|
|
}
|
|
}
|
|
|
|
|
|
void XMLRedlineExport::ExportChangesList(
|
|
const Reference<XText> & rText,
|
|
bool bAutoStyles)
|
|
{
|
|
// in the header/footer case, auto styles are collected from the
|
|
// inline change elements.
|
|
if (bAutoStyles)
|
|
return;
|
|
|
|
// look for changes list for this XText
|
|
ChangesMapType::iterator aFind = aChangeMap.find(rText);
|
|
if (aFind == aChangeMap.end())
|
|
return;
|
|
|
|
ChangesVectorType& rChangesList = aFind->second;
|
|
|
|
// export only if changes are found
|
|
if (rChangesList.empty())
|
|
return;
|
|
|
|
// changes container element
|
|
SvXMLElementExport aChanges(rExport, XML_NAMESPACE_TEXT,
|
|
XML_TRACKED_CHANGES,
|
|
true, true);
|
|
|
|
// iterate over changes list
|
|
for (auto const& change : rChangesList)
|
|
{
|
|
ExportChangedRegion(change);
|
|
}
|
|
// else: changes list empty -> ignore
|
|
// else: no changes list found -> empty
|
|
}
|
|
|
|
void XMLRedlineExport::SetCurrentXText(
|
|
const Reference<XText> & rText)
|
|
{
|
|
if (rText.is())
|
|
{
|
|
// look for appropriate list in map; use the found one, or create new
|
|
ChangesMapType::iterator aIter = aChangeMap.find(rText);
|
|
if (aIter == aChangeMap.end())
|
|
{
|
|
auto rv = aChangeMap.emplace(std::piecewise_construct, std::forward_as_tuple(rText), std::forward_as_tuple());
|
|
pCurrentChangesList = &rv.first->second;
|
|
}
|
|
else
|
|
pCurrentChangesList = &aIter->second;
|
|
}
|
|
else
|
|
{
|
|
// don't record changes
|
|
SetCurrentXText();
|
|
}
|
|
}
|
|
|
|
void XMLRedlineExport::SetCurrentXText()
|
|
{
|
|
pCurrentChangesList = nullptr;
|
|
}
|
|
|
|
|
|
void XMLRedlineExport::ExportChangesListElements()
|
|
{
|
|
// get redlines (aka tracked changes) from the model
|
|
Reference<XRedlinesSupplier> xSupplier(rExport.GetModel(), uno::UNO_QUERY);
|
|
if (!xSupplier.is())
|
|
return;
|
|
|
|
Reference<XEnumerationAccess> aEnumAccess = xSupplier->getRedlines();
|
|
|
|
// redline protection key
|
|
Reference<XPropertySet> aDocPropertySet( rExport.GetModel(),
|
|
uno::UNO_QUERY );
|
|
// redlining enabled?
|
|
bool bEnabled = *o3tl::doAccess<bool>(aDocPropertySet->getPropertyValue(
|
|
u"RecordChanges"_ustr ));
|
|
|
|
// only export if we have redlines or attributes
|
|
if ( !(aEnumAccess->hasElements() || bEnabled) )
|
|
return;
|
|
|
|
|
|
// export only if we have changes, but tracking is not enabled
|
|
if ( !bEnabled != !aEnumAccess->hasElements() )
|
|
{
|
|
rExport.AddAttribute(
|
|
XML_NAMESPACE_TEXT, XML_TRACK_CHANGES,
|
|
bEnabled ? XML_TRUE : XML_FALSE );
|
|
}
|
|
|
|
// changes container element
|
|
SvXMLElementExport aChanges(rExport, XML_NAMESPACE_TEXT,
|
|
XML_TRACKED_CHANGES,
|
|
true, true);
|
|
|
|
// get enumeration and iterate over elements
|
|
Reference<XEnumeration> aEnum = aEnumAccess->createEnumeration();
|
|
while (aEnum->hasMoreElements())
|
|
{
|
|
Any aAny = aEnum->nextElement();
|
|
Reference<XPropertySet> xPropSet;
|
|
aAny >>= xPropSet;
|
|
|
|
DBG_ASSERT(xPropSet.is(),
|
|
"can't get XPropertySet; skipping Redline");
|
|
if (xPropSet.is())
|
|
{
|
|
// export only if not in header or footer
|
|
// (those must be exported with their XText)
|
|
aAny = xPropSet->getPropertyValue(u"IsInHeaderFooter"_ustr);
|
|
if (! *o3tl::doAccess<bool>(aAny))
|
|
{
|
|
// and finally, export change
|
|
ExportChangedRegion(xPropSet);
|
|
}
|
|
}
|
|
// else: no XPropertySet -> no export
|
|
}
|
|
// else: no redlines -> no export
|
|
// else: no XRedlineSupplier -> no export
|
|
}
|
|
|
|
void XMLRedlineExport::ExportChangeAutoStyle(
|
|
const Reference<XPropertySet> & rPropSet)
|
|
{
|
|
// record change (if changes should be recorded)
|
|
if (nullptr != pCurrentChangesList)
|
|
{
|
|
// put redline in list if it's collapsed or the redline start
|
|
Any aIsStart = rPropSet->getPropertyValue(u"IsStart"_ustr);
|
|
Any aIsCollapsed = rPropSet->getPropertyValue(u"IsCollapsed"_ustr);
|
|
|
|
if ( *o3tl::doAccess<bool>(aIsStart) ||
|
|
*o3tl::doAccess<bool>(aIsCollapsed) )
|
|
pCurrentChangesList->push_back(rPropSet);
|
|
}
|
|
|
|
// get XText for export of redline auto styles
|
|
Any aAny = rPropSet->getPropertyValue(u"RedlineText"_ustr);
|
|
Reference<XText> xText;
|
|
aAny >>= xText;
|
|
if (xText.is())
|
|
{
|
|
// export the auto styles
|
|
rExport.GetTextParagraphExport()->collectTextAutoStyles(xText);
|
|
}
|
|
|
|
// See if the format redline has an autostyle for old direct formatting: if so, export that as
|
|
// an autostyle.
|
|
aAny = rPropSet->getPropertyValue(u"RedlineAutoFormat"_ustr);
|
|
uno::Reference<beans::XPropertySet> xAutoFormat;
|
|
aAny >>= xAutoFormat;
|
|
if (xAutoFormat.is())
|
|
{
|
|
// Check for parent style when declaring the automatic style.
|
|
rExport.GetTextParagraphExport()->Add(XmlStyleFamily::TEXT_TEXT, xAutoFormat,
|
|
/*aAddStates=*/{},
|
|
/*bCheckParent=*/true);
|
|
}
|
|
}
|
|
|
|
void XMLRedlineExport::ExportChangesListAutoStyles()
|
|
{
|
|
// get redlines (aka tracked changes) from the model
|
|
Reference<XRedlinesSupplier> xSupplier(rExport.GetModel(), uno::UNO_QUERY);
|
|
if (!xSupplier.is())
|
|
return;
|
|
|
|
Reference<XEnumerationAccess> aEnumAccess = xSupplier->getRedlines();
|
|
|
|
// only export if we actually have redlines
|
|
if (!aEnumAccess->hasElements())
|
|
return;
|
|
|
|
// get enumeration and iterate over elements
|
|
Reference<XEnumeration> aEnum = aEnumAccess->createEnumeration();
|
|
while (aEnum->hasMoreElements())
|
|
{
|
|
Any aAny = aEnum->nextElement();
|
|
Reference<XPropertySet> xPropSet;
|
|
aAny >>= xPropSet;
|
|
|
|
DBG_ASSERT(xPropSet.is(),
|
|
"can't get XPropertySet; skipping Redline");
|
|
if (xPropSet.is())
|
|
{
|
|
|
|
// export only if not in header or footer
|
|
// (those must be exported with their XText)
|
|
aAny = xPropSet->getPropertyValue(u"IsInHeaderFooter"_ustr);
|
|
if (! *o3tl::doAccess<bool>(aAny))
|
|
{
|
|
ExportChangeAutoStyle(xPropSet);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void XMLRedlineExport::ExportChangeInline(
|
|
const Reference<XPropertySet> & rPropSet)
|
|
{
|
|
// determine element name (depending on collapsed, start/end)
|
|
enum XMLTokenEnum eElement = XML_TOKEN_INVALID;
|
|
Any aAny = rPropSet->getPropertyValue(u"IsCollapsed"_ustr);
|
|
bool bCollapsed = *o3tl::doAccess<bool>(aAny);
|
|
if (bCollapsed)
|
|
{
|
|
eElement = XML_CHANGE;
|
|
}
|
|
else
|
|
{
|
|
aAny = rPropSet->getPropertyValue(u"IsStart"_ustr);
|
|
const bool bStart = *o3tl::doAccess<bool>(aAny);
|
|
eElement = bStart ? XML_CHANGE_START : XML_CHANGE_END;
|
|
}
|
|
|
|
if (XML_TOKEN_INVALID != eElement)
|
|
{
|
|
// we always need the ID
|
|
rExport.AddAttribute(XML_NAMESPACE_TEXT, XML_CHANGE_ID,
|
|
GetRedlineID(rPropSet));
|
|
|
|
// export the element (no whitespace because we're in the text body)
|
|
SvXMLElementExport aChangeElem(rExport, XML_NAMESPACE_TEXT,
|
|
eElement, false, false);
|
|
}
|
|
}
|
|
|
|
|
|
void XMLRedlineExport::ExportChangedRegion(
|
|
const Reference<XPropertySet> & rPropSet)
|
|
{
|
|
// Redline-ID
|
|
rExport.AddAttributeIdLegacy(XML_NAMESPACE_TEXT, GetRedlineID(rPropSet));
|
|
|
|
// merge-last-paragraph
|
|
Any aAny = rPropSet->getPropertyValue(u"MergeLastPara"_ustr);
|
|
if( ! *o3tl::doAccess<bool>(aAny) )
|
|
rExport.AddAttribute(XML_NAMESPACE_TEXT, XML_MERGE_LAST_PARAGRAPH,
|
|
XML_FALSE);
|
|
|
|
// export change region element
|
|
SvXMLElementExport aChangedRegion(rExport, XML_NAMESPACE_TEXT,
|
|
XML_CHANGED_REGION, true, true);
|
|
|
|
|
|
// scope for (first) change element
|
|
{
|
|
aAny = rPropSet->getPropertyValue(u"RedlineType"_ustr);
|
|
OUString sType;
|
|
aAny >>= sType;
|
|
|
|
// See if the format redline has an autostyle for old direct formatting: if so, refer to the
|
|
// already exported autostyle.
|
|
aAny = rPropSet->getPropertyValue(u"RedlineAutoFormat"_ustr);
|
|
uno::Reference<beans::XPropertySet> xAutoStyle;
|
|
aAny >>= xAutoStyle;
|
|
if (xAutoStyle.is())
|
|
{
|
|
bool bIsUICharStyle;
|
|
bool bHasAutoStyle;
|
|
OUString aParentName;
|
|
uno::Reference<beans::XPropertySetInfo> xPropSetInfo = xAutoStyle->getPropertySetInfo();
|
|
if (xPropSetInfo->hasPropertyByName("CharStyleName"))
|
|
{
|
|
// Consider parent style when referring to the declared automatic style.
|
|
xAutoStyle->getPropertyValue("CharStyleName") >>= aParentName;
|
|
}
|
|
OUString sStyle = rExport.GetTextParagraphExport()->FindTextStyle(
|
|
xAutoStyle, bIsUICharStyle, bHasAutoStyle, /*pAddState=*/nullptr, &aParentName);
|
|
if (!sStyle.isEmpty())
|
|
{
|
|
rExport.AddAttribute(XML_NAMESPACE_LO_EXT, XML_STYLE_NAME, rExport.EncodeStyleName(sStyle));
|
|
}
|
|
}
|
|
|
|
SvXMLElementExport aChange(rExport, XML_NAMESPACE_TEXT,
|
|
ConvertTypeName(sType), true, true);
|
|
|
|
ExportChangeInfo(rPropSet);
|
|
|
|
// get XText from the redline and export (if the XText exists)
|
|
aAny = rPropSet->getPropertyValue(u"RedlineText"_ustr);
|
|
Reference<XText> xText;
|
|
aAny >>= xText;
|
|
// Avoid double export for format-on-delete: no write on format, only on delete.
|
|
if (xText.is() && sType == u"Delete")
|
|
{
|
|
rExport.GetTextParagraphExport()->exportText(xText);
|
|
// default parameters: bProgress, bExportParagraph ???
|
|
}
|
|
// else: no text interface -> content is inline and will
|
|
// be exported there
|
|
}
|
|
|
|
// changed change? Hierarchical changes can only be two levels
|
|
// deep. Here we check for the second level.
|
|
aAny = rPropSet->getPropertyValue(u"RedlineSuccessorData"_ustr);
|
|
Sequence<PropertyValue> aSuccessorData;
|
|
aAny >>= aSuccessorData;
|
|
|
|
// if we actually got a hierarchical change, make element and
|
|
// process change info
|
|
if (aSuccessorData.hasElements())
|
|
{
|
|
// Look up the type of the change. In practice this is always insert or delete, other types
|
|
// can't have additional redlines on top of them.
|
|
OUString sType;
|
|
comphelper::SequenceAsHashMap aMap(aSuccessorData);
|
|
auto it = aMap.find("RedlineType");
|
|
if (it != aMap.end())
|
|
{
|
|
it->second >>= sType;
|
|
}
|
|
SvXMLElementExport aSecondChangeElem(
|
|
rExport, XML_NAMESPACE_TEXT, ConvertTypeName(sType),
|
|
true, true);
|
|
|
|
ExportChangeInfo(aSuccessorData);
|
|
it = aMap.find("RedlineText");
|
|
if (it != aMap.end())
|
|
{
|
|
// Delete has its own content outside the body text: export it here.
|
|
uno::Reference<text::XText> xText;
|
|
it->second >>= xText;
|
|
if (xText.is())
|
|
{
|
|
rExport.GetTextParagraphExport()->exportText(xText);
|
|
}
|
|
}
|
|
}
|
|
// else: no hierarchical change
|
|
}
|
|
|
|
|
|
OUString const & XMLRedlineExport::ConvertTypeName(
|
|
std::u16string_view sApiName)
|
|
{
|
|
if (sApiName == u"Delete")
|
|
{
|
|
return sDeletion;
|
|
}
|
|
else if (sApiName == u"Insert")
|
|
{
|
|
return sInsertion;
|
|
}
|
|
else if (sApiName == u"Format")
|
|
{
|
|
return sFormatChange;
|
|
}
|
|
else
|
|
{
|
|
OSL_FAIL("unknown redline type");
|
|
static constexpr OUString sUnknownChange(u"UnknownChange"_ustr);
|
|
return sUnknownChange;
|
|
}
|
|
}
|
|
|
|
|
|
/** Create a Redline-ID */
|
|
OUString XMLRedlineExport::GetRedlineID(
|
|
const Reference<XPropertySet> & rPropSet)
|
|
{
|
|
Any aAny = rPropSet->getPropertyValue(u"RedlineIdentifier"_ustr);
|
|
OUString sTmp;
|
|
aAny >>= sTmp;
|
|
|
|
return "ct" + sTmp;
|
|
}
|
|
|
|
|
|
void XMLRedlineExport::ExportChangeInfo(
|
|
const Reference<XPropertySet> & rPropSet)
|
|
{
|
|
bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
|
|
SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ) && !SvtSecurityOptions::IsOptionSet(
|
|
SvtSecurityOptions::EOption::DocWarnKeepRedlineInfo);
|
|
|
|
SvXMLElementExport aChangeInfo(rExport, XML_NAMESPACE_OFFICE,
|
|
XML_CHANGE_INFO, true, true);
|
|
|
|
Any aAny = rPropSet->getPropertyValue(u"RedlineAuthor"_ustr);
|
|
OUString sTmp;
|
|
aAny >>= sTmp;
|
|
if (!sTmp.isEmpty())
|
|
{
|
|
SvXMLElementExport aCreatorElem( rExport, XML_NAMESPACE_DC,
|
|
XML_CREATOR, true,
|
|
false );
|
|
rExport.Characters(bRemovePersonalInfo
|
|
? "Author" + OUString::number(rExport.GetInfoID(sTmp))
|
|
: sTmp );
|
|
}
|
|
|
|
aAny = rPropSet->getPropertyValue(u"RedlineMovedID"_ustr);
|
|
sal_uInt32 nTmp(0);
|
|
aAny >>= nTmp;
|
|
if (nTmp > 1)
|
|
{
|
|
SvXMLElementExport aCreatorElem(rExport, XML_NAMESPACE_LO_EXT, XML_MOVE_ID, true, false);
|
|
rExport.Characters( OUString::number( nTmp ) );
|
|
}
|
|
|
|
aAny = rPropSet->getPropertyValue(u"RedlineDateTime"_ustr);
|
|
util::DateTime aDateTime;
|
|
aAny >>= aDateTime;
|
|
{
|
|
OUStringBuffer sBuf;
|
|
::sax::Converter::convertDateTime(sBuf, bRemovePersonalInfo
|
|
? util::DateTime(0, 0, 0, 0, 1, 1, 1970, true) // Epoch time
|
|
: aDateTime, nullptr, true);
|
|
SvXMLElementExport aDateElem( rExport, XML_NAMESPACE_DC,
|
|
XML_DATE, true,
|
|
false );
|
|
rExport.Characters(sBuf.makeStringAndClear());
|
|
}
|
|
|
|
// comment as <text:p> sequence
|
|
aAny = rPropSet->getPropertyValue(u"RedlineComment"_ustr);
|
|
aAny >>= sTmp;
|
|
WriteComment( sTmp );
|
|
}
|
|
|
|
// write RedlineSuccessorData
|
|
void XMLRedlineExport::ExportChangeInfo(
|
|
const Sequence<PropertyValue> & rPropertyValues)
|
|
{
|
|
OUString sComment;
|
|
bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
|
|
SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ) && !SvtSecurityOptions::IsOptionSet(
|
|
SvtSecurityOptions::EOption::DocWarnKeepRedlineInfo);
|
|
|
|
SvXMLElementExport aChangeInfo(rExport, XML_NAMESPACE_OFFICE,
|
|
XML_CHANGE_INFO, true, true);
|
|
|
|
for(const PropertyValue& rVal : rPropertyValues)
|
|
{
|
|
if( rVal.Name == "RedlineAuthor" )
|
|
{
|
|
OUString sTmp;
|
|
rVal.Value >>= sTmp;
|
|
if (!sTmp.isEmpty())
|
|
{
|
|
SvXMLElementExport aCreatorElem( rExport, XML_NAMESPACE_DC,
|
|
XML_CREATOR, true,
|
|
false );
|
|
rExport.Characters(bRemovePersonalInfo
|
|
? "Author" + OUString::number(rExport.GetInfoID(sTmp))
|
|
: sTmp );
|
|
}
|
|
}
|
|
else if( rVal.Name == "RedlineComment" )
|
|
{
|
|
rVal.Value >>= sComment;
|
|
}
|
|
else if( rVal.Name == "RedlineDateTime" )
|
|
{
|
|
util::DateTime aDateTime;
|
|
rVal.Value >>= aDateTime;
|
|
OUStringBuffer sBuf;
|
|
::sax::Converter::convertDateTime(sBuf, bRemovePersonalInfo
|
|
? util::DateTime(0, 0, 0, 0, 1, 1, 1970, true) // Epoch time
|
|
: aDateTime, nullptr, true);
|
|
SvXMLElementExport aDateElem( rExport, XML_NAMESPACE_DC,
|
|
XML_DATE, true,
|
|
false );
|
|
rExport.Characters(sBuf.makeStringAndClear());
|
|
}
|
|
else if( rVal.Name == "RedlineType" )
|
|
{
|
|
// check if this has one of the expected types
|
|
OUString sTmp;
|
|
rVal.Value >>= sTmp;
|
|
DBG_ASSERT(sTmp == "Insert" || sTmp == "Delete",
|
|
"hierarchical change must be insertion");
|
|
}
|
|
// else: unknown value -> ignore
|
|
}
|
|
|
|
// finally write comment paragraphs
|
|
WriteComment( sComment );
|
|
}
|
|
|
|
void XMLRedlineExport::ExportStartOrEndRedline(
|
|
const Reference<XPropertySet> & rPropSet,
|
|
bool bStart)
|
|
{
|
|
if( ! rPropSet.is() )
|
|
return;
|
|
|
|
// get appropriate (start or end) property
|
|
Any aAny;
|
|
try
|
|
{
|
|
aAny = rPropSet->getPropertyValue(bStart ? u"StartRedline"_ustr : u"EndRedline"_ustr);
|
|
}
|
|
catch(const UnknownPropertyException&)
|
|
{
|
|
// If we don't have the property, there's nothing to do.
|
|
return;
|
|
}
|
|
|
|
Sequence<PropertyValue> aValues;
|
|
aAny >>= aValues;
|
|
|
|
// seek for redline properties
|
|
bool bIsCollapsed = false;
|
|
bool bIsStart = true;
|
|
OUString sId;
|
|
bool bIdOK = false; // have we seen an ID?
|
|
for (const auto& rValue : aValues)
|
|
{
|
|
if (rValue.Name == "RedlineIdentifier")
|
|
{
|
|
rValue.Value >>= sId;
|
|
bIdOK = true;
|
|
}
|
|
else if (rValue.Name == "IsCollapsed")
|
|
{
|
|
bIsCollapsed = *o3tl::doAccess<bool>(rValue.Value);
|
|
}
|
|
else if (rValue.Name == "IsStart")
|
|
{
|
|
bIsStart = *o3tl::doAccess<bool>(rValue.Value);
|
|
}
|
|
}
|
|
|
|
if( !bIdOK )
|
|
return;
|
|
|
|
SAL_WARN_IF( sId.isEmpty(), "xmloff", "Redlines must have IDs" );
|
|
|
|
// TODO: use GetRedlineID or eliminate that function
|
|
rExport.AddAttribute(XML_NAMESPACE_TEXT, XML_CHANGE_ID,
|
|
"ct" + sId);
|
|
|
|
// export the element
|
|
// (whitespace because we're not inside paragraphs)
|
|
SvXMLElementExport aChangeElem(
|
|
rExport, XML_NAMESPACE_TEXT,
|
|
bIsCollapsed ? XML_CHANGE :
|
|
( bIsStart ? XML_CHANGE_START : XML_CHANGE_END ),
|
|
true, true);
|
|
}
|
|
|
|
void XMLRedlineExport::ExportStartOrEndRedline(
|
|
const Reference<XTextContent> & rContent,
|
|
bool bStart)
|
|
{
|
|
Reference<XPropertySet> xPropSet(rContent, uno::UNO_QUERY);
|
|
if (xPropSet.is())
|
|
{
|
|
ExportStartOrEndRedline(xPropSet, bStart);
|
|
}
|
|
else
|
|
{
|
|
OSL_FAIL("XPropertySet expected");
|
|
}
|
|
}
|
|
|
|
void XMLRedlineExport::ExportStartOrEndRedline(
|
|
const Reference<XTextSection> & rSection,
|
|
bool bStart)
|
|
{
|
|
Reference<XPropertySet> xPropSet(rSection, uno::UNO_QUERY);
|
|
if (xPropSet.is())
|
|
{
|
|
ExportStartOrEndRedline(xPropSet, bStart);
|
|
}
|
|
else
|
|
{
|
|
OSL_FAIL("XPropertySet expected");
|
|
}
|
|
}
|
|
|
|
void XMLRedlineExport::WriteComment(std::u16string_view rComment)
|
|
{
|
|
if (rComment.empty())
|
|
return;
|
|
|
|
// iterate over all string-pieces separated by return (0x0a) and
|
|
// put each inside a paragraph element.
|
|
SvXMLTokenEnumerator aEnumerator(rComment, char(0x0a));
|
|
std::u16string_view aSubString;
|
|
while (aEnumerator.getNextToken(aSubString))
|
|
{
|
|
SvXMLElementExport aParagraph(
|
|
rExport, XML_NAMESPACE_TEXT, XML_P, true, false);
|
|
rExport.Characters(OUString(aSubString));
|
|
}
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|