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