1 /* AbiWord
2  * Copyright (C) 2002 Tomas Frydrych <tomas@frydrych.uklinux.net>
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301 USA.
18  */
19 
20 #include "pp_Revision.h"
21 #include "pp_AttrProp.h"
22 #include "pd_Style.h"
23 #include "pd_Document.h"
24 #include "ut_debugmsg.h"
25 #include "ut_misc.h"
26 #include "ut_std_map.h"
27 
28 //#include <limits.h>
29 
30 #include <sstream>
31 
32 
PP_Revision(UT_uint32 Id,PP_RevisionType eType,const gchar * props,const gchar * attrs)33 PP_Revision::PP_Revision(UT_uint32 Id, PP_RevisionType eType, const gchar * props, const gchar * attrs):
34 	m_iID(Id), m_eType(eType), m_bDirty(true)
35 {
36 	if(!props && !attrs)
37 		return;
38 
39 	const char * empty = "";
40 
41 	if(props)
42 	{
43 		char * pProps = g_strdup(props);
44 		UT_return_if_fail (pProps);
45 
46 		char * p = strtok(pProps, ":");
47 
48 		while(p)
49 		{
50 			char * n = p;
51 
52 			// skip over spaces ...
53 			while(n && *n == ' ')
54 				++n;
55 
56 			p = strtok(NULL, ";");
57 
58 			// if we have no p, that means the property is being removed ...
59 			const char * v = p ? p : empty;
60 			if(! strcmp(v, "-/-"))
61 				v = empty;
62 
63 			if(n)
64 			{
65 				setProperty(n,v);
66 				p = strtok(NULL,":");
67 			}
68 			else
69 			{
70 				// malformed property
71 				UT_DEBUGMSG(("PP_Revision::PP_Revision: malformed props string [%s]\n", props));
72 				// if we have not reached the end, we will keep trying ...
73 				if(p)
74 					p = strtok(NULL,":");
75 			}
76 		}
77 
78 		FREEP(pProps);
79 	}
80 
81 	if(attrs)
82 	{
83 		char * pAttrs = g_strdup(attrs);
84 
85 		UT_ASSERT_HARMLESS(pAttrs);
86 		if(!pAttrs)
87 		{
88 			UT_DEBUGMSG(("PP_Revision::PP_Revision: out of memory\n"));
89 			return;
90 		}
91 
92 		char * p = strtok(pAttrs, ":");
93 
94 		while(p)
95 		{
96 			char * n = p;
97 			p = strtok(NULL, ";");
98 
99 			const char * v = p ? p : empty;
100 			if(! strcmp(v, "-/-"))
101 				v = empty;
102 
103 			if(n)
104 			{
105 				setAttribute(n,v);
106 				p = strtok(NULL,":");
107 			}
108 			else
109 			{
110 				// malformed property
111 				UT_DEBUGMSG(("PP_Revision::PP_Revision: malformed props string [%s]\n", props));
112 				// if we have not reached the end, we will keep trying ...
113 				if(p)
114 					p = strtok(NULL,":");
115 			}
116 		}
117 
118 		FREEP(pAttrs);
119 	}
120 }
121 
PP_Revision(UT_uint32 Id,PP_RevisionType eType,const gchar ** props,const gchar ** attrs)122 PP_Revision::PP_Revision(UT_uint32 Id, PP_RevisionType eType, const gchar ** props, const gchar ** attrs):
123 	m_iID(Id), m_eType(eType), m_bDirty(true)
124 {
125 	if(!props && !attrs)
126 		return;
127 
128 	if(props)
129 	{
130 		setProperties(props);
131 	}
132 
133 	if(attrs)
134 	{
135 		setAttributes(attrs);
136 	}
137 }
138 
139 /*!
140     Sets attributes taking care of any nested revision attribute (which needs to be parsed
141     and combined with the current AP set.
142 */
setAttributes(const gchar ** attributes)143 bool PP_Revision::setAttributes(const gchar ** attributes)
144 {
145 	if(!PP_AttrProp::setAttributes(attributes))
146 		return false;
147 
148 	return _handleNestedRevAttr();
149 }
150 
151 
152 
_handleNestedRevAttr()153 bool PP_Revision::_handleNestedRevAttr()
154 {
155 	const gchar * pNestedRev = NULL;
156 	getAttribute("revision", pNestedRev);
157 
158 	if(pNestedRev)
159 	{
160 		PP_RevisionAttr NestedAttr(pNestedRev);
161 
162 		// now remove "revision"
163 		setAttribute("revision", NULL);
164 		prune();
165 
166 		// overlay the attrs and props from the revision attribute
167 		for(UT_uint32 i = 0; i < NestedAttr.getRevisionsCount(); ++i)
168 		{
169 			const PP_Revision * pRev = NestedAttr.getNthRevision(i);
170 			UT_return_val_if_fail( pRev, false );
171 
172 			// ignore inserts and deletes
173 			if(pRev->getType() == PP_REVISION_ADDITION || pRev->getType() == PP_REVISION_DELETION)
174 				continue;
175 
176 			setProperties(pRev->getProperties());
177 			setAttributes(pRev->getAttributes());
178 		}
179 
180 		prune();
181 	}
182 
183 	return true;
184 }
185 
186 
187 /*! converts the internal vector of properties into XML string */
getPropsString() const188 const gchar * PP_Revision::getPropsString() const
189 {
190 	if(m_bDirty)
191 		_refreshString();
192 
193 	return (const gchar*) m_sXMLProps.c_str();
194 }
195 
196 /*! converts the internal vector of attributes into XML string */
getAttrsString() const197 const gchar * PP_Revision::getAttrsString() const
198 {
199 	if(m_bDirty)
200 		_refreshString();
201 
202 	return (const gchar*) m_sXMLAttrs.c_str();
203 }
204 
_refreshString() const205 void PP_Revision::_refreshString() const
206 {
207 	m_sXMLProps.clear();
208 	m_sXMLAttrs.clear();
209 
210 	UT_uint32 i;
211 	UT_uint32 iCount = getPropertyCount();
212 	const gchar * n, *v;
213 
214 	for(i = 0; i < iCount; i++)
215 	{
216 		if(!getNthProperty(i,n,v))
217 		{
218 			// UT_ASSERT_HARMLESS( UT_SHOULD_NOT_HAPPEN );
219 			continue;
220 		}
221 
222 		if(!v || !*v) v = "-/-";
223 
224 		m_sXMLProps += n;
225 		m_sXMLProps += ":";
226 		m_sXMLProps += v;
227 		if(i < iCount - 1)
228 			m_sXMLProps += ";";
229 	}
230 
231 	iCount = getAttributeCount();
232 	for(i = 0; i < iCount; i++)
233 	{
234 		if(!getNthAttribute(i,n,v))
235 		{
236 			// UT_ASSERT_HARMLESS( UT_SHOULD_NOT_HAPPEN );
237 			continue;
238 		}
239 
240 		if(!v || !*v) v = "-/-";
241 
242 		m_sXMLAttrs += n;
243 		m_sXMLAttrs += ":";
244 		m_sXMLAttrs += v;
245 		if(i < iCount - 1)
246 			m_sXMLAttrs += ";";
247 	}
248 
249 	m_bDirty = false;
250 }
251 
toString() const252 std::string PP_Revision::toString() const
253 {
254     std::stringstream ret;
255     PP_RevisionType r_type = getType();
256 
257     if(r_type == PP_REVISION_FMT_CHANGE)
258         ret << "!";
259 
260     // print the id with appropriate sign
261     ret << (int)(getId()* ((r_type == PP_REVISION_DELETION)?-1:1));
262 
263     if(r_type != PP_REVISION_DELETION)
264     {
265         // if we have no props but have attribs, we have to issue empty braces so as not to
266         // confuse attribs with props
267         if(hasProperties() || hasAttributes())
268             ret << "{";
269 
270         if(hasProperties())
271             ret << getPropsString();
272 
273         if(hasProperties() || hasAttributes())
274             ret << "}";
275 
276         if(hasAttributes())
277         {
278             ret << "{" << getAttrsString() << "}";
279         }
280     }
281 
282     return ret.str();
283 }
284 
onlyContainsAbiwordChangeTrackingMarkup() const285 bool PP_Revision::onlyContainsAbiwordChangeTrackingMarkup() const
286 {
287     UT_DEBUGMSG(("onlyContainsAbiwordChangeTrackingMarkup(top) ac:%ld pc:%ld\n",
288 		 (long)getAttributeCount(), (long)getPropertyCount() ));
289 
290     if( !getAttributeCount() )
291         return false;
292     if( getPropertyCount() )
293         return false;
294 
295     bool ret = true;
296 	UT_uint32 i;
297 	UT_uint32 iCount = getAttributeCount();
298 	const gchar * n, *v;
299 
300 	for(i = 0; i < iCount; i++)
301 	{
302 		if(!getNthAttribute(i,n,v))
303 		{
304 			// UT_ASSERT_HARMLESS( UT_SHOULD_NOT_HAPPEN );
305 			continue;
306 		}
307         UT_DEBUGMSG(("onlyContainsAbiwordChangeTrackingMarkup() n:%s\n", n ));
308 
309         if( n != strstr( n, "abi-para" ) )
310         {
311             return false;
312         }
313     }
314 
315     return ret;
316 }
317 
318 
operator ==(const PP_Revision & op2) const319 bool PP_Revision::operator == (const PP_Revision &op2) const
320 {
321 	// this is quite involved, but we will start with the simple
322 	// non-equality cases
323 
324 	if(getId() != op2.getId())
325 		return false;
326 
327 	if(getType() != op2.getType())
328 		return false;
329 
330 
331 	// OK, so we have the same type and id, do we have the same props ???
332 	UT_uint32 iPCount1 = getPropertyCount();
333 	UT_uint32 iPCount2 = op2.getPropertyCount();
334 	UT_uint32 iACount1 = getAttributeCount();
335 	UT_uint32 iACount2 = op2.getAttributeCount();
336 
337 	if((iPCount1 != iPCount2) || (iACount1 != iACount2))
338 		return false;
339 
340 	// now the lengthy comparison
341 	UT_uint32 i;
342 	const gchar * n;
343 	const gchar * v1, * v2;
344 
345 	for(i = 0; i < iPCount1; i++)
346 	{
347 
348 		getNthProperty(i,n,v1);
349 		op2.getProperty(n,v2);
350 
351 		if(strcmp(v1,v2))
352 			return false;
353 	}
354 
355 	for(i = 0; i < iACount1; i++)
356 	{
357 
358 		getNthAttribute(i,n,v1);
359 		op2.getAttribute(n,v2);
360 
361 		if(strcmp(v1,v2))
362 			return false;
363 	}
364 	return true;
365 }
366 
367 
368 // PP_Revision*
369 // PP_Revision::clone() const
370 // {
371 //     PP_Revision* ret = new PP_Revision( *this );
372 //     return ret;
373 // }
374 
375 
376 /************************************************************
377  ************************************************************/
378 
379 /*! create class instance from an XML attribute string
380  */
PP_RevisionAttr(const gchar * r)381 PP_RevisionAttr::PP_RevisionAttr(const gchar * r):
382 	m_pLastRevision(NULL)
383 {
384 	_init(r);
385 }
386 
387 /*! create class instance from a single revision data */
PP_RevisionAttr(UT_uint32 iId,PP_RevisionType eType,const gchar ** pAttrs,const gchar ** pProps)388 PP_RevisionAttr::PP_RevisionAttr(UT_uint32 iId, PP_RevisionType eType,
389 								 const gchar ** pAttrs, const gchar ** pProps)
390 {
391 	PP_Revision * pRevision = new PP_Revision((UT_uint32)iId, eType, pProps, pAttrs);
392 	m_vRev.addItem((void*)pRevision);
393 }
394 
395 
~PP_RevisionAttr()396 PP_RevisionAttr::~PP_RevisionAttr()
397 {
398 	_clear();
399 }
400 
401 /*! initialize instance with XML attribute string
402  */
setRevision(const gchar * r)403 void PP_RevisionAttr::setRevision(const gchar * r)
404 {
405 	_clear();
406 	_init(r);
407 }
408 
409 void
setRevision(std::string & r)410 PP_RevisionAttr::setRevision(std::string&  r)
411 {
412     setRevision( r.c_str() );
413 }
414 
415 
416 /*! destroys all internal data */
_clear()417 void PP_RevisionAttr::_clear()
418 {
419 	for(UT_sint32 i = 0; i < m_vRev.getItemCount(); i++)
420 	{
421 		delete (PP_Revision *) m_vRev.getNthItem(i);
422 	}
423 
424 	m_vRev.clear();
425 	m_bDirty = true;
426 	m_pLastRevision = NULL;
427 }
428 
429 
430 /*! parse given XML attribute string and fill the
431     instance with the data
432 */
_init(const gchar * r)433 void PP_RevisionAttr::_init(const gchar *r)
434 {
435 	if(!r)
436 		return;
437 
438 	// the string we are parsing looks like
439 	// "+1,-2,!3{font-family: Times New Roman}"
440 
441 	// first duplicate the string so we can play with it ...
442 	char * s = (char*) g_strdup(r);
443 	char * end_s = s + strlen(s); // we need to remember where this
444 								  // string ends because we cannot use strtok(NULL,...)
445 
446 	UT_sint32 iId;
447 	PP_RevisionType eType;
448 	gchar * pProps, * pAttrs,
449 		     * cl_brace = 0, * op_brace = 0,
450 		     * cl_brace2 = 0;
451 
452 	char * t = strtok(s,",");
453 
454 	// we have to remember the end of this token for future calls to
455 	// strtok since strtok is also used in the PP_Revision class and
456 	// it screws us up, so we have to start always with explicit
457 	// string
458 	char * next_s = s;
459 
460 	while(t)
461 	{
462 		next_s = next_s + strlen(t) + 1; // 1 for the token separator
463 
464 		if(*t == '!')
465 		{
466 			eType = PP_REVISION_FMT_CHANGE;
467 			t++;
468 		}
469 		else if(*t == '-')
470 		{
471 			eType = PP_REVISION_DELETION;
472 			t++; // so we do not need to deal with sign later
473 		}
474 
475 		else
476 			eType = PP_REVISION_ADDITION; // this value is only
477 										  // temporary because this
478 										  // could equally be addition
479 										  // + format
480 
481 		cl_brace = strchr(t, '}');
482 		op_brace = strchr(t, '{');
483 
484 		if(!cl_brace || !op_brace)
485 		{
486 			// no props
487 			if(eType == PP_REVISION_FMT_CHANGE)
488 			{
489 				// malformed token, move onto the next one
490 				UT_DEBUGMSG(("PP_RevisionAttr::_init: invalid ! token [%s]\n",t));
491 				goto skip_this_token;
492 			}
493 			pProps = NULL;
494 			pAttrs = NULL;
495 		}
496 		else
497 		{
498 			// OK this is a case where we have some props, i.e., it
499 			// must be either fmt change or addition
500 			if(eType == PP_REVISION_DELETION)
501 			{
502 				// malformed token, move onto the next one
503 				UT_DEBUGMSG(("PP_RevisionAttr::_init: invalid - token [%s]\n",t));
504 				goto skip_this_token;
505 			}
506 
507 			// insert null as needed to be able to parse the id and props
508 			*op_brace = 0;
509 			*cl_brace = 0;
510 			pProps = op_brace+1;
511 
512 			// now see if the props are followed by attributes
513 			if(*(cl_brace + 1) == '{')
514 			{
515 				cl_brace2 = strchr(cl_brace + 2,'}');
516 				if(cl_brace2)
517 				{
518 					pAttrs = cl_brace + 2;
519 					*cl_brace2 = 0;
520 				}
521 				else
522 				{
523 					UT_DEBUGMSG(( "PP_RevisionAttr::_init: invalid token - [%s]\n", t ));
524 					pAttrs = NULL;
525 				}
526 			}
527 			else
528 				pAttrs = NULL;
529 
530 			if(eType == PP_REVISION_ADDITION)
531 				eType = PP_REVISION_ADDITION_AND_FMT;
532 		}
533 
534 		// now we can retrieve the id
535 		iId = atol(t);
536 
537 		{
538 			PP_Revision * pRevision = new PP_Revision((UT_uint32)iId, eType, pProps, pAttrs);
539 
540 			m_vRev.addItem((void*)pRevision);
541 		}
542 
543 	skip_this_token:
544 		if(next_s < end_s)
545 			t = strtok(next_s,",");
546 		else
547 			t = NULL;
548 	}
549 
550 	FREEP(s);
551 	m_bDirty = true;
552 	m_iSuperfluous = 0;
553 	m_pLastRevision = NULL;
554 }
555 
556 /*!
557     changes the type of revision with id iId to eType; if revision
558     with that id is not present, returns false
559  */
changeRevisionType(UT_uint32 iId,PP_RevisionType eType)560 bool PP_RevisionAttr::changeRevisionType(UT_uint32 iId, PP_RevisionType eType)
561 {
562 	for(UT_sint32 i = 0; i < m_vRev.getItemCount(); i++)
563 	{
564 		PP_Revision * r = (PP_Revision *)m_vRev.getNthItem(i);
565 
566 		if(iId == r->getId())
567 		{
568 			r->setType(eType);
569 			m_bDirty = true;
570 			return true;
571 		}
572 	}
573 
574 	return false;
575 }
576 
changeRevisionId(UT_uint32 iOldId,UT_uint32 iNewId)577 bool PP_RevisionAttr::changeRevisionId(UT_uint32 iOldId, UT_uint32 iNewId)
578 {
579 	UT_return_val_if_fail(iNewId >= iOldId, false);
580 
581 	for(UT_sint32 i = 0; i < m_vRev.getItemCount(); i++)
582 	{
583 		PP_Revision * r = (PP_Revision *)m_vRev.getNthItem(i);
584 
585 		if(iOldId == r->getId())
586 		{
587 			r->setId(iNewId);
588 			m_bDirty = true;
589 			return true;
590 		}
591 	}
592 
593 	return false;
594 }
595 
596 /*!
597     this function removes any revisions that no-longer contribute to the cumulative effect
598     it is used in full-history mode when transfering attrs and props from the revision attribute
599     into the main attrs and props
600 */
pruneForCumulativeResult(PD_Document * pDoc)601 void PP_RevisionAttr::pruneForCumulativeResult(PD_Document * pDoc)
602 {
603 	// first we are looking for any deletions which cancel anything below them
604 	bool bDelete = false;
605 	UT_sint32 i;
606 	if(m_vRev.getItemCount() == 0)
607 	{
608 		return;
609 	}
610 
611     m_bDirty = true;
612 
613 	for(i = m_vRev.getItemCount()-1; i >=0; --i)
614 	{
615 		PP_Revision * r = (PP_Revision *)m_vRev.getNthItem(i);
616 
617 		if(!bDelete && r->getType() == PP_REVISION_DELETION)
618 		{
619 			bDelete = true;
620 			continue; // we do not want the top revision deleted
621 		}
622 
623 
624 		if(bDelete)
625 		{
626 			delete r;
627 			m_vRev.deleteNthItem(i);
628 		}
629 	}
630 
631 	// now we merge props and attrs in what is left
632 	if(m_vRev.getItemCount() == 0)
633 	{
634 		return;
635 	}
636 
637 	PP_Revision * r0 = (PP_Revision *)m_vRev.getNthItem(0);
638 	UT_return_if_fail(r0);
639 
640 	for(i = 1; i < m_vRev.getItemCount(); ++i)
641 	{
642 		PP_Revision * r = (PP_Revision *)m_vRev.getNthItem(i);
643 		UT_return_if_fail( r );
644 
645 		r0->setProperties(r->getProperties());
646 		r0->setAttributes(r->getAttributes());
647 
648 		delete r;
649 		m_vRev.deleteNthItem(i);
650 		--i;
651 	}
652 
653     // explode the style if present
654 	if(pDoc)
655 		r0->explodeStyle(pDoc);
656 
657 #if 0
658 	// I do not think we should do this -- the emptiness indicates that the props and
659 	// attributes should be removed from the format; if we remove them, we irreversibly
660 	// lose this information
661 
662 	// get rid of any empty props and attrs
663 	r0->prune();
664 #endif
665 
666 	// finally, remove the revision attribute if present
667 	const gchar * v;
668 	if(r0->getAttribute("revision", v))
669 		r0->setAttribute("revision", NULL);
670 
671 	UT_ASSERT_HARMLESS( m_vRev.getItemCount() == 1 );
672 }
673 
674 
675 /*! return highest revision associated with this revision attribute
676     that has ID at most equal to id; the returned PP_Revision can be
677     used to determine whether this text should be displayed or hidden
678     in revision with the original id
679 
680     \param UT_uint32 id : the id of this revision
681     \param PP_Revision ** ppR: location where to store pointer to one
682                                of the special revisions in case return
683                                value is NULL
684 
685     \return : pointer to PP_Revision, or NULL if the revision
686     attribute should be ignored for the present level
687 */
688 
689 // these are special instances of PP_Revision that are used to in the
690 // following function to handle special cases
691 static const PP_Revision s_del(0, PP_REVISION_DELETION, (gchar*)0, (gchar*)0);
692 static const PP_Revision s_add(0, PP_REVISION_ADDITION, (gchar*)0, (gchar*)0);
693 
getGreatestLesserOrEqualRevision(UT_uint32 id,const PP_Revision ** ppR) const694 const PP_Revision *  PP_RevisionAttr::getGreatestLesserOrEqualRevision(UT_uint32 id,
695 																	   const PP_Revision ** ppR) const
696 {
697 	if(ppR)
698 		*ppR = NULL;
699 
700 	if(id == 0)
701 		return getLastRevision();
702 
703 	const PP_Revision *r = NULL; // this will be the revision we are looking for
704 	UT_uint32 r_id = 0;
705 
706 	const PP_Revision *m = NULL; // this will be the lowest revision present
707 	UT_uint32 m_id = 0xFFFF;
708 
709 	for(UT_sint32 i = 0; i < m_vRev.getItemCount(); i++)
710 	{
711 		const PP_Revision * t = (const PP_Revision *) m_vRev.getNthItem(i);
712 		UT_uint32 t_id = t->getId();
713 
714 		// the special case speedup - if we hit our id, then we can return immediately
715 		if(t_id == id)
716 			return t;
717 
718 		if(t_id < m_id)
719 		{
720 			m = t;
721 			m_id = t_id;
722 		}
723 
724 		if((t_id < id) && (t_id > r_id))
725 		{
726 			r = t;
727 			r_id = t_id;
728 		}
729 	}
730 
731 	// now that we have the biggest revision with ID lesser or equal
732 	// id, we have to deal with the special case when this is NULL
733 	// i.e., this fragment only figures in revisions > id; the problem
734 	// with NULL is that it is visible if the smallest revision ID is
735 	// negative, and hidden in the opposite case -- we use the special
736 	// static variables s_del and s_add to indicate what should
737 	// be done
738 
739 	if(r == NULL && ppR)
740 	{
741 		if(!m)
742 		{
743 			//UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN);
744 			// this happens when there was no revision attribute
745 			return NULL;
746 		}
747 
748 		if(m->getType() == PP_REVISION_DELETION)
749 			*ppR = &s_del;
750 		else if((m->getType() == PP_REVISION_ADDITION)
751 				||(m->getType() == PP_REVISION_ADDITION_AND_FMT))
752 			*ppR = &s_add;
753 		else // the initial revision was fmt change, so ignore it
754 			*ppR = NULL;
755 	}
756 
757 	return r;
758 }
759 
getLowestGreaterOrEqualRevision(UT_uint32 id) const760 const PP_Revision * PP_RevisionAttr::getLowestGreaterOrEqualRevision(UT_uint32 id) const
761 {
762 	if(id == 0)
763 		return NULL;
764 
765 	const PP_Revision *r = NULL; // this will be the revision we are looking for
766 	UT_uint32 r_id = PD_MAX_REVISION;
767 
768 	for(UT_sint32 i = 0; i < m_vRev.getItemCount(); i++)
769 	{
770 		const PP_Revision * t = (const PP_Revision *) m_vRev.getNthItem(i);
771 		UT_uint32 t_id = t->getId();
772 
773 		// the special case speedup - if we hit our id, then we can return immediately
774 		if(t_id == id)
775 			return t;
776 
777 		if((t_id > id) && (t_id < r_id))
778 		{
779 			r = t;
780 			r_id = t_id;
781 		}
782 	}
783 
784 	return r;
785 }
786 
787 
788 /*! finds the highest revision number in this attribute
789  */
getLastRevision() const790 const PP_Revision * PP_RevisionAttr::getLastRevision() const
791 {
792 	// since this is rather involved, we will cache the result and
793 	// use the cache if it is uptodate
794 	if(m_pLastRevision)
795 		return m_pLastRevision;
796 
797 	//const PP_Revision * r = NULL;
798 	UT_uint32 r_id = 0;
799 
800 	for(UT_sint32 i = 0; i < m_vRev.getItemCount(); i++)
801 	{
802 		const PP_Revision * t = (const PP_Revision *)m_vRev.getNthItem(i);
803 		UT_uint32 t_id = t->getId();
804 
805 		if(t_id > r_id)
806 		{
807 			r_id = t_id;
808 			m_pLastRevision = t;
809 		}
810 	}
811 
812 	// UT_ASSERT_HARMLESS( m_pLastRevision );
813 	// it is legal for this to be NULL -- it happens when the revision was pruned for
814 	// cumulative effect and the last revision was a deletion.
815 	return m_pLastRevision;
816 }
817 
818 
getHighestId() const819 UT_uint32 PP_RevisionAttr::getHighestId() const
820 {
821     UT_uint32 ret = 0;
822 	for(UT_sint32 i = 0; i < m_vRev.getItemCount(); i++)
823 	{
824 		const PP_Revision * t = (const PP_Revision *)m_vRev.getNthItem(i);
825         ret = std::max( ret, t->getId() );
826     }
827     return ret;
828 }
829 
830 
831 /*!
832    find revision with id == iId; if revision is not found minId
833    contains the smallest id in this set greater than iId; if return value is and minId
834    is PD_MAX_REVISION then there are revisions preset
835 */
getRevisionWithId(UT_uint32 iId,UT_uint32 & minId) const836 const PP_Revision * PP_RevisionAttr::getRevisionWithId(UT_uint32 iId, UT_uint32 &minId) const
837 {
838 	minId = PD_MAX_REVISION;
839 
840 	for(UT_sint32 i = 0; i < m_vRev.getItemCount(); i++)
841 	{
842 		const PP_Revision * t = (const PP_Revision *)m_vRev.getNthItem(i);
843 		UT_uint32 t_id = t->getId();
844 
845 		if(t_id == iId)
846 		{
847 			return t;
848 		}
849 
850 		if(minId > t_id && t_id > iId)
851 			minId = t_id;
852 	}
853 
854 	return NULL;
855 }
856 
857 
858 /*! given revision level id, this function returns true if given
859     segment of text is to be visible, false if it is to be hidden
860 */
isVisible(UT_uint32 id) const861 bool PP_RevisionAttr::isVisible(UT_uint32 id) const
862 {
863 	if(id == 0)
864 	{
865 		// id 0 means show all revisions
866 		return true;
867 	}
868 
869 	const PP_Revision * pSpecial;
870 	const PP_Revision * pR = getGreatestLesserOrEqualRevision(id, &pSpecial);
871 
872 	if(pR)
873 	{
874 		// found compliant revision ...
875 		return true;
876 	}
877 
878 
879 	if(pSpecial)
880 	{
881 		// pSpecial is of the same type as the revision with the
882 		// lowest id
883 		PP_RevisionType eType = pSpecial->getType();
884 
885 		// deletions and fmt changes can be ignored; insertions need
886 		// to be hidden
887 		return ((eType != PP_REVISION_ADDITION) && (eType == PP_REVISION_ADDITION_AND_FMT));
888 	}
889 
890 	// the revision with the lowest id is a change of format, this
891 	// text has to remain visible
892 	return true;
893 }
894 
895 
896 /*! adds id to the revision vector handling the special cases where id
897     is already present in this attribute.
898 */
addRevision(UT_uint32 iId,PP_RevisionType eType,const gchar ** pAttrs,const gchar ** pProps)899 void PP_RevisionAttr::addRevision(UT_uint32 iId, PP_RevisionType eType,
900 								  const gchar ** pAttrs, const gchar ** pProps)
901 {
902 	UT_sint32 i;
903 
904 	for(i = 0; i < m_vRev.getItemCount(); i++)
905 	{
906 		PP_Revision * r = (PP_Revision*) m_vRev.getNthItem(i);
907 		UT_uint32 r_id = r->getId();
908 		PP_RevisionType r_type = r->getType();
909 
910 		if(iId != r_id)
911 			continue;
912 
913 		if(eType != r_type)
914 		{
915 			// we are trying to add a revision id already in the vector
916 			// but of a different -- this is legal, i.e., the
917 			// editor just changed his mind
918 			// we need to make distinction between different cases
919 
920 			if((eType == PP_REVISION_DELETION) && (   r_type == PP_REVISION_ADDITION
921 												   || r_type == PP_REVISION_ADDITION_AND_FMT))
922 			{
923 				// the editor originally inserted a new segment of
924 				// text but now wants it out; we cannot just remove
925 				// the id, because the original operation resulted in
926 				// a new fragment in the piece table; rather we will
927 				// mark this with '-' and will remember the superfluous
928 				// id, so if queried later we can work out if this
929 				// whole fragment should in fact go
930 
931 				delete r;
932 				m_vRev.deleteNthItem(i);
933 
934 				m_iSuperfluous = iId;
935 
936 				const PP_Revision * pRevision = new PP_Revision(iId, eType, (gchar*)0, (gchar*)0);
937 				m_vRev.addItem((void*)pRevision);
938 			}
939 			else if((eType == PP_REVISION_ADDITION) && (r_type == PP_REVISION_DELETION))
940 			{
941 				// in the opposite case, when the editor originally
942 				// marked the text for removal and now wants it back,
943 				// we just remove the attribute (which we have done)
944 				// this also happens when we have been left with a
945 				// superfluous deletion id in the vector; if that is
946 				// the case we need to reset m_iSuperfluous, since
947 				// this fragment can no more be superfluous
948 
949 				delete r;
950 				m_vRev.deleteNthItem(i);
951 
952 				if(m_iSuperfluous == iId)
953 				{
954 					// the editor has had another change of heart
955 					m_iSuperfluous  = 0;
956 				}
957 			}
958 			else if((eType == PP_REVISION_DELETION) && (r_type == PP_REVISION_FMT_CHANGE))
959 			{
960 				// this is the case when the editor changed
961 				// formatting, but now wants the whole fragment out
962 				// instead -- we simly replace the old revision with
963 				// the new, since the original action did not result
964 				// in inserting new text
965 
966 				delete r;
967 				m_vRev.deleteNthItem(i);
968 
969 				const PP_Revision * pRevision = new PP_Revision(iId, eType, (gchar*)0, (gchar*)0);
970 				m_vRev.addItem((void*)pRevision);
971 			}
972 			else if((eType == PP_REVISION_FMT_CHANGE) && (r_type == PP_REVISION_DELETION))
973 			{
974 				// originally the editor marked the text for deletion,
975 				// but now he just wants a format change, in this case
976 				// we just replace the old revision with the new
977 
978 				delete r;
979 				m_vRev.deleteNthItem(i);
980 
981 				const PP_Revision * pRevision = new PP_Revision(iId, eType, pProps, pAttrs);
982 
983 				m_vRev.addItem((void*)pRevision);
984 			}
985 			else if((eType == PP_REVISION_FMT_CHANGE) && (r_type == PP_REVISION_ADDITION))
986 			{
987 				// the editor first added this fragment, and now wants
988 				// to apply a format change on the top of that
989 				// so we will keep the old revision record, but need
990 				// to merge any existing props in the revision with
991 				// the new ones
992 				r->setProperties(pProps);
993 				r->setAttributes(pAttrs);
994 			}
995 			else if((eType == PP_REVISION_FMT_CHANGE) && (r_type == PP_REVISION_ADDITION_AND_FMT))
996 			{
997 				// addition with a fmt change, just add our changes to it
998 				r->setProperties(pProps);
999 				r->setAttributes(pAttrs);
1000 			}
1001 
1002 			m_bDirty = true;
1003 			m_pLastRevision = NULL;
1004 			return;
1005 		}
1006 		else //(eType == r_type)
1007 		{
1008 			// we are trying to add a type already in the vector; this is legal but makes sense only
1009 			// if both are fmt changes
1010 			if(!((eType == PP_REVISION_FMT_CHANGE) && (r_type == PP_REVISION_FMT_CHANGE)))
1011 				return;
1012 
1013 			r->setProperties(pProps);
1014 			r->setAttributes(pAttrs);
1015 
1016 			m_bDirty = true;
1017 			m_pLastRevision = NULL;
1018 			return;
1019 		}
1020 	}
1021 
1022 	// if we got here then the item is not in our vector so add it
1023 	const PP_Revision * pRevision = new PP_Revision(iId, eType, pProps, pAttrs);
1024 
1025 	m_vRev.addItem((void*)pRevision);
1026 	m_bDirty = true;
1027 	m_pLastRevision = NULL;
1028 }
1029 
1030 
1031 /**
1032  * Logically Performs addRevision( iId, eType, 0, 0 ). This method is
1033  * mainly useful for loading an ODT+GCT file where you want to add and
1034  * delete revisions but don't actually care about the attrs/props for
1035  * that action.
1036  */
addRevision(UT_uint32 iId,PP_RevisionType eType)1037 void PP_RevisionAttr::addRevision(UT_uint32 iId, PP_RevisionType eType )
1038 {
1039     const gchar ** pAttrs = 0;
1040     const gchar ** pProps = 0;
1041     addRevision( iId, eType, pAttrs, pProps );
1042 }
1043 
1044 
1045 void
addRevision(const PP_Revision * r)1046 PP_RevisionAttr::addRevision( const PP_Revision* r )
1047 {
1048     std::stringstream ss;
1049     if(r->getType() & PP_REVISION_FMT_CHANGE)
1050         ss << "!";
1051 
1052     ss << (r->getId() * ((r->getType() == PP_REVISION_DELETION)?-1:1));
1053 
1054     if(r->hasProperties())
1055     {
1056         ss << "{" << r->getPropsString() << "}";
1057     }
1058     if(r->hasAttributes())
1059     {
1060         ss << "{" << r->getAttrsString() << "}";
1061     }
1062 
1063     PP_RevisionAttr us( getXMLstring() );
1064     _clear();
1065     std::string tmp = (std::string)us.getXMLstring() + "," + ss.str();
1066     setRevision( tmp.c_str() );
1067 }
1068 
mergeAttr(UT_uint32 iId,PP_RevisionType t,const gchar * pzName,const gchar * pzValue)1069 void PP_RevisionAttr::mergeAttr( UT_uint32 iId, PP_RevisionType t,
1070                                  const gchar* pzName, const gchar* pzValue )
1071 {
1072     PP_RevisionAttr ra;
1073     const gchar* ppAtts[10];
1074     ppAtts[0] = pzName;
1075     ppAtts[1] = pzValue;
1076     ppAtts[2] = 0;
1077     ra.addRevision(iId,t,ppAtts,NULL);
1078 
1079     mergeAll( ra );
1080 }
1081 
1082 /**
1083  * Do not replace the attribute/value if it exists in the given revision already.
1084  */
mergeAttrIfNotAlreadyThere(UT_uint32 iId,PP_RevisionType t,const gchar * pzName,const gchar * pzValue)1085 void PP_RevisionAttr::mergeAttrIfNotAlreadyThere( UT_uint32 iId,
1086                                                   PP_RevisionType t,
1087                                                   const gchar* pzName,
1088                                                   const gchar* pzValue )
1089 {
1090 	for(UT_sint32 i = 0; i < m_vRev.getItemCount(); i++)
1091 	{
1092 		const PP_Revision * tr = (const PP_Revision *)m_vRev.getNthItem(i);
1093 		UT_uint32 tid = tr->getId();
1094 
1095         if( tid == iId )
1096         {
1097             if( t == PP_REVISION_NONE || t == tr->getType() )
1098             {
1099                 const gchar * tattrs = tr->getAttrsString();
1100                 if( strstr( tattrs, pzName ))
1101                 {
1102                     return;
1103                 }
1104             }
1105         }
1106     }
1107 
1108     return mergeAttr( iId, t, pzName, pzValue );
1109 }
1110 
1111 
1112 
1113 //
1114 //                           getId()    getType()                rev
1115 typedef std::map< std::pair< UT_uint32, PP_RevisionType >, const PP_Revision* > revidx_t;
1116 
toIndex(const PP_RevisionAttr & ra)1117 static revidx_t toIndex( const PP_RevisionAttr& ra )
1118 {
1119     revidx_t ret;
1120     for( UT_uint32 i=0; i < ra.getRevisionsCount(); ++i )
1121     {
1122         const PP_Revision* r = ra.getNthRevision( i );
1123         ret[ std::make_pair( r->getId(), r->getType() ) ] = r;
1124     }
1125     return ret;
1126 }
1127 
mergeAPStrings(const std::string & a,const std::string & b)1128 static std::string mergeAPStrings( const std::string& a, const std::string& b )
1129 {
1130     if( b.empty() )
1131         return a;
1132     if( a.empty() )
1133         return b;
1134     std::stringstream ss;
1135     ss << a << ";" << b;
1136     return ss.str();
1137 }
1138 
1139 
1140 #define DEBUG_MERGEALL false
1141 
mergeAll(const PP_RevisionAttr & ra)1142 void PP_RevisionAttr::mergeAll( const PP_RevisionAttr& ra )
1143 {
1144     PP_RevisionAttr us( getXMLstring() );
1145     _clear();
1146     std::string tmp = (std::string)us.getXMLstring() + "," + ra.getXMLstring();
1147 
1148     revidx_t oldidx = toIndex( us );
1149     revidx_t newidx = toIndex( ra );
1150 
1151     /*
1152      * Iterate over the entries in the oldidx merging the data from
1153      * newidx if found whenever a entry from newidx is used it is
1154      * removed from newidx too. This way, we can then just iterate
1155      * over newidx to add the entries which are in newidx but not in
1156      * oldidx.
1157      */
1158     revidx_t output;
1159     for( revidx_t::iterator iter = oldidx.begin(); iter != oldidx.end(); ++iter )
1160     {
1161         const PP_Revision* r = iter->second;
1162         revidx_t::iterator niter = newidx.find( iter->first );
1163         // UT_DEBUGMSG(("ODTCT ra::merge() id:%d attrs:%s props:%s\n",
1164         //              r->getId(), r->getAttrsString(), r->getPropsString() ));
1165 
1166         /*
1167          * If there is an entry in oldidx and newidx then merge them
1168          */
1169         if( niter != newidx.end() )
1170         {
1171             const PP_Revision* nr = niter->second;
1172 
1173             std::string attrs = mergeAPStrings( r->getAttrsString(), nr->getAttrsString() );
1174             std::string props = mergeAPStrings( r->getPropsString(), nr->getPropsString() );
1175             output[ iter->first ] = new PP_Revision( iter->first.first,
1176                                                      iter->first.second,
1177                                                      props.c_str(), attrs.c_str() );
1178             newidx.erase( niter );
1179         }
1180         else
1181         {
1182             /*
1183              * disregard entries without anything to tell
1184              */
1185             if( r->getType() != PP_REVISION_DELETION
1186                 && !strlen(r->getAttrsString())
1187                 && !strlen(r->getPropsString()) )
1188             {
1189                 // UT_DEBUGMSG(("ODTCT ra::merge() rev as no attr/props, skipping old id:%d type:%d\n",
1190                 //              r->getId(), r->getType() ));
1191 
1192                 continue;
1193             }
1194 
1195             /*
1196              * no matching entry in the newidx, just copy the data
1197              */
1198             output[ iter->first ] = new PP_Revision( iter->first.first,
1199                                                      iter->first.second,
1200                                                      r->getPropsString(),
1201                                                      r->getAttrsString() );
1202         }
1203     }
1204 
1205     /*
1206      * copy over new revisions which didn't have a matching entry in the oldidx
1207      */
1208     for( revidx_t::iterator iter = newidx.begin(); iter != newidx.end(); ++iter )
1209     {
1210             output[ iter->first ] = new PP_Revision( iter->first.first,
1211                                                      iter->first.second,
1212                                                      iter->second->getPropsString(),
1213                                                      iter->second->getAttrsString() );
1214     }
1215 
1216     /*
1217      * Build the XML string for the merged revision attribute from the output index
1218      */
1219     bool outputssVirgin = true;
1220     std::stringstream outputss;
1221     for( revidx_t::iterator iter = output.begin(); iter != output.end(); ++iter )
1222     {
1223         const PP_Revision* r = iter->second;
1224 
1225         if( DEBUG_MERGEALL )
1226         {
1227             UT_DEBUGMSG(("ODTCT ra::merge() output id:%d t:%d attr:%s\n",
1228                          r->getId(), r->getType(), r->getAttrsString() ));
1229             UT_DEBUGMSG(("ODTCT ra::merge() output id:%d t:%d prop:%s\n",
1230                          r->getId(), r->getType(), r->getPropsString() ));
1231         }
1232 
1233         if( outputssVirgin ) outputssVirgin = false;
1234         else                 outputss << ",";
1235 
1236         outputss << r->toString();
1237     }
1238     UT_map_delete_all_second( output );
1239 
1240 
1241 
1242     setRevision( outputss.str().c_str() );
1243 
1244     if( DEBUG_MERGEALL )
1245     {
1246         UT_DEBUGMSG(("ODTCT ra::merge() outputss::%s\n", outputss.str().c_str() ));
1247         UT_DEBUGMSG(("ODTCT ra::merge() ret:%s\n", getXMLstring() ));
1248     }
1249     return;
1250 }
1251 
1252 
1253 
1254 
1255 /*! removes id from this revision, respecting the sign, i.e., it will
1256   not remove -5 if given 5
1257  */
removeRevisionIdWithType(UT_uint32 iId,PP_RevisionType eType)1258 void PP_RevisionAttr::removeRevisionIdWithType(UT_uint32 iId, PP_RevisionType eType)
1259 {
1260 	for(UT_sint32 i = 0; i < m_vRev.getItemCount(); i++)
1261 	{
1262 		PP_Revision * r = (PP_Revision *)m_vRev.getNthItem(i);
1263 
1264 		if((iId == r->getId()) && (eType == r->getType()))
1265 		{
1266 			delete r;
1267 			m_vRev.deleteNthItem(i);
1268 			m_bDirty = true;
1269 			m_pLastRevision = NULL;
1270 			return;
1271 		}
1272 	}
1273 }
1274 
1275 /*! removes id from the attribute disregarding sign, i.e.,
1276     if given 5 it will remove both -5 and +5
1277 */
removeRevisionIdTypeless(UT_uint32 iId)1278 void PP_RevisionAttr::removeRevisionIdTypeless(UT_uint32 iId)
1279 {
1280 	for(UT_sint32 i = 0; i < m_vRev.getItemCount(); i++)
1281 	{
1282 		PP_Revision * r = (PP_Revision *)m_vRev.getNthItem(i);
1283 
1284 		if(iId == r->getId())
1285 		{
1286 			delete r;
1287 			m_vRev.deleteNthItem(i);
1288 			m_bDirty = true;
1289 			m_pLastRevision = NULL;
1290 			return;
1291 		}
1292 	}
1293 }
1294 
1295 /*! removes pRev unconditionally from the attribute
1296 */
removeRevision(const PP_Revision * pRev)1297 void PP_RevisionAttr::removeRevision(const PP_Revision * pRev)
1298 {
1299 	for(UT_sint32 i = 0; i < m_vRev.getItemCount(); i++)
1300 	{
1301 		PP_Revision * r = (PP_Revision *)m_vRev.getNthItem(i);
1302 
1303 		if(r == pRev)
1304 		{
1305 			delete r;
1306 			m_vRev.deleteNthItem(i);
1307 			m_bDirty = true;
1308 			m_pLastRevision = NULL;
1309 			return;
1310 		}
1311 	}
1312 }
1313 
1314 
1315 /*! removes all IDs from the attribute whose value is lesser or
1316     equal the given id
1317 */
removeAllLesserOrEqualIds(UT_uint32 iId)1318 void PP_RevisionAttr::removeAllLesserOrEqualIds(UT_uint32 iId)
1319 {
1320 	for(UT_sint32 i = 0; i < m_vRev.getItemCount(); i++)
1321 	{
1322 		PP_Revision * r = (PP_Revision *)m_vRev.getNthItem(i);
1323 
1324 		if(iId >= r->getId())
1325 		{
1326 			delete r;
1327 			m_vRev.deleteNthItem(i);
1328 			--i; // the vector just shrunk
1329 		}
1330 	}
1331 
1332 	m_bDirty = true;
1333 	m_pLastRevision = NULL;
1334 }
1335 
1336 /*! removes all IDs from the attribute whose value is higher or
1337     equal the given id
1338 */
removeAllHigherOrEqualIds(UT_uint32 iId)1339 void PP_RevisionAttr::removeAllHigherOrEqualIds(UT_uint32 iId)
1340 {
1341 	for(UT_sint32 i = 0; i < m_vRev.getItemCount(); i++)
1342 	{
1343 		PP_Revision * r = (PP_Revision *)m_vRev.getNthItem(i);
1344 
1345 		if(iId <= r->getId())
1346 		{
1347 			delete r;
1348 			m_vRev.deleteNthItem(i);
1349 			--i; // the vector just shrunk
1350 		}
1351 	}
1352 
1353 	m_bDirty = true;
1354 	m_pLastRevision = NULL;
1355 }
1356 
1357 
1358 /*! create XML string from our vector
1359  */
_refreshString() const1360 void PP_RevisionAttr::_refreshString() const
1361 {
1362   //	char buf[30];
1363 	m_sXMLstring.clear();
1364 	UT_uint32 iCount = m_vRev.getItemCount();
1365 
1366 	for(UT_uint32 i = 0; i < iCount; i++)
1367 	{
1368 		PP_Revision * r = (PP_Revision *)m_vRev.getNthItem(i);
1369 
1370         if( !m_sXMLstring.empty() )
1371             m_sXMLstring += ",";
1372 
1373         m_sXMLstring += r->toString();
1374 
1375 		// PP_Revision * r = (PP_Revision *)m_vRev.getNthItem(i);
1376 		// PP_RevisionType r_type = r->getType();
1377 
1378 		// if(r_type == PP_REVISION_FMT_CHANGE)
1379 		// 	m_sXMLstring += "!";
1380 
1381 		// // print the id with appropriate sign
1382 		// sprintf(buf,"%d",r->getId()* ((r_type == PP_REVISION_DELETION)?-1:1));
1383 		// m_sXMLstring += buf;
1384 
1385 		// if(r_type != PP_REVISION_DELETION)
1386 		// {
1387 		// 	// if we have no props but have attribs, we have to issue empty braces so as not to
1388 		// 	// confuse attribs with props
1389 		// 	if(r->hasProperties() || r->hasAttributes())
1390 		// 		m_sXMLstring += "{";
1391 
1392         // if(r->hasProperties())
1393         // 	m_sXMLstring += r->getPropsString();
1394 
1395         // if(r->hasProperties() || r->hasAttributes())
1396         // 	m_sXMLstring += "}";
1397 
1398 		// 	if(r->hasAttributes())
1399 		// 	{
1400 		// 		m_sXMLstring += "{";
1401 		// 		m_sXMLstring += r->getAttrsString();
1402 		// 		m_sXMLstring += "}";
1403 		// 	}
1404 		// };
1405 
1406 		// if(i != iCount - 1)
1407 		// {
1408 		// 	//not the last itteration, append ','
1409 		// 	m_sXMLstring += ",";
1410 		// }
1411 
1412 	}
1413 	m_bDirty = false;
1414 }
1415 
1416 
1417 
1418 /*! get an gchar string representation of this revision
1419  */
getXMLstring() const1420 const gchar * PP_RevisionAttr::getXMLstring() const
1421 {
1422 	if(m_bDirty)
1423 		_refreshString();
1424 
1425 	return (const gchar*) m_sXMLstring.c_str();
1426 }
1427 
1428 std::string
getXMLstringUpTo(UT_uint32 iId) const1429 PP_RevisionAttr::getXMLstringUpTo( UT_uint32 iId ) const
1430 {
1431     PP_RevisionAttr rat;
1432     rat.setRevision( getXMLstring() );
1433     UT_DEBUGMSG(("PP_RevisionAttr::getXMLstringUpTo() id:%d before:%s\n", iId, rat.getXMLstring() ));
1434     rat.removeAllHigherOrEqualIds( iId );
1435     UT_DEBUGMSG(("PP_RevisionAttr::getXMLstringUpTo() id:%d  after:%s\n", iId, rat.getXMLstring() ));
1436     // PP_RevisionAttr rat;
1437     // const PP_Revision* r = 0;
1438     // for( int raIdx = 0;
1439     //      raIdx < iId && (r = ra.getNthRevision( raIdx ));
1440     //      raIdx++ )
1441     // {
1442     //     rat.addRevision( r );
1443     // }
1444     return rat.getXMLstring();
1445 }
1446 
1447 
1448 
1449 /*! returns true if the fragment marked by this attribute is
1450     superfluous, i.e, it was created in the process of the present
1451     revision but the editor has later changed his/her mind and decided
1452     it should go away
1453 */
isFragmentSuperfluous() const1454 bool PP_RevisionAttr::isFragmentSuperfluous() const
1455 {
1456 	// the fragment is superfluous if the superfluous flag is set
1457 	// and the fragment belongs only to a single revision level
1458 	if(m_iSuperfluous != 0 && m_vRev.getItemCount() == 1)
1459 	{
1460 		UT_return_val_if_fail (((PP_Revision *)m_vRev.getNthItem(0))->getId() == m_iSuperfluous,false);
1461 		return true;
1462 	}
1463 	else
1464 		return false;
1465 }
1466 
operator ==(const PP_RevisionAttr & op2) const1467 bool PP_RevisionAttr::operator== (const PP_RevisionAttr &op2) const
1468 {
1469 	for(UT_sint32 i = 0; i < m_vRev.getItemCount(); i++)
1470 	{
1471 		const PP_Revision * r1 = (const PP_Revision *) m_vRev.getNthItem(i);
1472 
1473 		for(UT_sint32 j = 0; j < op2.m_vRev.getItemCount(); j++)
1474 		{
1475 			const PP_Revision * r2 = (const PP_Revision *) op2.m_vRev.getNthItem(j);
1476 
1477 			if(!(*r1 == *r2))
1478 				return false;
1479 		}
1480 	}
1481 	return true;
1482 }
1483 
1484 // PP_RevisionAttr&
1485 // PP_RevisionAttr::operator=(const PP_RevisionAttr &rhs)
1486 // {
1487 //     setRevision( rhs.getXMLstring() );
1488 //     return *this;
1489 // }
1490 
1491 
1492 /*! returns true if after revision iId this fragment carries revised
1493     property pName, the value of which will be stored in pValue; see
1494     notes on PP_Revision::hasProperty(...)
1495 */
hasProperty(UT_uint32 iId,const gchar * pName,const gchar * & pValue) const1496 bool PP_RevisionAttr::hasProperty(UT_uint32 iId, const gchar * pName, const gchar * &pValue) const
1497 {
1498 	const PP_Revision * s;
1499 	const PP_Revision * r = getGreatestLesserOrEqualRevision(iId, &s);
1500 
1501 	if(r)
1502 		return r->getProperty(pName, pValue);
1503 
1504 	return false;
1505 }
1506 
1507 /*! returns true if after the last revision this fragment carries revised
1508     property pName, the value of which will be stored in pValue; see
1509     notes on PP_Revision::hasProperty(...)
1510 */
hasProperty(const gchar * pName,const gchar * & pValue) const1511 bool PP_RevisionAttr::hasProperty(const gchar * pName, const gchar * &pValue) const
1512 {
1513 	const PP_Revision * r = getLastRevision();
1514 	return r->getProperty(pName, pValue);
1515 }
1516 
1517 /*! returns the type of cumulative revision up to iId represented by this attribute
1518  */
getType(UT_uint32 iId) const1519 PP_RevisionType PP_RevisionAttr::getType(UT_uint32 iId) const
1520 {
1521 	const PP_Revision * s;
1522 	const PP_Revision * r = getGreatestLesserOrEqualRevision(iId,&s);
1523 
1524 	if(!r)
1525 	{
1526 		// HACK need to return something
1527 		return PP_REVISION_FMT_CHANGE;
1528 	}
1529 
1530 	return r->getType();
1531 }
1532 
1533 /*! returns the type of overall cumulative revision represented by this attribute
1534  */
getType() const1535 PP_RevisionType PP_RevisionAttr::getType() const
1536 {
1537 	const PP_Revision * r = getLastRevision();
1538 	return r->getType();
1539 }
1540 
1541 
getHighestRevisionNumberWithAttribute(const gchar * attrName) const1542 UT_uint32 PP_RevisionAttr::getHighestRevisionNumberWithAttribute( const gchar * attrName ) const
1543 {
1544     const PP_Revision* r = 0;
1545 
1546     for( UT_uint32 raIdx = 0;
1547          raIdx < getRevisionsCount() && (r = getNthRevision( raIdx ));
1548          raIdx++ )
1549     {
1550         if( UT_getAttribute( r, attrName, 0 ))
1551             return r->getId();
1552     }
1553     return 0;
1554 }
1555 
1556 
UT_getAttribute(const PP_AttrProp * pAP,const char * name,const char * def)1557 const char* UT_getAttribute( const PP_AttrProp* pAP, const char* name, const char* def  )
1558 {
1559     const gchar* pValue;
1560     bool ok;
1561 
1562     ok = pAP->getAttribute( name, pValue );
1563     if (!ok)
1564     {
1565         pValue = def;
1566     }
1567     return pValue;
1568 }
1569 
1570 const PP_Revision *
getLowestDeletionRevision() const1571 PP_RevisionAttr::getLowestDeletionRevision() const
1572 {
1573     if( !getRevisionsCount() )
1574         return 0;
1575 
1576     UT_uint32 rmax = getRevisionsCount();
1577     const PP_Revision* last  = getNthRevision( rmax-1 );
1578     if( last->getType() != PP_REVISION_DELETION )
1579         return 0;
1580 
1581     for( long idx = rmax - 1; idx >= 0; --idx )
1582     {
1583         const PP_Revision* p = getNthRevision( idx );
1584         if( p->getType() != PP_REVISION_DELETION )
1585         {
1586             return last;
1587         }
1588         last = p;
1589     }
1590     return 0;
1591 }
1592 
1593 
UT_getLatestAttribute(const PP_AttrProp * pAP,const char * name,const char * def)1594 std::string UT_getLatestAttribute( const PP_AttrProp* pAP,
1595                                    const char* name,
1596                                    const char* def )
1597 {
1598     const char* t = 0;
1599     std::string ret = def;
1600     bool ok = false;
1601 
1602     if( const char* revisionString = UT_getAttribute( pAP, "revision", 0 ))
1603     {
1604         PP_RevisionAttr ra( revisionString );
1605         const PP_Revision* r = 0;
1606 
1607         for( int raIdx = ra.getRevisionsCount()-1;
1608              raIdx >= 0 && (r = ra.getNthRevision( raIdx ));
1609              --raIdx )
1610         {
1611             ok = r->getAttribute( name, t );
1612             if (ok)
1613             {
1614                 ret = t;
1615                 return ret;
1616             }
1617         }
1618     }
1619 
1620     ok = pAP->getAttribute( name, t );
1621     if (ok)
1622     {
1623         ret = t;
1624         return ret;
1625     }
1626     ret = def;
1627 
1628     return ret;
1629 }
1630