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