1 /*
2  * sxesession.cpp - Sxe Session
3  * Copyright (C) 2006  Joonas Govenius
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18  *
19  */
20 
21 #include "sxesession.h"
22 #include "sxemanager.h"
23 
24 #include "QTimer"
25 #include "QUuid"
26 
27 // The maxlength of a chdata that gets put in one edit
28 enum {MAXCHDATA = 1024};
29 
30 using namespace XMPP;
31 
32 //----------------------------------------------------------------------------
33 // SxeSession
34 //----------------------------------------------------------------------------
35 
SxeSession(SxeManager * manager,const Jid & target,const QString & session,const Jid & ownJid,bool groupChat,bool serverSupport,const QList<QString> & features)36 SxeSession::SxeSession(SxeManager *manager, const Jid &target, const QString &session, const Jid &ownJid,
37 						bool groupChat, bool serverSupport, const QList<QString> &features)
38 	: QObject(manager)
39 	, session_(session)
40 	, target_(target)
41 	, ownJid_(ownJid)
42 	, groupChat_(groupChat)
43 	, serverSupport_(serverSupport)
44 	, queueing_(false)
45 	, importing_(false)
46 	, features_(features)
47 	, uuidMaxPostfix_(0)
48 
49 {
50 	setUUIDPrefix();
51 }
52 
~SxeSession()53 SxeSession::~SxeSession() {
54 	qDebug("destruct SxeSession");
55 	qDeleteAll(recordByNodeId_);
56 	recordByNodeId_.clear();
57 	emit sessionEnded(this);
58 }
59 
initializeDocument(const QDomDocument & doc)60 void SxeSession::initializeDocument(const QDomDocument &doc) {
61 	bool origImporting = importing_;
62 
63 	importing_ = true;
64 
65 	// reset the document
66 	doc_ = QDomDocument();
67 	foreach(SxeRecord* meta, recordByNodeId_.values())
68 		meta->deleteLater();
69 	// recordByNode_.clear();
70 	recordByNodeId_.clear();
71 	queuedIncomingEdits_.clear();
72 	queuedOutgoingEdits_.clear();
73 
74 
75 	// import prolog
76 	doc_.setContent(parseProlog(doc));
77 
78 	// import other nodes
79 	// create all nodes recursively from root
80 	QDomNodeList children = doc.childNodes();
81 	for(int i = 0; i < children.size(); i++) {
82 		// skip the XML declaration <?xml ...?> because it isn't a processing instruction
83 		if(!(children.at(i).isProcessingInstruction() && children.at(i).toProcessingInstruction().target().toLower() == "xml"))
84 			generateNewNode(children.at(i), QString(), i);
85 	}
86 
87 	importing_ = origImporting;
88 }
89 
processIncomingSxeElement(const QDomElement & sxe,const QString & id)90 void SxeSession::processIncomingSxeElement(const QDomElement &sxe, const QString &id) {
91 	if(id.isEmpty() && !importing_) {
92 		qDebug("Trying to process an SXE element without an associated id!");
93 		return;
94 	}
95 
96 	if(processSxe(sxe, id))
97 		emit documentUpdated(true);
98 }
99 
processSxe(const QDomElement & sxe,const QString & id)100 bool SxeSession::processSxe(const QDomElement &sxe, const QString &id) {
101 	// Don't accept duplicates
102 	if(!id.isEmpty() && usedSxeIds_.contains(id)) {
103 		qDebug() << QString("Tried to process a duplicate %1 (received: %2).").arg(sxe.attribute("id")).arg(usedSxeIds_.size()).toLatin1();
104 		return false;
105 	}
106 
107 	if(!id.isEmpty())
108 		usedSxeIds_ += id;
109 
110 	// store incoming edits when queueing
111 	if(queueing_) {
112 		// Make sure the element is not already in the queue.
113 		foreach(IncomingEdit i, queuedIncomingEdits_)
114 			if(i.xml == sxe)  return false;
115 
116 		IncomingEdit incoming;
117 		incoming.id = id;
118 		incoming.xml = sxe.cloneNode(true).toElement();
119 
120 		queuedIncomingEdits_.append(incoming);
121 		return false;
122 	}
123 
124 	// create an SxeEdit for each child of the <sxe/>
125 	QList<SxeEdit*> edits;
126 	for(QDomNode n = sxe.firstChild(); !n.isNull(); n = n.nextSibling()) {
127 		if(n.nodeName() == "new")
128 			edits.append(new SxeNewEdit(n.toElement()));
129 		else if(n.nodeName() == "set")
130 			edits.append(new SxeRecordEdit(n.toElement()));
131 		else if(n.nodeName() == "remove")
132 			edits.append(new SxeRemoveEdit(n.toElement()));
133 	}
134 
135 	if (edits.size() == 0)  return false;
136 
137 	// process all the edits
138 	foreach(SxeEdit* e, edits) {
139 		SxeRecord* meta;
140 		if(e->type() == SxeEdit::New)
141 			meta = createRecord(e->rid());
142 		else
143 			meta = record(e->rid());
144 
145 		if(meta)
146 			meta->apply(doc_, e);
147 	}
148 
149 	return true;
150 }
151 
document() const152 const QDomDocument& SxeSession::document() const {
153 	return doc_;
154 }
155 
groupChat() const156 bool SxeSession::groupChat() const {
157 	return groupChat_;
158 }
159 
serverSupport() const160 bool SxeSession::serverSupport() const {
161 	return serverSupport_;
162 }
163 
target() const164 const Jid SxeSession::target() const {
165 	return target_;
166 }
167 
session() const168 const QString SxeSession::session() const {
169 	return session_;
170 }
171 
ownJid() const172 const Jid SxeSession::ownJid() const {
173 	return ownJid_;
174 }
175 
features() const176 const QList<QString> SxeSession::features() const {
177 	return features_;
178 }
179 
startQueueing()180 QList<const SxeEdit*> SxeSession::startQueueing() {
181 	// do nothing if already queueing
182 	if(queueing_)
183 		return QList<const SxeEdit*>();
184 
185 	queueing_ = true;
186 
187 	// Return all the effective Edits to the session so far (snapshot)
188 	// make sure that they are added in the right order (parents first)
189 	QString rootid;
190 	QList<const SxeEdit*> nonDocElementEdits;
191 	QMultiHash<QString, QString> ridByParent;
192 
193 	// first collect all nodes into a hash by their parent
194 	foreach(SxeRecord* m, recordByNodeId_.values()) {
195 		if(!m->parent().isEmpty()) {
196 			ridByParent.insert(m->parent(), m->rid());
197 		} else if(!m->node().isElement()) {
198 			nonDocElementEdits += m->edits();
199 		} else
200 			rootid = m->rid();
201 	}
202 
203 	// starting from the root, add all edits to a list recursively
204 	QList<const SxeEdit*> edits;
205 
206 	if(!rootid.isNull())
207 		arrangeEdits(ridByParent, edits, rootid);
208 
209 	return nonDocElementEdits + edits;
210 }
211 
arrangeEdits(QHash<QString,QString> & ridByParent,QList<const SxeEdit * > & output,const QString & iterator)212 void SxeSession::arrangeEdits(QHash<QString, QString> &ridByParent, QList<const SxeEdit*> &output, const QString &iterator) {
213 	// add the edits to this node
214 	if(recordByNodeId_.contains(iterator))
215 		output += recordByNodeId_.value(iterator)->edits();
216 
217 	// process all the children
218 	QString child;
219 	while(!(child = ridByParent.take(iterator)).isNull()) {
220 		arrangeEdits(ridByParent, output, child);
221 	}
222 }
223 
stopQueueing()224 void SxeSession::stopQueueing() {
225 	// do nothing if not queueing
226 	if(!queueing_)
227 		return;
228 
229 	queueing_ = false;
230 
231 	// Process queued elements
232 	flush();
233 
234 	if(!queuedIncomingEdits_.isEmpty()) {
235 		while(!queuedIncomingEdits_.isEmpty()) {
236 			IncomingEdit queued = queuedIncomingEdits_.takeFirst();
237 			processSxe(queued.xml, queued.id);
238 		}
239 
240 		emit documentUpdated(true);
241 	}
242 }
243 
startImporting(const QDomDocument & doc)244 void SxeSession::startImporting(const QDomDocument &doc) {
245 	importing_ = true;
246 
247 	// reset the document
248 	initializeDocument(doc);
249 
250 	// start queueing outgoing edits
251 	startQueueing();
252 }
253 
stopImporting()254 void SxeSession::stopImporting() {
255 	stopQueueing();
256 
257 	importing_ = false;
258 }
259 
endSession()260 void SxeSession::endSession() {
261 	deleteLater();
262 }
263 
insertNodeBefore(const QDomNode & node,const QDomNode & parent,const QDomNode & referenceNode)264 const QDomNode SxeSession::insertNodeBefore(const QDomNode &node, const QDomNode &parent, const QDomNode &referenceNode) {
265 	if(referenceNode.isNull() || referenceNode.previousSibling().isNull()) {
266 		// insert as the first node
267 		SxeRecord* firstMeta = record(parent.firstChild());
268 		double primaryWeight;
269 		if(firstMeta)
270 			primaryWeight = firstMeta->primaryWeight() - 1;
271 		else
272 			primaryWeight = 0;
273 
274 		// find out the rid of the parent node
275 		QString parentId;
276 		SxeRecord* parentMeta = record(parent);
277 		if(parentMeta)
278 			parentId = parentMeta->rid();
279 		else {
280 			qDebug("Trying to insert a node to parent without an id");
281 			return QDomNode();
282 		}
283 
284 		return insertNode(node, parentId, primaryWeight);
285 	} else
286 		return insertNodeAfter(node, parent, referenceNode.previousSibling());
287 }
288 
insertNodeAfter(const QDomNode & node,const QDomNode & parent,const QDomNode & referenceNode)289 const QDomNode SxeSession::insertNodeAfter(const QDomNode &node, const QDomNode &parent, const QDomNode &referenceNode) {
290 	if(node.isNull())
291 		return QDomNode();
292 
293 	// process each child of a document fragment separately
294 	if(node.isDocumentFragment()) {
295 		// insert the first node relative to the specified referenceNode
296 		QDomNode reference = referenceNode;
297 		QDomNodeList children = node.childNodes();
298 		for(int i = 0; i < children.size(); i++) {
299 			QDomNode newNode = children.at(i);
300 			insertNodeAfter(newNode, parent, reference);
301 			// and the rest relative to the previous sibling
302 			reference = newNode;
303 		}
304 
305 		return QDomNode();
306 	}
307 
308 	// find out the rid of the parent node
309 	QString parentId;
310 	SxeRecord* parentMeta = record(parent);
311 	if(parentMeta)
312 		parentId = parentMeta->rid();
313 	else {
314 		qDebug("Trying to insert a node to parent without an id");
315 		return QDomNode();
316 	}
317 
318 	// find out the appropriate weight for the node
319 	double primaryWeight;
320 	SxeRecord* referenceMeta = record(referenceNode);
321 	SxeRecord* nextReferenceMeta = record(referenceNode.nextSibling());
322 	if(parent.childNodes().count() == 0)
323 		primaryWeight = 0;
324 	else if(referenceMeta && nextReferenceMeta && referenceNode.parentNode() == parent) {
325 		// get the average of the weights of the reference and it's next sibling
326 		primaryWeight = (referenceMeta->primaryWeight() + nextReferenceMeta->primaryWeight()) / 2;
327 	} else {
328 		// insert as the last node
329 		referenceMeta = record(parent.lastChild());
330 		if(referenceMeta)
331 			primaryWeight = referenceMeta->primaryWeight() + 1;
332 		else
333 			primaryWeight = 0;
334 	}
335 
336 	return insertNode(node, parentId, primaryWeight);
337 }
338 
insertNode(const QDomNode & node,const QString & parentId,double primaryWeight)339 const QDomNode SxeSession::insertNode(const QDomNode &node, const QString &parentId, double primaryWeight) {
340 	QDomNode newNode;
341 
342 	SxeRecord* meta = record(node);
343 	if(meta) {
344 		// move an existing node
345 
346 		// figure out what's to be changed
347 		QHash<SxeRecordEdit::Key, QString> changes;
348 		if(meta->parent() != parentId)
349 			changes.insert(SxeRecordEdit::Parent, parentId);
350 		if(meta->primaryWeight() != primaryWeight)
351 			changes.insert(SxeRecordEdit::PrimaryWeight, QString::number(primaryWeight));
352 
353 		if(changes.size() > 0) {
354 			// create the edit
355 			SxeRecordEdit* edit = new SxeRecordEdit(meta->rid(), meta->version() + 1, changes);
356 
357 			// apply it
358 			meta->apply(doc_, edit);
359 
360 			// send the edit to others
361 			queueOutgoingEdit(edit);
362 
363 			emit documentUpdated(false);
364 		}
365 		return node;
366 
367 	} else {
368 
369 		QList<SxeEdit*> edits;
370 		// create a new node
371 		QDomNode result = generateNewNode(node, parentId, primaryWeight);
372 
373 		emit documentUpdated(false);
374 		return result;
375 	}
376 }
377 
removeNode(const QDomNode & node)378 void SxeSession::removeNode(const QDomNode &node) {
379 	if(node.isNull())
380 		return;
381 
382 	// create SxeRemoveEdits for all child nodes
383 	generateRemoves(node);
384 	flush();
385 
386 	emit documentUpdated(false);
387 }
388 
setAttribute(const QDomNode & node,const QString & attribute,const QString & value,int from,int n)389 void SxeSession::setAttribute(const QDomNode &node, const QString &attribute, const QString &value, int from, int n) {
390 	if(!node.isElement() || attribute.isEmpty())
391 		return;
392 
393 	if(value.isNull()) {
394 		if(from < 0) {
395 			// Interpret passing QString() as value as wishing to remove the attribute
396 			if(node.toElement().hasAttribute(attribute))
397 				removeNode(node.toElement().attributeNode(attribute));
398 		}
399 
400 		return;
401 	}
402 
403 	if(node.toElement().hasAttribute(attribute)) {
404 		setNodeValue(node.attributes().namedItem(attribute), value, from, n);
405 	} else {
406 		if(from >= 0) {
407 			qDebug("from > 0 although attribute doesn't exist yet.");
408 			return;
409 		}
410 
411 		QDomAttr domattr = document_.createAttribute(attribute);
412 		domattr.setValue(value);
413 		insertNodeAfter(domattr, node, QDomNode());
414 	}
415 }
416 
setNodeValue(const QDomNode & node,const QString & value,int from,int n)417 void SxeSession::setNodeValue(const QDomNode &node, const QString &value, int from, int n) {
418 	SxeRecord* meta = record(node);
419 
420 	if(!meta) {
421 		qDebug() << "Trying to set value of " << node.nodeName() << " (a non-existent node) to \"" << value << "\"";
422 		return;
423 	}
424 
425 	if(!(node.isAttr() || node.isText())) {
426 		qDebug() << "Trying to set value of a non-attr/text node " << node.nodeName();
427 		return;
428 	}
429 
430 	// Check whether anythings changing:
431 	QString newValue;
432 	if(from >= 0 && n >= 0) {
433 		if((from + n) > node.nodeValue().length()) {
434 			qDebug() << QString("from (%1) + n (%2) > (length of existing node value) (%3).").arg(from).arg(n).arg(node.nodeValue().length());
435 			return;
436 		}
437 		newValue = node.nodeValue().replace(from, n, value);
438 	} else
439 		newValue = value;
440 
441 	if(newValue == node.nodeValue())
442 		return;
443 
444 	// Create the appropriate RecordEdit
445 	QHash<SxeRecordEdit::Key, QString> changes;
446 	changes.insert(SxeRecordEdit::Chdata, value);
447 	if(from >= 0 && n >= 0) {
448 		changes.insert(SxeRecordEdit::ReplaceFrom, QString("%1").arg(from));
449 		changes.insert(SxeRecordEdit::ReplaceN, QString("%1").arg(n));
450 	}
451 
452 	// create the edit
453 	SxeRecordEdit* edit = new SxeRecordEdit(meta->rid(), meta->version() + 1, changes);
454 
455 	// apply it
456 	meta->apply(doc_, edit);
457 
458 	// send the edit to others
459 	queueOutgoingEdit(edit);
460 
461 	emit documentUpdated(false);
462 }
463 
flush()464 void SxeSession::flush() {
465 	if(queuedOutgoingEdits_.isEmpty())
466 		return;
467 
468 	// create the sxe element
469 	QDomDocument *doc = ((SxeManager*)parent())->client()->doc();
470 	QDomElement sxe = doc->createElementNS(SXENS, "sxe");
471 	sxe.setAttribute("session", session_);
472 
473 	// append all queued edits
474 	while(!queuedOutgoingEdits_.isEmpty()) {
475 		sxe.appendChild(queuedOutgoingEdits_.takeFirst());
476 	}
477 
478 	// pass the bundle to SxeManager
479 	emit newSxeElement(sxe, target(), groupChat_);
480 }
481 
generateNewNode(const QDomNode node,const QString & parent,double primaryWeight)482 QDomNode SxeSession::generateNewNode(const QDomNode node, const QString &parent, double primaryWeight) {
483 	if(!record(node)) {
484 		// generate the appropriate edit(s) for the node
485 		QString rid = generateUUIDForSession();
486 		// create the SxeRecord
487 		SxeRecord* meta = createRecord(rid);
488 		if(!meta)
489 			return QDomNode();
490 
491 		if((node.isAttr() || node.isText()) && node.nodeValue().length() > MAXCHDATA) {
492 			// Generate a "stub" of the new node
493 			QDomNode clone = node.cloneNode();
494 			QString full = clone.nodeValue();
495 			clone.setNodeValue("");
496 			QDomNode newNode = generateNewNode(clone, parent, primaryWeight);
497 			flush();
498 
499 			// append the value
500 			for(int i = 0; i < full.length(); i += MAXCHDATA) {
501 				setNodeValue(newNode, full.mid(i, MAXCHDATA), i, 0);
502 				flush();
503 			}
504 		} else {
505 			SxeEdit* edit = new SxeNewEdit(rid, node, parent, primaryWeight, false);
506 
507 			meta->apply(doc_, edit);
508 			queueOutgoingEdit(edit);
509 		}
510 
511 		// process all the attributes and child nodes recursively
512 		if(node.isElement()) {
513 			// attributes
514 			QDomNamedNodeMap attributes = node.attributes();
515 			for(int i = 0; i < attributes.count(); i++)
516 				generateNewNode(attributes.item(i), rid, i);
517 
518 			// child nodes
519 			int i = 0;
520 			for(QDomNode n = node.firstChild(); !n.isNull(); n = n.nextSibling())
521 				generateNewNode(n, rid, i++);
522 		}
523 
524 		return meta->node();
525 	}
526 
527 	return QDomNode();
528 }
529 
generateRemoves(const QDomNode & node)530 void SxeSession::generateRemoves(const QDomNode &node) {
531 	SxeRecord* meta = record(node);
532 	if(meta) {
533 		// process all the attributes and child nodes recursively
534 		if(node.isElement()) {
535 			// attributes
536 			QDomNamedNodeMap attributes = node.attributes();
537 			for(int i = 0; i < attributes.count(); i++)
538 				generateRemoves(attributes.item(i));
539 
540 			// child nodes
541 			QDomNodeList children = node.childNodes();
542 			for(int i = 0; i < children.count(); i++)
543 				generateRemoves(children.at(i));
544 		}
545 
546 		// generate the appropriate edit for the node
547 		SxeRemoveEdit* edit = new SxeRemoveEdit(meta->rid());
548 		queueOutgoingEdit(edit);
549 		meta->apply(doc_, edit);
550 	}
551 }
552 
reposition(const QDomNode & node,bool remote)553 void SxeSession::reposition(const QDomNode &node, bool remote) {
554 	Q_UNUSED(remote);
555 	SxeRecord* meta = record(node);
556 
557 	if(!meta) {
558 		qDebug("Trying to reposition a node without record.");
559 		return;
560 	}
561 
562 	// inserting nodes to the document node is a special case
563 	if(meta->parent().isEmpty()) {
564 		if(node.isElement() && !(doc_.documentElement().isNull() || doc_.documentElement() == node)) {
565 			qDebug("Trying to add a root node when one already exists.");
566 			removeNode(node);
567 			flush();
568 			return;
569 		}
570 
571 		doc_.appendChild(node);
572 
573 		return;
574 	}
575 
576 	// find the parent node
577 	SxeRecord* parentMeta = record(meta->parent());
578 	QDomNode parentNode;
579 	if(!parentMeta
580 		|| (parentNode = parentMeta->node()).isNull()) {
581 		qDebug("non-existent parent. Deleting node.");
582 		removeNode(node);
583 		flush();
584 		return;
585 	}
586 
587 	// simply insert if an attribute
588 	if(node.isAttr()) {
589 		QDomElement parentElement = parentNode.toElement();
590 		if(parentElement.isNull()) {
591 			qDebug("Trying to insert an attribute to a non-element.");
592 			return;
593 		}
594 
595 		// unless an attribute with the same name already exists
596 		if(parentElement.hasAttribute(node.nodeName())
597 			&& node != parentElement.attributeNode(node.nodeName())) {
598 
599 			// qDebug() << QString("Removing an attribute node '%1' because one already exists").arg(node.nodeName());
600 
601 			// delete the node with smaller secondary weight
602 			if(removeSmaller(meta, record(parentElement.attributeNode(node.nodeName()))))
603 				return;
604 
605 		}
606 
607 		parentElement.setAttributeNode(node.toAttr());
608 
609 		return;
610 	}
611 
612 	// get the list of siblings
613 	QDomNodeList children = parentNode.childNodes();
614 
615 	// default to appending
616 	QDomNode before;
617 	bool insertLast = true;
618 	if(children.length() > 0) {
619 		// find the child with the smallest weight greater than the weight of the node itself
620 		// if any, insert the node before that node
621 		for(int i=0; i < children.length(); i++) {
622 			if(children.item(i) != node) {
623 				SxeRecord* siblingMeta = record(children.item(i));
624 				if(siblingMeta && *meta < *siblingMeta) {
625 					// qDebug() << QString("%1 (pw: %2) is less than %3 (pw: %4)").arg(meta->name()).arg(meta->primaryWeight()).arg(siblingMeta->name()).arg(siblingMeta->primaryWeight()).toLatin1();
626 					before = children.item(i);
627 					insertLast = false;
628 					break;
629 				}
630 			}
631 		}
632 	}
633 
634 	if(insertLast) {
635 		// qDebug() << QString("Repositioning '%1' (pw: %2) as last.").arg(node.nodeName()).arg(meta->primaryWeight()).toLatin1();
636 		parentNode.appendChild(node);
637 	} else {
638 		// qDebug() << QString("Repositioning '%1' (pw: %2) before '%3' (pw: %4).").arg(node.nodeName()).arg(meta->primaryWeight()).arg(before.nodeName()).arg(record(before)->primaryWeight()).toLatin1();
639 		parentNode.insertBefore(node, before);
640 	}
641 }
642 
643 // void SxeSession::addToLookup(const QDomNode &node, bool, const QString &rid) {
644 	// recordByNode_[node]
645 	//	 = recordByNodeId_[rid];
646 // }
647 
handleNodeToBeAdded(const QDomNode & node,bool remote)648 void SxeSession::handleNodeToBeAdded(const QDomNode &node, bool remote) {
649 	emit nodeToBeAdded(node, remote);
650 	reposition(node, remote);
651 	emit nodeAdded(node, remote);
652 }
653 
handleNodeToBeMoved(const QDomNode & node,bool remote)654 void SxeSession::handleNodeToBeMoved(const QDomNode &node, bool remote) {
655 	emit nodeToBeMoved(node, remote);
656 	reposition(node, remote);
657 	emit nodeMoved(node, remote);
658 }
659 
handleNodeToBeRemoved(const QDomNode & node,bool remote)660 void SxeSession::handleNodeToBeRemoved(const QDomNode &node, bool remote) {
661 	emit nodeToBeRemoved(node, remote);
662 	removeRecord(node);
663 }
664 
665 
removeRecord(const QDomNode & node)666 void SxeSession::removeRecord(const QDomNode &node) {
667 	QMutableHashIterator<QString, SxeRecord*> i(recordByNodeId_);
668 
669 	while(i.hasNext()) {
670 		if(node == i.next().value()->node()) {
671 			i.remove();
672 			return;
673 		}
674 	}
675 }
676 
removeSmaller(SxeRecord * meta1,SxeRecord * meta2)677 bool SxeSession::removeSmaller(SxeRecord* meta1, SxeRecord* meta2) {
678 	if(!meta1)
679 		return true;
680 	if(!meta2)
681 		return false;
682 
683 	if(meta1->hasSmallerSecondaryWeight(*meta2)) {
684 		removeNode(meta1->node());
685 		flush();
686 		return true;
687 	} else {
688 		removeNode(meta2->node());
689 		flush();
690 		return false;
691 	}
692 }
693 
addUsedSxeId(QString id)694 void SxeSession::addUsedSxeId(QString id) {
695 	usedSxeIds_ += id;
696 }
697 
usedSxeIds()698 QList<QString> SxeSession::usedSxeIds() {
699 	return usedSxeIds_;
700 }
701 
queueOutgoingEdit(SxeEdit * edit)702 void SxeSession::queueOutgoingEdit(SxeEdit* edit) {
703 	if(!importing_) {
704 		QDomElement el = edit->xml(doc_);
705 		queuedOutgoingEdits_.append(((SxeManager*)parent())->client()->doc()->importNode(el, true));
706 	}
707 }
708 
createRecord(const QString & id)709 SxeRecord* SxeSession::createRecord(const QString &id) {
710 	if(recordByNodeId_.contains(id)) {
711 		qDebug() << QString("record by id '%1' already exists.").arg(id).toLatin1();
712 		return NULL;
713 	}
714 
715 	SxeRecord* m = new SxeRecord(id);
716 	recordByNodeId_[id] = m;
717 
718 	// once the node is actually created, add it to the lookup table
719 	// connect(m, SIGNAL(nodeAdded(QDomNode, bool, QString)), SLOT(addToLookup(const QDomNode &, bool, const QString &)));
720 
721 	// remove the node in case of a conflicting edit
722 	connect(m, SIGNAL(nodeRemovalRequired(QDomNode)), SLOT(removeNode(QDomNode)));
723 
724 	// reposition and emit public signals as needed when record is changed
725 	connect(m, SIGNAL(nodeToBeAdded(QDomNode, bool, QString)), SLOT(handleNodeToBeAdded(const QDomNode &, bool)));
726 	connect(m, SIGNAL(nodeToBeMoved(QDomNode, bool)), SLOT(handleNodeToBeMoved(const QDomNode &, bool)));
727 	connect(m, SIGNAL(nodeToBeRemoved(QDomNode, bool)), SLOT(handleNodeToBeRemoved(const QDomNode &, bool)));
728 	connect(m, SIGNAL(chdataToBeChanged(QDomNode, bool)), SIGNAL(chdataToBeChanged(const QDomNode &, bool)));
729 	connect(m, SIGNAL(chdataChanged(QDomNode, bool)), SIGNAL(chdataChanged(const QDomNode &, bool)));
730 	// connect(m, SIGNAL(nameChanged(QDomNode, bool)), SIGNAL(nameChanged(const QDomNode &, bool)));
731 
732 	return m;
733 }
734 
record(const QString & id)735 SxeRecord* SxeSession::record(const QString &id) {
736 	return recordByNodeId_.value(id);
737 }
738 
record(const QDomNode & node) const739 SxeRecord* SxeSession::record(const QDomNode &node) const {
740 	if(node.isNull())
741 		return NULL;
742 
743 	// go through all the SxeRecord's
744 	foreach(SxeRecord* meta, recordByNodeId_.values()) {
745 		// qDebug() << QString("id: %1").arg(meta->rid()).toLatin1();
746 		if(node == meta->node())
747 			return meta;
748 	}
749 
750 	return NULL;
751 }
752 
setUUIDPrefix(const QString uuidPrefix)753 void SxeSession::setUUIDPrefix(const QString uuidPrefix) {
754 	if(!uuidPrefix.isNull())
755 		uuidPrefix_ = uuidPrefix;
756 	else
757 		uuidPrefix_ = generateUUID();
758 }
759 
generateUUIDForSession()760 QString SxeSession::generateUUIDForSession() {
761 	return QString("%1.%2").arg(uuidPrefix_).arg(++uuidMaxPostfix_, 0, 36); // 36 is the max allowed base
762 }
763 
generateUUID()764 QString SxeSession::generateUUID() {
765 	QString fullstring = QUuid::createUuid().toString();
766 	// return the string between "{" and "}"
767 	int start = fullstring.indexOf("{") + 1;
768 	return fullstring.mid(start, fullstring.lastIndexOf("}") - start);
769 }
770 
parseProlog(const QDomDocument & doc)771 QString SxeSession::parseProlog(const QDomDocument &doc) {
772 	QString prolog;
773 	QTextStream stream(&prolog);
774 
775 	// check for the XML declaration
776 	if(doc.childNodes().at(0).isProcessingInstruction()
777 		  && doc.childNodes().at(0).toProcessingInstruction().target().toLower() == "xml")
778 		doc.childNodes().at(0).save(stream, 1);
779 
780 	if(!doc.doctype().isNull())
781 		doc.doctype().save(stream, 1);
782 
783 	return prolog;
784 }
785