bump product version to 5.0.4.1
[LibreOffice.git] / xmloff / source / text / XMLRedlineExport.cxx
blob899f6a509b706b0177b45f6fdb87bffaf28acfa0
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include "XMLRedlineExport.hxx"
21 #include <tools/debug.hxx>
22 #include <rtl/ustring.hxx>
23 #include <rtl/ustrbuf.hxx>
24 #include <com/sun/star/beans/XPropertySet.hpp>
25 #include <com/sun/star/beans/UnknownPropertyException.hpp>
26 #include <com/sun/star/container/XEnumerationAccess.hpp>
28 #include <com/sun/star/container/XEnumeration.hpp>
29 #include <com/sun/star/document/XRedlinesSupplier.hpp>
30 #include <com/sun/star/text/XText.hpp>
31 #include <com/sun/star/text/XTextContent.hpp>
32 #include <com/sun/star/text/XTextSection.hpp>
33 #include <com/sun/star/util/DateTime.hpp>
35 #include <sax/tools/converter.hxx>
37 #include <xmloff/xmltoken.hxx>
38 #include <xmloff/xmlnmspe.hxx>
39 #include <xmloff/xmlexp.hxx>
40 #include <xmloff/xmluconv.hxx>
43 using namespace ::com::sun::star;
44 using namespace ::xmloff::token;
46 using ::com::sun::star::beans::PropertyValue;
47 using ::com::sun::star::beans::XPropertySet;
48 using ::com::sun::star::beans::UnknownPropertyException;
49 using ::com::sun::star::document::XRedlinesSupplier;
50 using ::com::sun::star::container::XEnumerationAccess;
51 using ::com::sun::star::container::XEnumeration;
52 using ::com::sun::star::text::XText;
53 using ::com::sun::star::text::XTextContent;
54 using ::com::sun::star::text::XTextSection;
55 using ::com::sun::star::uno::Any;
56 using ::com::sun::star::uno::Reference;
57 using ::com::sun::star::uno::Sequence;
58 using ::std::list;
61 XMLRedlineExport::XMLRedlineExport(SvXMLExport& rExp)
62 : sDelete("Delete")
63 , sDeletion(GetXMLToken(XML_DELETION))
64 , sFormat("Format")
65 , sFormatChange(GetXMLToken(XML_FORMAT_CHANGE))
66 , sInsert("Insert")
67 , sInsertion(GetXMLToken(XML_INSERTION))
68 , sIsCollapsed("IsCollapsed")
69 , sIsStart("IsStart")
70 , sRedlineAuthor("RedlineAuthor")
71 , sRedlineComment("RedlineComment")
72 , sRedlineDateTime("RedlineDateTime")
73 , sRedlineSuccessorData("RedlineSuccessorData")
74 , sRedlineText("RedlineText")
75 , sRedlineType("RedlineType")
76 , sUnknownChange("UnknownChange")
77 , sStartRedline("StartRedline")
78 , sEndRedline("EndRedline")
79 , sRedlineIdentifier("RedlineIdentifier")
80 , sIsInHeaderFooter("IsInHeaderFooter")
81 , sRecordChanges("RecordChanges")
82 , sMergeLastPara("MergeLastPara")
83 , sChangePrefix("ct")
84 , rExport(rExp)
85 , pCurrentChangesList(NULL)
90 XMLRedlineExport::~XMLRedlineExport()
92 // delete changes lists
93 for( ChangesMapType::iterator aIter = aChangeMap.begin();
94 aIter != aChangeMap.end();
95 ++aIter )
97 delete aIter->second;
99 aChangeMap.clear();
103 void XMLRedlineExport::ExportChange(
104 const Reference<XPropertySet> & rPropSet,
105 bool bAutoStyle)
107 if (bAutoStyle)
109 // For the headers/footers, we have to collect the autostyles
110 // here. For the general case, however, it's better to collet
111 // the autostyles by iterating over the global redline
112 // list. So that's what we do: Here, we collect autostyles
113 // only if we have no current list of changes. For the
114 // main-document case, the autostyles are collected in
115 // ExportChangesListAutoStyles().
116 if (pCurrentChangesList != NULL)
117 ExportChangeAutoStyle(rPropSet);
119 else
121 ExportChangeInline(rPropSet);
126 void XMLRedlineExport::ExportChangesList(bool bAutoStyles)
128 if (bAutoStyles)
130 ExportChangesListAutoStyles();
132 else
134 ExportChangesListElements();
139 void XMLRedlineExport::ExportChangesList(
140 const Reference<XText> & rText,
141 bool bAutoStyles)
143 // in the header/footer case, auto styles are collected from the
144 // inline change elements.
145 if (bAutoStyles)
146 return;
148 // look for changes list for this XText
149 ChangesMapType::iterator aFind = aChangeMap.find(rText);
150 if (aFind != aChangeMap.end())
152 ChangesListType* pChangesList = aFind->second;
154 // export only if changes are found
155 if (pChangesList->size() > 0)
157 // changes container element
158 SvXMLElementExport aChanges(rExport, XML_NAMESPACE_TEXT,
159 XML_TRACKED_CHANGES,
160 true, true);
162 // iterate over changes list
163 for( ChangesListType::iterator aIter = pChangesList->begin();
164 aIter != pChangesList->end();
165 ++aIter )
167 ExportChangedRegion( *aIter );
170 // else: changes list empty -> ignore
172 // else: no changes list found -> empty
175 void XMLRedlineExport::SetCurrentXText(
176 const Reference<XText> & rText)
178 if (rText.is())
180 // look for appropriate list in map; use the found one, or create new
181 ChangesMapType::iterator aIter = aChangeMap.find(rText);
182 if (aIter == aChangeMap.end())
184 ChangesListType* pList = new ChangesListType;
185 aChangeMap[rText] = pList;
186 pCurrentChangesList = pList;
188 else
189 pCurrentChangesList = aIter->second;
191 else
193 // don't record changes
194 SetCurrentXText();
198 void XMLRedlineExport::SetCurrentXText()
200 pCurrentChangesList = NULL;
204 void XMLRedlineExport::ExportChangesListElements()
206 // get redlines (aka tracked changes) from the model
207 Reference<XRedlinesSupplier> xSupplier(rExport.GetModel(), uno::UNO_QUERY);
208 if (xSupplier.is())
210 Reference<XEnumerationAccess> aEnumAccess = xSupplier->getRedlines();
212 // redline protection key
213 Reference<XPropertySet> aDocPropertySet( rExport.GetModel(),
214 uno::UNO_QUERY );
215 // redlining enabled?
216 bool bEnabled = *static_cast<sal_Bool const *>(aDocPropertySet->getPropertyValue(
217 sRecordChanges ).getValue());
219 // only export if we have redlines or attributes
220 if ( aEnumAccess->hasElements() || bEnabled )
223 // export only if we have changes, but tracking is not enabled
224 if ( !bEnabled != !aEnumAccess->hasElements() )
226 rExport.AddAttribute(
227 XML_NAMESPACE_TEXT, XML_TRACK_CHANGES,
228 bEnabled ? XML_TRUE : XML_FALSE );
231 // changes container element
232 SvXMLElementExport aChanges(rExport, XML_NAMESPACE_TEXT,
233 XML_TRACKED_CHANGES,
234 true, true);
236 // get enumeration and iterate over elements
237 Reference<XEnumeration> aEnum = aEnumAccess->createEnumeration();
238 while (aEnum->hasMoreElements())
240 Any aAny = aEnum->nextElement();
241 Reference<XPropertySet> xPropSet;
242 aAny >>= xPropSet;
244 DBG_ASSERT(xPropSet.is(),
245 "can't get XPropertySet; skipping Redline");
246 if (xPropSet.is())
248 // export only if not in header or footer
249 // (those must be exported with their XText)
250 aAny = xPropSet->getPropertyValue(sIsInHeaderFooter);
251 if (! *static_cast<sal_Bool const *>(aAny.getValue()))
253 // and finally, export change
254 ExportChangedRegion(xPropSet);
257 // else: no XPropertySet -> no export
260 // else: no redlines -> no export
262 // else: no XRedlineSupplier -> no export
265 void XMLRedlineExport::ExportChangeAutoStyle(
266 const Reference<XPropertySet> & rPropSet)
268 // record change (if changes should be recorded)
269 if (NULL != pCurrentChangesList)
271 // put redline in list if it's collapsed or the redline start
272 Any aIsStart = rPropSet->getPropertyValue(sIsStart);
273 Any aIsCollapsed = rPropSet->getPropertyValue(sIsCollapsed);
275 if ( *static_cast<sal_Bool const *>(aIsStart.getValue()) ||
276 *static_cast<sal_Bool const *>(aIsCollapsed.getValue()) )
277 pCurrentChangesList->push_back(rPropSet);
280 // get XText for export of redline auto styles
281 Any aAny = rPropSet->getPropertyValue(sRedlineText);
282 Reference<XText> xText;
283 aAny >>= xText;
284 if (xText.is())
286 // export the auto styles
287 rExport.GetTextParagraphExport()->collectTextAutoStyles(xText);
291 void XMLRedlineExport::ExportChangesListAutoStyles()
293 // get redlines (aka tracked changes) from the model
294 Reference<XRedlinesSupplier> xSupplier(rExport.GetModel(), uno::UNO_QUERY);
295 if (xSupplier.is())
297 Reference<XEnumerationAccess> aEnumAccess = xSupplier->getRedlines();
299 // only export if we actually have redlines
300 if (aEnumAccess->hasElements())
302 // get enumeration and iterate over elements
303 Reference<XEnumeration> aEnum = aEnumAccess->createEnumeration();
304 while (aEnum->hasMoreElements())
306 Any aAny = aEnum->nextElement();
307 Reference<XPropertySet> xPropSet;
308 aAny >>= xPropSet;
310 DBG_ASSERT(xPropSet.is(),
311 "can't get XPropertySet; skipping Redline");
312 if (xPropSet.is())
315 // export only if not in header or footer
316 // (those must be exported with their XText)
317 aAny = xPropSet->getPropertyValue(sIsInHeaderFooter);
318 if (! *static_cast<sal_Bool const *>(aAny.getValue()))
320 ExportChangeAutoStyle(xPropSet);
328 void XMLRedlineExport::ExportChangeInline(
329 const Reference<XPropertySet> & rPropSet)
331 // determine element name (depending on collapsed, start/end)
332 enum XMLTokenEnum eElement = XML_TOKEN_INVALID;
333 Any aAny = rPropSet->getPropertyValue(sIsCollapsed);
334 bool bCollapsed = *static_cast<sal_Bool const *>(aAny.getValue());
335 if (bCollapsed)
337 eElement = XML_CHANGE;
339 else
341 aAny = rPropSet->getPropertyValue(sIsStart);
342 const bool bStart = *static_cast<sal_Bool const *>(aAny.getValue());
343 eElement = bStart ? XML_CHANGE_START : XML_CHANGE_END;
346 if (XML_TOKEN_INVALID != eElement)
348 // we always need the ID
349 rExport.AddAttribute(XML_NAMESPACE_TEXT, XML_CHANGE_ID,
350 GetRedlineID(rPropSet));
352 // export the element (no whitespace because we're in the text body)
353 SvXMLElementExport aChangeElem(rExport, XML_NAMESPACE_TEXT,
354 eElement, false, false);
359 void XMLRedlineExport::ExportChangedRegion(
360 const Reference<XPropertySet> & rPropSet)
362 // Redline-ID
363 rExport.AddAttributeIdLegacy(XML_NAMESPACE_TEXT, GetRedlineID(rPropSet));
365 // merge-last-paragraph
366 Any aAny = rPropSet->getPropertyValue(sMergeLastPara);
367 if( ! *static_cast<sal_Bool const *>(aAny.getValue()) )
368 rExport.AddAttribute(XML_NAMESPACE_TEXT, XML_MERGE_LAST_PARAGRAPH,
369 XML_FALSE);
371 // export change region element
372 SvXMLElementExport aChangedRegion(rExport, XML_NAMESPACE_TEXT,
373 XML_CHANGED_REGION, true, true);
376 // scope for (first) change element
378 aAny = rPropSet->getPropertyValue(sRedlineType);
379 OUString sType;
380 aAny >>= sType;
381 SvXMLElementExport aChange(rExport, XML_NAMESPACE_TEXT,
382 ConvertTypeName(sType), true, true);
384 ExportChangeInfo(rPropSet);
386 // get XText from the redline and export (if the XText exists)
387 aAny = rPropSet->getPropertyValue(sRedlineText);
388 Reference<XText> xText;
389 aAny >>= xText;
390 if (xText.is())
392 rExport.GetTextParagraphExport()->exportText(xText);
393 // default parameters: bProgress, bExportParagraph ???
395 // else: no text interface -> content is inline and will
396 // be exported there
399 // changed change? Hierarchical changes can onl be two levels
400 // deep. Here we check for the second level.
401 aAny = rPropSet->getPropertyValue(sRedlineSuccessorData);
402 Sequence<PropertyValue> aSuccessorData;
403 aAny >>= aSuccessorData;
405 // if we actually got a hierarchical change, make element and
406 // process change info
407 if (aSuccessorData.getLength() > 0)
409 // The only change that can be "undone" is an insertion -
410 // after all, you can't re-insert an deletion, but you can
411 // delete an insertion. This assumption is asserted in
412 // ExportChangeInfo(Sequence<PropertyValue>&).
413 SvXMLElementExport aSecondChangeElem(
414 rExport, XML_NAMESPACE_TEXT, XML_INSERTION,
415 true, true);
417 ExportChangeInfo(aSuccessorData);
419 // else: no hierarchical change
423 const OUString XMLRedlineExport::ConvertTypeName(
424 const OUString& sApiName)
426 if (sApiName == sDelete)
428 return sDeletion;
430 else if (sApiName == sInsert)
432 return sInsertion;
434 else if (sApiName == sFormat)
436 return sFormatChange;
438 else
440 OSL_FAIL("unknown redline type");
441 return sUnknownChange;
446 /** Create a Redline-ID */
447 const OUString XMLRedlineExport::GetRedlineID(
448 const Reference<XPropertySet> & rPropSet)
450 Any aAny = rPropSet->getPropertyValue(sRedlineIdentifier);
451 OUString sTmp;
452 aAny >>= sTmp;
454 OUStringBuffer sBuf(sChangePrefix);
455 sBuf.append(sTmp);
456 return sBuf.makeStringAndClear();
460 void XMLRedlineExport::ExportChangeInfo(
461 const Reference<XPropertySet> & rPropSet)
464 SvXMLElementExport aChangeInfo(rExport, XML_NAMESPACE_OFFICE,
465 XML_CHANGE_INFO, true, true);
467 Any aAny = rPropSet->getPropertyValue(sRedlineAuthor);
468 OUString sTmp;
469 aAny >>= sTmp;
470 if (!sTmp.isEmpty())
472 SvXMLElementExport aCreatorElem( rExport, XML_NAMESPACE_DC,
473 XML_CREATOR, true,
474 false );
475 rExport.Characters(sTmp);
478 aAny = rPropSet->getPropertyValue(sRedlineDateTime);
479 util::DateTime aDateTime;
480 aAny >>= aDateTime;
482 OUStringBuffer sBuf;
483 ::sax::Converter::convertDateTime(sBuf, aDateTime, 0);
484 SvXMLElementExport aDateElem( rExport, XML_NAMESPACE_DC,
485 XML_DATE, true,
486 false );
487 rExport.Characters(sBuf.makeStringAndClear());
490 // comment as <text:p> sequence
491 aAny = rPropSet->getPropertyValue(sRedlineComment);
492 aAny >>= sTmp;
493 WriteComment( sTmp );
496 void XMLRedlineExport::ExportChangeInfo(
497 const Sequence<PropertyValue> & rPropertyValues)
499 OUString sComment;
501 sal_Int32 nCount = rPropertyValues.getLength();
502 for(sal_Int32 i = 0; i < nCount; i++)
504 const PropertyValue& rVal = rPropertyValues[i];
506 if( rVal.Name.equals(sRedlineAuthor) )
508 OUString sTmp;
509 rVal.Value >>= sTmp;
510 if (!sTmp.isEmpty())
512 rExport.AddAttribute(XML_NAMESPACE_OFFICE, XML_CHG_AUTHOR, sTmp);
515 else if( rVal.Name.equals(sRedlineComment) )
517 rVal.Value >>= sComment;
519 else if( rVal.Name.equals(sRedlineDateTime) )
521 util::DateTime aDateTime;
522 rVal.Value >>= aDateTime;
523 OUStringBuffer sBuf;
524 ::sax::Converter::convertDateTime(sBuf, aDateTime, 0);
525 rExport.AddAttribute(XML_NAMESPACE_OFFICE, XML_CHG_DATE_TIME,
526 sBuf.makeStringAndClear());
528 else if( rVal.Name.equals(sRedlineType) )
530 // check if this is an insertion; cf. comment at calling location
531 OUString sTmp;
532 rVal.Value >>= sTmp;
533 DBG_ASSERT(sTmp.equals(sInsert),
534 "hierarchical change must be insertion");
536 // else: unknown value -> ignore
539 // finally write element
540 SvXMLElementExport aChangeInfo(rExport, XML_NAMESPACE_OFFICE,
541 XML_CHANGE_INFO, true, true);
543 WriteComment( sComment );
546 void XMLRedlineExport::ExportStartOrEndRedline(
547 const Reference<XPropertySet> & rPropSet,
548 bool bStart)
550 if( ! rPropSet.is() )
551 return;
553 // get appropriate (start or end) property
554 Any aAny;
557 aAny = rPropSet->getPropertyValue(bStart ? sStartRedline : sEndRedline);
559 catch(const UnknownPropertyException&)
561 // If we don't have the property, there's nothing to do.
562 return;
565 Sequence<PropertyValue> aValues;
566 aAny >>= aValues;
567 const PropertyValue* pValues = aValues.getConstArray();
569 // seek for redline properties
570 bool bIsCollapsed = false;
571 bool bIsStart = true;
572 OUString sId;
573 bool bIdOK = false; // have we seen an ID?
574 sal_Int32 nLength = aValues.getLength();
575 for(sal_Int32 i = 0; i < nLength; i++)
577 if (sRedlineIdentifier.equals(pValues[i].Name))
579 pValues[i].Value >>= sId;
580 bIdOK = true;
582 else if (sIsCollapsed.equals(pValues[i].Name))
584 bIsCollapsed = *static_cast<sal_Bool const *>(pValues[i].Value.getValue());
586 else if (sIsStart.equals(pValues[i].Name))
588 bIsStart = *static_cast<sal_Bool const *>(pValues[i].Value.getValue());
592 if( bIdOK )
594 DBG_ASSERT( !sId.isEmpty(), "Redlines must have IDs" );
596 // TODO: use GetRedlineID or elimiate that function
597 OUStringBuffer sBuffer(sChangePrefix);
598 sBuffer.append(sId);
600 rExport.AddAttribute(XML_NAMESPACE_TEXT, XML_CHANGE_ID,
601 sBuffer.makeStringAndClear());
603 // export the element
604 // (whitespace because we're not inside paragraphs)
605 SvXMLElementExport aChangeElem(
606 rExport, XML_NAMESPACE_TEXT,
607 bIsCollapsed ? XML_CHANGE :
608 ( bIsStart ? XML_CHANGE_START : XML_CHANGE_END ),
609 true, true);
613 void XMLRedlineExport::ExportStartOrEndRedline(
614 const Reference<XTextContent> & rContent,
615 bool bStart)
617 Reference<XPropertySet> xPropSet(rContent, uno::UNO_QUERY);
618 if (xPropSet.is())
620 ExportStartOrEndRedline(xPropSet, bStart);
622 else
624 OSL_FAIL("XPropertySet expected");
628 void XMLRedlineExport::ExportStartOrEndRedline(
629 const Reference<XTextSection> & rSection,
630 bool bStart)
632 Reference<XPropertySet> xPropSet(rSection, uno::UNO_QUERY);
633 if (xPropSet.is())
635 ExportStartOrEndRedline(xPropSet, bStart);
637 else
639 OSL_FAIL("XPropertySet expected");
643 void XMLRedlineExport::WriteComment(const OUString& rComment)
645 if (!rComment.isEmpty())
647 // iterate over all string-pieces separated by return (0x0a) and
648 // put each inside a paragraph element.
649 SvXMLTokenEnumerator aEnumerator(rComment, sal_Char(0x0a));
650 OUString aSubString;
651 while (aEnumerator.getNextToken(aSubString))
653 SvXMLElementExport aParagraph(
654 rExport, XML_NAMESPACE_TEXT, XML_P, true, false);
655 rExport.Characters(aSubString);
660 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */