xref: /trunk/main/xmloff/source/text/XMLRedlineExport.cxx (revision cdf0e10c4e3984b49a9502b011690b615761d4a3)
1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_xmloff.hxx"
30 #include "XMLRedlineExport.hxx"
31 #include <tools/debug.hxx>
32 #include <rtl/ustring.hxx>
33 #include <rtl/ustrbuf.hxx>
34 #include <com/sun/star/beans/XPropertySet.hpp>
35 #include <com/sun/star/beans/UnknownPropertyException.hpp>
36 #include <com/sun/star/container/XEnumerationAccess.hpp>
37 
38 #include <com/sun/star/container/XEnumeration.hpp>
39 #include <com/sun/star/document/XRedlinesSupplier.hpp>
40 #include <com/sun/star/text/XText.hpp>
41 #include <com/sun/star/text/XTextContent.hpp>
42 #include <com/sun/star/text/XTextSection.hpp>
43 #include <com/sun/star/util/DateTime.hpp>
44 #include <xmloff/xmltoken.hxx>
45 #include "xmloff/xmlnmspe.hxx"
46 #include <xmloff/xmlexp.hxx>
47 #include <xmloff/xmluconv.hxx>
48 
49 
50 using namespace ::com::sun::star;
51 using namespace ::xmloff::token;
52 
53 using ::com::sun::star::beans::PropertyValue;
54 using ::com::sun::star::beans::XPropertySet;
55 using ::com::sun::star::beans::UnknownPropertyException;
56 using ::com::sun::star::document::XRedlinesSupplier;
57 using ::com::sun::star::container::XEnumerationAccess;
58 using ::com::sun::star::container::XEnumeration;
59 using ::com::sun::star::text::XText;
60 using ::com::sun::star::text::XTextContent;
61 using ::com::sun::star::text::XTextSection;
62 using ::com::sun::star::uno::Any;
63 using ::com::sun::star::uno::Reference;
64 using ::com::sun::star::uno::Sequence;
65 using ::com::sun::star::util::DateTime;
66 using ::rtl::OUString;
67 using ::rtl::OUStringBuffer;
68 using ::std::list;
69 
70 
71 XMLRedlineExport::XMLRedlineExport(SvXMLExport& rExp)
72 :   sDelete(RTL_CONSTASCII_USTRINGPARAM("Delete"))
73 ,   sDeletion(GetXMLToken(XML_DELETION))
74 ,   sFormat(RTL_CONSTASCII_USTRINGPARAM("Format"))
75 ,   sFormatChange(GetXMLToken(XML_FORMAT_CHANGE))
76 ,   sInsert(RTL_CONSTASCII_USTRINGPARAM("Insert"))
77 ,   sInsertion(GetXMLToken(XML_INSERTION))
78 ,   sIsCollapsed(RTL_CONSTASCII_USTRINGPARAM("IsCollapsed"))
79 ,   sIsStart(RTL_CONSTASCII_USTRINGPARAM("IsStart"))
80 ,   sRedlineAuthor(RTL_CONSTASCII_USTRINGPARAM("RedlineAuthor"))
81 ,   sRedlineComment(RTL_CONSTASCII_USTRINGPARAM("RedlineComment"))
82 ,   sRedlineDateTime(RTL_CONSTASCII_USTRINGPARAM("RedlineDateTime"))
83 ,   sRedlineSuccessorData(RTL_CONSTASCII_USTRINGPARAM("RedlineSuccessorData"))
84 ,   sRedlineText(RTL_CONSTASCII_USTRINGPARAM("RedlineText"))
85 ,   sRedlineType(RTL_CONSTASCII_USTRINGPARAM("RedlineType"))
86 ,   sStyle(RTL_CONSTASCII_USTRINGPARAM("Style"))
87 ,   sTextTable(RTL_CONSTASCII_USTRINGPARAM("TextTable"))
88 ,   sUnknownChange(RTL_CONSTASCII_USTRINGPARAM("UnknownChange"))
89 ,   sStartRedline(RTL_CONSTASCII_USTRINGPARAM("StartRedline"))
90 ,   sEndRedline(RTL_CONSTASCII_USTRINGPARAM("EndRedline"))
91 ,   sRedlineIdentifier(RTL_CONSTASCII_USTRINGPARAM("RedlineIdentifier"))
92 ,   sIsInHeaderFooter(RTL_CONSTASCII_USTRINGPARAM("IsInHeaderFooter"))
93 ,   sRedlineProtectionKey(RTL_CONSTASCII_USTRINGPARAM("RedlineProtectionKey"))
94 ,   sRecordChanges(RTL_CONSTASCII_USTRINGPARAM("RecordChanges"))
95 ,   sMergeLastPara(RTL_CONSTASCII_USTRINGPARAM("MergeLastPara"))
96 ,   sChangePrefix(RTL_CONSTASCII_USTRINGPARAM("ct"))
97 ,   rExport(rExp)
98 ,   pCurrentChangesList(NULL)
99 {
100 }
101 
102 
103 XMLRedlineExport::~XMLRedlineExport()
104 {
105     // delete changes lists
106     for( ChangesMapType::iterator aIter = aChangeMap.begin();
107          aIter != aChangeMap.end();
108          aIter++ )
109     {
110         delete aIter->second;
111     }
112     aChangeMap.clear();
113 }
114 
115 
116 void XMLRedlineExport::ExportChange(
117     const Reference<XPropertySet> & rPropSet,
118     sal_Bool bAutoStyle)
119 {
120     if (bAutoStyle)
121     {
122         // For the headers/footers, we have to collect the autostyles
123         // here.  For the general case, however, it's better to collet
124         // the autostyles by iterating over the global redline
125         // list. So that's what we do: Here, we collect autostyles
126         // only if we have no current list of changes. For the
127         // main-document case, the autostyles are collected in
128         // ExportChangesListAutoStyles().
129         if (pCurrentChangesList != NULL)
130             ExportChangeAutoStyle(rPropSet);
131     }
132     else
133     {
134         ExportChangeInline(rPropSet);
135     }
136 }
137 
138 
139 void XMLRedlineExport::ExportChangesList(sal_Bool bAutoStyles)
140 {
141     if (bAutoStyles)
142     {
143         ExportChangesListAutoStyles();
144     }
145     else
146     {
147         ExportChangesListElements();
148     }
149 }
150 
151 
152 void XMLRedlineExport::ExportChangesList(
153     const Reference<XText> & rText,
154     sal_Bool bAutoStyles)
155 {
156     // in the header/footer case, auto styles are collected from the
157     // inline change elements.
158     if (bAutoStyles)
159         return;
160 
161     // look for changes list for this XText
162     ChangesMapType::iterator aFind = aChangeMap.find(rText);
163     if (aFind != aChangeMap.end())
164     {
165         ChangesListType* pChangesList = aFind->second;
166 
167         // export only if changes are found
168         if (pChangesList->size() > 0)
169         {
170             // changes container element
171             SvXMLElementExport aChanges(rExport, XML_NAMESPACE_TEXT,
172                                         XML_TRACKED_CHANGES,
173                                         sal_True, sal_True);
174 
175             // iterate over changes list
176             for( ChangesListType::iterator aIter = pChangesList->begin();
177                  aIter != pChangesList->end();
178                  aIter++ )
179             {
180                 ExportChangedRegion( *aIter );
181             }
182         }
183         // else: changes list empty -> ignore
184     }
185     // else: no changes list found -> empty
186 }
187 
188 void XMLRedlineExport::SetCurrentXText(
189     const Reference<XText> & rText)
190 {
191     if (rText.is())
192     {
193         // look for appropriate list in map; use the found one, or create new
194         ChangesMapType::iterator aIter = aChangeMap.find(rText);
195         if (aIter == aChangeMap.end())
196         {
197             ChangesListType* pList = new ChangesListType;
198             aChangeMap[rText] = pList;
199             pCurrentChangesList = pList;
200         }
201         else
202             pCurrentChangesList = aIter->second;
203     }
204     else
205     {
206         // don't record changes
207         SetCurrentXText();
208     }
209 }
210 
211 void XMLRedlineExport::SetCurrentXText()
212 {
213     pCurrentChangesList = NULL;
214 }
215 
216 
217 void XMLRedlineExport::ExportChangesListElements()
218 {
219     // get redlines (aka tracked changes) from the model
220     Reference<XRedlinesSupplier> xSupplier(rExport.GetModel(), uno::UNO_QUERY);
221     if (xSupplier.is())
222     {
223         Reference<XEnumerationAccess> aEnumAccess = xSupplier->getRedlines();
224 
225         // redline protection key
226         Reference<XPropertySet> aDocPropertySet( rExport.GetModel(),
227                                                  uno::UNO_QUERY );
228         // redlining enabled?
229         sal_Bool bEnabled = *(sal_Bool*)aDocPropertySet->getPropertyValue(
230                                                 sRecordChanges ).getValue();
231 
232         // only export if we have redlines or attributes
233         if ( aEnumAccess->hasElements() || bEnabled )
234         {
235 
236             // export only if we have changes, but tracking is not enabled
237             if ( !bEnabled != !aEnumAccess->hasElements() )
238             {
239                 rExport.AddAttribute(
240                     XML_NAMESPACE_TEXT, XML_TRACK_CHANGES,
241                     bEnabled ? XML_TRUE : XML_FALSE );
242             }
243 
244             // changes container element
245             SvXMLElementExport aChanges(rExport, XML_NAMESPACE_TEXT,
246                                         XML_TRACKED_CHANGES,
247                                         sal_True, sal_True);
248 
249             // get enumeration and iterate over elements
250             Reference<XEnumeration> aEnum = aEnumAccess->createEnumeration();
251             while (aEnum->hasMoreElements())
252             {
253                 Any aAny = aEnum->nextElement();
254                 Reference<XPropertySet> xPropSet;
255                 aAny >>= xPropSet;
256 
257                 DBG_ASSERT(xPropSet.is(),
258                            "can't get XPropertySet; skipping Redline");
259                 if (xPropSet.is())
260                 {
261                     // export only if not in header or footer
262                     // (those must be exported with their XText)
263                     aAny = xPropSet->getPropertyValue(sIsInHeaderFooter);
264                     if (! *(sal_Bool*)aAny.getValue())
265                     {
266                         // and finally, export change
267                         ExportChangedRegion(xPropSet);
268                     }
269                 }
270                 // else: no XPropertySet -> no export
271             }
272         }
273         // else: no redlines -> no export
274     }
275     // else: no XRedlineSupplier -> no export
276 }
277 
278 void XMLRedlineExport::ExportChangeAutoStyle(
279     const Reference<XPropertySet> & rPropSet)
280 {
281     // record change (if changes should be recorded)
282     if (NULL != pCurrentChangesList)
283     {
284         // put redline in list if it's collapsed or the redline start
285         Any aIsStart = rPropSet->getPropertyValue(sIsStart);
286         Any aIsCollapsed = rPropSet->getPropertyValue(sIsCollapsed);
287 
288         if ( *(sal_Bool*)aIsStart.getValue() ||
289              *(sal_Bool*)aIsCollapsed.getValue() )
290             pCurrentChangesList->push_back(rPropSet);
291     }
292 
293     // get XText for export of redline auto styles
294     Any aAny = rPropSet->getPropertyValue(sRedlineText);
295     Reference<XText> xText;
296     aAny >>= xText;
297     if (xText.is())
298     {
299         // export the auto styles
300         rExport.GetTextParagraphExport()->collectTextAutoStyles(xText);
301     }
302 }
303 
304 void XMLRedlineExport::ExportChangesListAutoStyles()
305 {
306     // get redlines (aka tracked changes) from the model
307     Reference<XRedlinesSupplier> xSupplier(rExport.GetModel(), uno::UNO_QUERY);
308     if (xSupplier.is())
309     {
310         Reference<XEnumerationAccess> aEnumAccess = xSupplier->getRedlines();
311 
312         // only export if we actually have redlines
313         if (aEnumAccess->hasElements())
314         {
315             // get enumeration and iterate over elements
316             Reference<XEnumeration> aEnum = aEnumAccess->createEnumeration();
317             while (aEnum->hasMoreElements())
318             {
319                 Any aAny = aEnum->nextElement();
320                 Reference<XPropertySet> xPropSet;
321                 aAny >>= xPropSet;
322 
323                 DBG_ASSERT(xPropSet.is(),
324                            "can't get XPropertySet; skipping Redline");
325                 if (xPropSet.is())
326                 {
327 
328                     // export only if not in header or footer
329                     // (those must be exported with their XText)
330                     aAny = xPropSet->getPropertyValue(sIsInHeaderFooter);
331                     if (! *(sal_Bool*)aAny.getValue())
332                     {
333                         ExportChangeAutoStyle(xPropSet);
334                     }
335                 }
336             }
337         }
338     }
339 }
340 
341 void XMLRedlineExport::ExportChangeInline(
342     const Reference<XPropertySet> & rPropSet)
343 {
344     // determine element name (depending on collapsed, start/end)
345     enum XMLTokenEnum eElement = XML_TOKEN_INVALID;
346     Any aAny = rPropSet->getPropertyValue(sIsCollapsed);
347     sal_Bool bCollapsed = *(sal_Bool *)aAny.getValue();
348     sal_Bool bStart = sal_True; // ignored if bCollapsed = sal_True
349     if (bCollapsed)
350     {
351         eElement = XML_CHANGE;
352     }
353     else
354     {
355         aAny = rPropSet->getPropertyValue(sIsStart);
356         bStart = *(sal_Bool *)aAny.getValue();
357         eElement = bStart ? XML_CHANGE_START : XML_CHANGE_END;
358     }
359 
360     if (XML_TOKEN_INVALID != eElement)
361     {
362         // we always need the ID
363         rExport.AddAttribute(XML_NAMESPACE_TEXT, XML_CHANGE_ID,
364                              GetRedlineID(rPropSet));
365 
366         // export the element (no whitespace because we're in the text body)
367         SvXMLElementExport aChangeElem(rExport, XML_NAMESPACE_TEXT,
368                                        eElement, sal_False, sal_False);
369     }
370 }
371 
372 
373 void XMLRedlineExport::ExportChangedRegion(
374     const Reference<XPropertySet> & rPropSet)
375 {
376     // Redline-ID
377     rExport.AddAttributeIdLegacy(XML_NAMESPACE_TEXT, GetRedlineID(rPropSet));
378 
379     // merge-last-paragraph
380     Any aAny = rPropSet->getPropertyValue(sMergeLastPara);
381     if( ! *(sal_Bool*)aAny.getValue() )
382         rExport.AddAttribute(XML_NAMESPACE_TEXT, XML_MERGE_LAST_PARAGRAPH,
383                              XML_FALSE);
384 
385     // export change region element
386     SvXMLElementExport aChangedRegion(rExport, XML_NAMESPACE_TEXT,
387                                       XML_CHANGED_REGION, sal_True, sal_True);
388 
389 
390     // scope for (first) change element
391     {
392         aAny = rPropSet->getPropertyValue(sRedlineType);
393         OUString sType;
394         aAny >>= sType;
395         SvXMLElementExport aChange(rExport, XML_NAMESPACE_TEXT,
396                                    ConvertTypeName(sType), sal_True, sal_True);
397 
398         ExportChangeInfo(rPropSet);
399 
400         // get XText from the redline and export (if the XText exists)
401         aAny = rPropSet->getPropertyValue(sRedlineText);
402         Reference<XText> xText;
403         aAny >>= xText;
404         if (xText.is())
405         {
406             rExport.GetTextParagraphExport()->exportText(xText);
407             // default parameters: bProgress, bExportParagraph ???
408         }
409         // else: no text interface -> content is inline and will
410         //       be exported there
411     }
412 
413     // changed change? Hierarchical changes can onl be two levels
414     // deep. Here we check for the second level.
415     aAny = rPropSet->getPropertyValue(sRedlineSuccessorData);
416     Sequence<PropertyValue> aSuccessorData;
417     aAny >>= aSuccessorData;
418 
419     // if we actually got a hierarchical change, make element and
420     // process change info
421     if (aSuccessorData.getLength() > 0)
422     {
423         // The only change that can be "undone" is an insertion -
424         // after all, you can't re-insert an deletion, but you can
425         // delete an insertion. This assumption is asserted in
426         // ExportChangeInfo(Sequence<PropertyValue>&).
427         SvXMLElementExport aSecondChangeElem(
428             rExport, XML_NAMESPACE_TEXT, XML_INSERTION,
429             sal_True, sal_True);
430 
431         ExportChangeInfo(aSuccessorData);
432     }
433     // else: no hierarchical change
434 }
435 
436 
437 const OUString XMLRedlineExport::ConvertTypeName(
438     const OUString& sApiName)
439 {
440     if (sApiName == sDelete)
441     {
442         return sDeletion;
443     }
444     else if (sApiName == sInsert)
445     {
446         return sInsertion;
447     }
448     else if (sApiName == sFormat)
449     {
450         return sFormatChange;
451     }
452     else
453     {
454         DBG_ERROR("unknown redline type");
455         return sUnknownChange;
456     }
457 }
458 
459 
460 /** Create a Redline-ID */
461 const OUString XMLRedlineExport::GetRedlineID(
462     const Reference<XPropertySet> & rPropSet)
463 {
464     Any aAny = rPropSet->getPropertyValue(sRedlineIdentifier);
465     OUString sTmp;
466     aAny >>= sTmp;
467 
468     OUStringBuffer sBuf(sChangePrefix);
469     sBuf.append(sTmp);
470     return sBuf.makeStringAndClear();
471 }
472 
473 
474 void XMLRedlineExport::ExportChangeInfo(
475     const Reference<XPropertySet> & rPropSet)
476 {
477 
478     SvXMLElementExport aChangeInfo(rExport, XML_NAMESPACE_OFFICE,
479                                    XML_CHANGE_INFO, sal_True, sal_True);
480 
481     Any aAny = rPropSet->getPropertyValue(sRedlineAuthor);
482     OUString sTmp;
483     aAny >>= sTmp;
484     if (sTmp.getLength() > 0)
485     {
486         SvXMLElementExport aCreatorElem( rExport, XML_NAMESPACE_DC,
487                                           XML_CREATOR, sal_True,
488                                           sal_False );
489         rExport.Characters(sTmp);
490     }
491 
492     aAny = rPropSet->getPropertyValue(sRedlineDateTime);
493     util::DateTime aDateTime;
494     aAny >>= aDateTime;
495     {
496         OUStringBuffer sBuf;
497         rExport.GetMM100UnitConverter().convertDateTime(sBuf, aDateTime);
498         SvXMLElementExport aDateElem( rExport, XML_NAMESPACE_DC,
499                                           XML_DATE, sal_True,
500                                           sal_False );
501         rExport.Characters(sBuf.makeStringAndClear());
502     }
503 
504     // comment as <text:p> sequence
505     aAny = rPropSet->getPropertyValue(sRedlineComment);
506     aAny >>= sTmp;
507     WriteComment( sTmp );
508 }
509 
510 void XMLRedlineExport::ExportChangeInfo(
511     const Sequence<PropertyValue> & rPropertyValues)
512 {
513     OUString sComment;
514 
515     sal_Int32 nCount = rPropertyValues.getLength();
516     for(sal_Int32 i = 0; i < nCount; i++)
517     {
518         const PropertyValue& rVal = rPropertyValues[i];
519 
520         if( rVal.Name.equals(sRedlineAuthor) )
521         {
522             OUString sTmp;
523             rVal.Value >>= sTmp;
524             if (sTmp.getLength() > 0)
525             {
526                 rExport.AddAttribute(XML_NAMESPACE_OFFICE, XML_CHG_AUTHOR, sTmp);
527             }
528         }
529         else if( rVal.Name.equals(sRedlineComment) )
530         {
531             rVal.Value >>= sComment;
532         }
533         else if( rVal.Name.equals(sRedlineDateTime) )
534         {
535             util::DateTime aDateTime;
536             rVal.Value >>= aDateTime;
537             OUStringBuffer sBuf;
538             rExport.GetMM100UnitConverter().convertDateTime(sBuf, aDateTime);
539             rExport.AddAttribute(XML_NAMESPACE_OFFICE, XML_CHG_DATE_TIME,
540                                  sBuf.makeStringAndClear());
541         }
542         else if( rVal.Name.equals(sRedlineType) )
543         {
544             // check if this is an insertion; cf. comment at calling location
545             OUString sTmp;
546             rVal.Value >>= sTmp;
547             DBG_ASSERT(sTmp.equals(sInsert),
548                        "hierarchical change must be insertion");
549         }
550         // else: unknown value -> ignore
551     }
552 
553     // finally write element
554     SvXMLElementExport aChangeInfo(rExport, XML_NAMESPACE_OFFICE,
555                                    XML_CHANGE_INFO, sal_True, sal_True);
556 
557     WriteComment( sComment );
558 }
559 
560 void XMLRedlineExport::ExportStartOrEndRedline(
561     const Reference<XPropertySet> & rPropSet,
562     sal_Bool bStart)
563 {
564     if( ! rPropSet.is() )
565         return;
566 
567     // get appropriate (start or end) property
568     Any aAny;
569     try
570     {
571         aAny = rPropSet->getPropertyValue(bStart ? sStartRedline : sEndRedline);
572     }
573     catch( UnknownPropertyException e )
574     {
575         // If we don't have the property, there's nothing to do.
576         return;
577     }
578 
579     Sequence<PropertyValue> aValues;
580     aAny >>= aValues;
581     const PropertyValue* pValues = aValues.getConstArray();
582 
583     // seek for redline properties
584     sal_Bool bIsCollapsed = sal_False;
585     sal_Bool bIsStart = sal_True;
586     OUString sId;
587     sal_Bool bIdOK = sal_False; // have we seen an ID?
588     sal_Int32 nLength = aValues.getLength();
589     for(sal_Int32 i = 0; i < nLength; i++)
590     {
591         if (sRedlineIdentifier.equals(pValues[i].Name))
592         {
593             pValues[i].Value >>= sId;
594             bIdOK = sal_True;
595         }
596         else if (sIsCollapsed.equals(pValues[i].Name))
597         {
598             bIsCollapsed = *(sal_Bool*)pValues[i].Value.getValue();
599         }
600         else if (sIsStart.equals(pValues[i].Name))
601         {
602             bIsStart = *(sal_Bool*)pValues[i].Value.getValue();
603         }
604     }
605 
606     if( bIdOK )
607     {
608         DBG_ASSERT( sId.getLength() > 0, "Redlines must have IDs" );
609 
610         // TODO: use GetRedlineID or elimiate that function
611         OUStringBuffer sBuffer(sChangePrefix);
612         sBuffer.append(sId);
613 
614         rExport.AddAttribute(XML_NAMESPACE_TEXT, XML_CHANGE_ID,
615                              sBuffer.makeStringAndClear());
616 
617         // export the element
618         // (whitespace because we're not inside paragraphs)
619         SvXMLElementExport aChangeElem(
620             rExport, XML_NAMESPACE_TEXT,
621             bIsCollapsed ? XML_CHANGE :
622                 ( bIsStart ? XML_CHANGE_START : XML_CHANGE_END ),
623             sal_True, sal_True);
624     }
625 }
626 
627 void XMLRedlineExport::ExportStartOrEndRedline(
628     const Reference<XTextContent> & rContent,
629     sal_Bool bStart)
630 {
631     Reference<XPropertySet> xPropSet(rContent, uno::UNO_QUERY);
632     if (xPropSet.is())
633     {
634         ExportStartOrEndRedline(xPropSet, bStart);
635     }
636     else
637     {
638         DBG_ERROR("XPropertySet expected");
639     }
640 }
641 
642 void XMLRedlineExport::ExportStartOrEndRedline(
643     const Reference<XTextSection> & rSection,
644     sal_Bool bStart)
645 {
646     Reference<XPropertySet> xPropSet(rSection, uno::UNO_QUERY);
647     if (xPropSet.is())
648     {
649         ExportStartOrEndRedline(xPropSet, bStart);
650     }
651     else
652     {
653         DBG_ERROR("XPropertySet expected");
654     }
655 }
656 
657 void XMLRedlineExport::WriteComment(const OUString& rComment)
658 {
659     if (rComment.getLength() > 0)
660     {
661         // iterate over all string-pieces separated by return (0x0a) and
662         // put each inside a paragraph element.
663         SvXMLTokenEnumerator aEnumerator(rComment, sal_Char(0x0a));
664         OUString aSubString;
665         while (aEnumerator.getNextToken(aSubString))
666         {
667             SvXMLElementExport aParagraph(
668                 rExport, XML_NAMESPACE_TEXT, XML_P, sal_True, sal_False);
669             rExport.Characters(aSubString);
670         }
671     }
672 }
673