1/**
2 * Copyright (c) 2006-2017, JGraph Ltd
3 * Copyright (c) 2006-2017, Gaudenz Alder
4 */
5/**
6 * Removes all labels, user objects and styles from the given node in-place.
7 */
8EditorUi.DIFF_INSERT = 'i';
9
10/**
11 * Removes all labels, user objects and styles from the given node in-place.
12 */
13EditorUi.DIFF_REMOVE = 'r';
14
15/**
16 * Removes all labels, user objects and styles from the given node in-place.
17 */
18EditorUi.DIFF_UPDATE = 'u';
19
20/**
21 * Shared codec.
22 */
23EditorUi.prototype.codec = new mxCodec();
24
25/**
26 * Contains all view state properties that should not be ignored in diff sync.
27 */
28EditorUi.prototype.viewStateProperties = {background: true, backgroundImage: true, shadowVisible: true,
29	foldingEnabled: true, pageScale: true, mathEnabled: true, pageFormat: true, extFonts: true};
30
31/**
32 * Contains all known cell properties that should be ignored for a generic cell diff.
33 */
34EditorUi.prototype.cellProperties = {id: true, value: true, xmlValue: true, vertex: true, edge: true,
35	visible: true, collapsed: true, connectable: true, parent: true, children: true, previous: true,
36	source: true, target: true, edges: true, geometry: true, style: true,
37	mxObjectId: true, mxTransient: true};
38
39/**
40 * Removes all labels, user objects and styles from the given node in-place.
41 */
42EditorUi.prototype.patchPages = function(pages, diff, markPages, resolver, updateEdgeParents)
43{
44	var resolverLookup = {};
45	var newPages = [];
46	var inserted = {};
47	var removed = {};
48	var lookup = {};
49	var moved = {};
50
51  	if (resolver != null && resolver[EditorUi.DIFF_UPDATE] != null)
52	{
53  		for (var id in resolver[EditorUi.DIFF_UPDATE])
54  		{
55  			resolverLookup[id] = resolver[EditorUi.DIFF_UPDATE][id];
56		}
57	}
58
59	if (diff[EditorUi.DIFF_REMOVE] != null)
60	{
61		for (var i = 0; i < diff[EditorUi.DIFF_REMOVE].length; i++)
62		{
63			removed[diff[EditorUi.DIFF_REMOVE][i]] = true;
64		}
65	}
66
67	if (diff[EditorUi.DIFF_INSERT] != null)
68	{
69		for (var i = 0; i < diff[EditorUi.DIFF_INSERT].length; i++)
70		{
71			inserted[diff[EditorUi.DIFF_INSERT][i].previous] = diff[EditorUi.DIFF_INSERT][i];
72		}
73	}
74
75	if (diff[EditorUi.DIFF_UPDATE] != null)
76	{
77		for (var id in diff[EditorUi.DIFF_UPDATE])
78		{
79			var pageDiff = diff[EditorUi.DIFF_UPDATE][id];
80
81			if (pageDiff.previous != null)
82			{
83				moved[pageDiff.previous] = id;
84			}
85		}
86	}
87
88	// Restores existing order and creates lookup
89  	if (pages != null)
90  	{
91		var prev = '';
92
93		for (var i = 0; i < pages.length; i++)
94		{
95			var pageId = pages[i].getId();
96			lookup[pageId] = pages[i];
97
98			if (moved[prev] == null && !removed[pageId] &&
99				(diff[EditorUi.DIFF_UPDATE] == null ||
100				diff[EditorUi.DIFF_UPDATE][pageId] == null ||
101				diff[EditorUi.DIFF_UPDATE][pageId].previous == null))
102			{
103				moved[prev] = pageId;
104			}
105
106			prev = pageId;
107		}
108  	}
109
110  	// FIXME: Workaround for possible duplicate pages
111  	var added = {};
112
113	var addPage = mxUtils.bind(this, function(page)
114	{
115		var id = (page != null) ? page.getId() : '';
116
117		if (page != null && !added[id])
118		{
119			added[id] = true;
120			newPages.push(page);
121			var pageDiff = (diff[EditorUi.DIFF_UPDATE] != null) ?
122					diff[EditorUi.DIFF_UPDATE][id] : null;
123
124			if (pageDiff != null)
125			{
126				this.updatePageRoot(page);
127
128				if (pageDiff.name != null)
129				{
130					page.setName(pageDiff.name);
131				}
132
133				if (pageDiff.view != null)
134				{
135					this.patchViewState(page, pageDiff.view);
136				}
137
138				if (pageDiff.cells != null)
139				{
140					this.patchPage(page, pageDiff.cells,
141						resolverLookup[page.getId()],
142						updateEdgeParents);
143				}
144
145				if (markPages && (pageDiff.cells != null ||
146					pageDiff.view != null))
147				{
148					page.needsUpdate = true;
149				}
150			}
151		}
152
153		var mov = moved[id];
154
155		if (mov != null)
156		{
157			delete moved[id];
158			addPage(lookup[mov]);
159		}
160
161		var ins = inserted[id];
162
163		if (ins != null)
164		{
165			delete inserted[id];
166			insertPage(ins);
167		}
168	});
169
170	var insertPage = mxUtils.bind(this, function(ins)
171	{
172		var diagram = mxUtils.parseXml(ins.data).documentElement;
173		var newPage = new DiagramPage(diagram);
174		this.updatePageRoot(newPage);
175		var page = lookup[newPage.getId()];
176
177		if (page == null)
178		{
179			addPage(newPage);
180		}
181		else
182		{
183			// Updates root if page already in UI
184			page.root = newPage.root;
185
186			if (this.currentPage == page)
187			{
188				this.editor.graph.model.setRoot(page.root);
189			}
190			else if (markPages)
191			{
192				page.needsUpdate = true;
193			}
194		}
195	});
196
197	addPage();
198
199	// Handles orphaned moved pages
200	for (var id in moved)
201	{
202		addPage(lookup[moved[id]]);
203		delete moved[id];
204	}
205
206	// Handles orphaned inserted pages
207	for (var id in inserted)
208	{
209		insertPage(inserted[id]);
210		delete inserted[id];
211	}
212
213	return newPages;
214};
215
216/**
217 * Removes all labels, user objects and styles from the given node in-place.
218 */
219EditorUi.prototype.patchViewState = function(page, diff)
220{
221	if (page.viewState != null && diff != null)
222	{
223		if (page == this.currentPage)
224		{
225			page.viewState = this.editor.graph.getViewState();
226		}
227
228		for (var key in diff)
229		{
230			try
231			{
232				page.viewState[key] = JSON.parse(diff[key]);
233			}
234			catch(e) {} //Ignore TODO Is this correct, we encountered an undefined value for a key (extFonts)
235		}
236
237		if (page == this.currentPage)
238		{
239			this.editor.graph.setViewState(page.viewState, true);
240		}
241	}
242};
243
244/**
245 * Removes all labels, user objects and styles from the given node in-place.
246 */
247EditorUi.prototype.createParentLookup = function(model, diff)
248{
249	var parentLookup = {};
250
251	function getLookup(id)
252	{
253		var result = parentLookup[id];
254
255		if (result == null)
256		{
257			result = {inserted: [], moved: {}};
258			parentLookup[id] = result;
259		}
260
261		return result;
262	};
263
264	if (diff[EditorUi.DIFF_INSERT] != null)
265	{
266		for (var i = 0; i < diff[EditorUi.DIFF_INSERT].length; i++)
267		{
268			var temp = diff[EditorUi.DIFF_INSERT][i];
269			var par = (temp.parent != null) ? temp.parent : '';
270			var prev = (temp.previous != null) ? temp.previous : '';
271			getLookup(par).inserted[prev] = temp;
272		}
273	}
274
275	if (diff[EditorUi.DIFF_UPDATE] != null)
276	{
277		for (var id in diff[EditorUi.DIFF_UPDATE])
278		{
279			var temp = diff[EditorUi.DIFF_UPDATE][id];
280
281			if (temp.previous != null)
282			{
283				var par = temp.parent;
284
285				if (par == null)
286				{
287					var cell = model.getCell(id);
288
289					if (cell != null)
290					{
291						var parent = model.getParent(cell);
292
293						if (parent != null)
294						{
295							par = parent.getId();
296						}
297					}
298				}
299
300				if (par != null)
301				{
302					getLookup(par).moved[temp.previous] = id;
303				}
304			}
305		}
306	}
307
308	return parentLookup;
309};
310
311/**
312 * Removes all labels, user objects and styles from the given node in-place.
313 */
314EditorUi.prototype.patchPage = function(page, diff, resolver, updateEdgeParents)
315{
316	var model = (page == this.currentPage) ? this.editor.graph.model : new mxGraphModel(page.root);
317	var parentLookup = this.createParentLookup(model, diff);
318
319	model.beginUpdate();
320	try
321	{
322		// Disables or delays update of edge parents to after patch
323		var prev = model.updateEdgeParent;
324		var dict = new mxDictionary();
325		var pendingUpdates = [];
326
327		model.updateEdgeParent = function(edge, root)
328		{
329			if (!dict.get(edge) && updateEdgeParents)
330			{
331				dict.put(edge, true);
332				pendingUpdates.push(edge);
333			}
334		};
335
336		// Handles new root cells
337		var temp = parentLookup[''];
338		var cellDiff = (temp != null && temp.inserted != null) ? temp.inserted[''] : null;
339		var root = null;
340
341		if (cellDiff != null)
342		{
343			root = this.getCellForJson(cellDiff);
344		}
345
346		// Handles cells becoming root (very unlikely but possible)
347		if (root == null)
348		{
349			var id = (temp != null && temp.moved != null) ? temp.moved[''] : null;
350
351			if (id != null)
352			{
353				root = model.getCell(id);
354			}
355		}
356
357		if (root != null)
358		{
359			model.setRoot(root);
360			page.root = root;
361		}
362
363		// Inserts and updates previous and parent (hierarchy update)
364		this.patchCellRecursive(page, model, model.root, parentLookup, diff);
365
366		// Removes cells after parents have been updated above
367		if (diff[EditorUi.DIFF_REMOVE] != null)
368		{
369			for (var i = 0; i < diff[EditorUi.DIFF_REMOVE].length; i++)
370			{
371				var cell = model.getCell(diff[EditorUi.DIFF_REMOVE][i]);
372
373				if (cell != null)
374				{
375					model.remove(cell);
376				}
377			}
378		}
379
380		// Updates cell states and terminals
381		if (diff[EditorUi.DIFF_UPDATE] != null)
382		{
383			var res = (resolver != null && resolver.cells != null) ?
384				resolver.cells[EditorUi.DIFF_UPDATE] : null;
385
386			for (var id in diff[EditorUi.DIFF_UPDATE])
387			{
388				this.patchCell(model, model.getCell(id),
389					diff[EditorUi.DIFF_UPDATE][id],
390					(res != null) ? res[id] : null);
391			}
392		}
393
394		// Updates terminals for inserted cells
395		if (diff[EditorUi.DIFF_INSERT] != null)
396		{
397			for (var i = 0; i < diff[EditorUi.DIFF_INSERT].length; i++)
398			{
399				var cellDiff = diff[EditorUi.DIFF_INSERT][i];
400				var cell = model.getCell(cellDiff.id);
401
402				if (cell != null)
403				{
404					model.setTerminal(cell, model.getCell(cellDiff.source), true);
405					model.setTerminal(cell, model.getCell(cellDiff.target), false);
406				}
407			}
408		}
409
410		// Delayed update of edge parents
411		model.updateEdgeParent = prev;
412
413		if (updateEdgeParents && pendingUpdates.length > 0)
414		{
415			for (var i = 0; i < pendingUpdates.length; i++)
416			{
417				if (model.contains(pendingUpdates[i]))
418				{
419					model.updateEdgeParent(pendingUpdates[i]);
420				}
421			}
422		}
423	}
424	finally
425	{
426		model.endUpdate();
427	}
428};
429
430/**
431 * Removes all labels, user objects and styles from the given node in-place.
432 */
433EditorUi.prototype.patchCellRecursive = function(page, model, cell, parentLookup, diff)
434{
435	if (cell != null)
436	{
437		var temp = parentLookup[cell.getId()];
438		var inserted = (temp != null && temp.inserted != null) ? temp.inserted : {};
439		var moved = (temp != null && temp.moved != null) ? temp.moved : {};
440		var index = 0;
441
442		// Restores existing order
443		var childCount = model.getChildCount(cell);
444		var prev = '';
445
446		for (var i = 0; i < childCount; i++)
447		{
448			var cellId = model.getChildAt(cell, i).getId();
449
450			if (moved[prev] == null &&
451				(diff[EditorUi.DIFF_UPDATE] == null ||
452				diff[EditorUi.DIFF_UPDATE][cellId] == null ||
453				(diff[EditorUi.DIFF_UPDATE][cellId].previous == null &&
454				diff[EditorUi.DIFF_UPDATE][cellId].parent == null)))
455			{
456				moved[prev] = cellId;
457			}
458
459			prev = cellId;
460		}
461
462		var addCell = mxUtils.bind(this, function(child, insert)
463		{
464			var id = (child != null) ? child.getId() : '';
465
466			// Ignores the insert if the cell is already in the model
467			if (child != null && insert)
468			{
469				var ex = model.getCell(id);
470
471				if (ex != null && ex != child)
472				{
473					child = null;
474				}
475			}
476
477			if (child != null)
478			{
479				if (model.getChildAt(cell, index) != child)
480				{
481					model.add(cell, child, index);
482				}
483
484				this.patchCellRecursive(page, model,
485					child, parentLookup, diff);
486				index++;
487			}
488
489			return id;
490		});
491
492		// Uses stack to avoid recursion for children
493		var children = [null];
494
495		while (children.length > 0)
496		{
497			var entry = children.shift();
498			var child = (entry != null) ? entry.child : null;
499			var insert = (entry != null) ? entry.insert : false;
500			var id = addCell(child, insert);
501
502			// Move and insert are mutually exclusive per predecessor
503			// since an insert changes the predecessor of existing cells
504			// and is therefore ignored in the loop above where the order
505			// for existing cells is added to the moved object
506			var mov = moved[id];
507
508			if (mov != null)
509			{
510				delete moved[id];
511				children.push({child: model.getCell(mov)});
512			}
513
514			var ins = inserted[id];
515
516			if (ins != null)
517			{
518				delete inserted[id];
519				children.push({child: this.getCellForJson(ins), insert: true});
520			}
521
522			// Orphaned moves and inserts are operations where the previous cell vanished
523			// in the local model so their position in the child array cannot be determined.
524			// In this case those cells are appended. Dependencies between orphans are
525			// maintained because for-in loops enumerate the IDs in order of insertion.
526			if (children.length == 0)
527			{
528				// Handles orphaned moved pages
529				for (var id in moved)
530				{
531					children.push({child: model.getCell(moved[id])});
532					delete moved[id];
533				}
534
535				// Handles orphaned inserted pages
536				for (var id in inserted)
537				{
538					children.push({child: this.getCellForJson(inserted[id]), insert: true});
539					delete inserted[id];
540				}
541			}
542		}
543	}
544};
545
546/**
547 * Removes all labels, user objects and styles from the given node in-place.
548 */
549EditorUi.prototype.patchCell = function(model, cell, diff, resolve)
550{
551	if (cell != null && diff != null)
552	{
553		// Last write wins for value except if label is empty
554		if (resolve == null || (resolve.xmlValue == null &&
555			(resolve.value == null || resolve.value == '')))
556		{
557			if ('value' in diff)
558			{
559				model.setValue(cell, diff.value);
560			}
561			else if (diff.xmlValue != null)
562			{
563				model.setValue(cell, mxUtils.parseXml(diff.xmlValue).documentElement);
564			}
565		}
566
567		// Last write wins for style
568		if ((resolve == null || resolve.style == null) && diff.style != null)
569		{
570			model.setStyle(cell, diff.style);
571		}
572
573		if (diff.visible != null)
574		{
575			model.setVisible(cell, diff.visible == 1);
576		}
577
578		if (diff.collapsed != null)
579		{
580			model.setCollapsed(cell, diff.collapsed == 1);
581		}
582
583		if (diff.vertex != null)
584		{
585			// Changes vertex state in-place
586			cell.vertex = diff.vertex == 1;
587		}
588
589		if (diff.edge != null)
590		{
591			// Changes edge state in-place
592			cell.edge = diff.edge == 1;
593		}
594
595		if (diff.connectable != null)
596		{
597			// Changes connectable state in-place
598			cell.connectable = diff.connectable == 1;
599		}
600
601		if (diff.geometry != null)
602		{
603			model.setGeometry(cell, this.codec.decode(mxUtils.parseXml(
604				diff.geometry).documentElement));
605		}
606
607		if (diff.source != null)
608		{
609			model.setTerminal(cell, model.getCell(diff.source), true);
610		}
611
612		if (diff.target != null)
613		{
614			model.setTerminal(cell, model.getCell(diff.target), false);
615		}
616
617		for (var key in diff)
618		{
619			if (!this.cellProperties[key])
620			{
621				cell[key] = diff[key];
622			}
623		}
624	}
625};
626
627/**
628 * Gets a file node that is comparable with a remote file node
629 * so that using isEqualNode returns true if the files can be
630 * considered equal.
631 */
632EditorUi.prototype.getPagesForNode = function(node, nodeName)
633{
634	var tmp = this.editor.extractGraphModel(node, true, true);
635
636	if (tmp != null)
637	{
638		node = tmp;
639	}
640
641	var diagrams = node.getElementsByTagName(nodeName || 'diagram');
642	var pages = [];
643
644	if (diagrams.length > 0)
645	{
646		for (var i = 0; i < diagrams.length; i++)
647		{
648			var page = new DiagramPage(diagrams[i]);
649			this.updatePageRoot(page, true);
650			pages.push(page);
651		}
652	}
653	else if (node.nodeName == 'mxGraphModel')
654	{
655		var graph = this.editor.graph;
656		var page = new DiagramPage(node.ownerDocument.createElement('diagram'));
657		page.setName(mxResources.get('pageWithNumber', [1]));
658		mxUtils.setTextContent(page.node, Graph.compressNode(node, true));
659		pages.push(page);
660	}
661
662	return pages;
663};
664
665/**
666 * Removes all labels, user objects and styles from the given node in-place.
667 */
668EditorUi.prototype.diffPages = function(oldPages, newPages)
669{
670	var graph = this.editor.graph;
671	var inserted = [];
672	var removed = [];
673	var result = {};
674	var lookup = {};
675	var diff = {};
676	var prev = null;
677
678	for (var i = 0; i < newPages.length; i++)
679	{
680		lookup[newPages[i].getId()] = {page: newPages[i], prev: prev};
681		prev = newPages[i];
682	}
683
684	prev = null;
685
686	for (var i = 0; i < oldPages.length; i++)
687	{
688		var id = oldPages[i].getId();
689		var newPage = lookup[id];
690
691		if (newPage == null)
692		{
693			removed.push(id);
694		}
695		else
696		{
697			var temp = this.diffPage(oldPages[i], newPage.page);
698			var pageDiff = {};
699
700			if (Object.keys(temp).length > 0)
701			{
702				pageDiff.cells = temp;
703			}
704
705			var view = this.diffViewState(oldPages[i], newPage.page);
706
707			if (Object.keys(view).length > 0)
708			{
709				pageDiff.view = view;
710			}
711
712			if (((newPage.prev != null) ? prev == null : prev != null) ||
713				(prev != null && newPage.prev != null &&
714				prev.getId() != newPage.prev.getId()))
715			{
716				pageDiff.previous = (newPage.prev != null) ? newPage.prev.getId() : '';
717			}
718
719			// FIXME: Check why names can be null in newer files
720			// ignore in hash and do not diff null names for now
721			if (newPage.page.getName() != null &&
722				oldPages[i].getName() != newPage.page.getName())
723			{
724				pageDiff.name = newPage.page.getName();
725			}
726
727			if (Object.keys(pageDiff).length > 0)
728			{
729				diff[id] = pageDiff;
730			}
731		}
732
733		delete lookup[oldPages[i].getId()];
734		prev = oldPages[i];
735	}
736
737	for (var id in lookup)
738	{
739		var newPage = lookup[id];
740		inserted.push({data: mxUtils.getXml(newPage.page.node),
741			previous: (newPage.prev != null) ?
742			newPage.prev.getId() : ''});
743	}
744
745	if (Object.keys(diff).length > 0)
746	{
747		result[EditorUi.DIFF_UPDATE] = diff;
748	}
749
750	if (removed.length > 0)
751	{
752		result[EditorUi.DIFF_REMOVE] = removed;
753	}
754
755	if (inserted.length > 0)
756	{
757		result[EditorUi.DIFF_INSERT] = inserted;
758	}
759
760	return result;
761};
762
763/**
764 * Removes all labels, user objects and styles from the given node in-place.
765 */
766EditorUi.prototype.createCellLookup = function(cell, prev, lookup)
767{
768	lookup = (lookup != null) ? lookup : {};
769	lookup[cell.getId()] = {cell: cell, prev: prev};
770
771	var childCount = cell.getChildCount();
772	prev = null;
773
774	for (var i = 0; i < childCount; i++)
775	{
776		var child = cell.getChildAt(i);
777		this.createCellLookup(child, prev, lookup);
778		prev = child;
779	}
780
781	return lookup;
782};
783
784/**
785 * Removes all labels, user objects and styles from the given node in-place.
786 */
787EditorUi.prototype.diffCellRecursive = function(cell, prev, lookup, diff, removed)
788{
789	diff = (diff != null) ? diff : {};
790	var newCell = lookup[cell.getId()];
791	delete lookup[cell.getId()];
792
793	if (newCell == null)
794	{
795		removed.push(cell.getId());
796	}
797	else
798	{
799		var temp = this.diffCell(cell, newCell.cell);
800
801		if (temp.parent != null ||
802			(((newCell.prev != null) ? prev == null : prev != null) ||
803			(prev != null && newCell.prev != null &&
804			prev.getId() != newCell.prev.getId())))
805		{
806			temp.previous = (newCell.prev != null) ? newCell.prev.getId() : '';
807		}
808
809		if (Object.keys(temp).length > 0)
810		{
811			diff[cell.getId()] = temp;
812		}
813	}
814
815	var childCount = cell.getChildCount();
816	prev = null;
817
818	for (var i = 0; i < childCount; i++)
819	{
820		var child = cell.getChildAt(i);
821		this.diffCellRecursive(child, prev, lookup, diff, removed);
822		prev = child;
823	}
824
825	return diff;
826};
827
828/**
829 * Removes all labels, user objects and styles from the given node in-place.
830 */
831EditorUi.prototype.diffPage = function(oldPage, newPage)
832{
833	var inserted = [];
834	var removed = [];
835	var result = {};
836
837	this.updatePageRoot(oldPage);
838	this.updatePageRoot(newPage);
839
840	var lookup = this.createCellLookup(newPage.root);
841	var diff = this.diffCellRecursive(oldPage.root, null, lookup, diff, removed);
842
843	for (var id in lookup)
844	{
845		var newCell = lookup[id];
846		inserted.push(this.getJsonForCell(newCell.cell, newCell.prev));
847	}
848
849	if (Object.keys(diff).length > 0)
850	{
851		result[EditorUi.DIFF_UPDATE] = diff;
852	}
853
854	if (removed.length > 0)
855	{
856		result[EditorUi.DIFF_REMOVE] = removed;
857	}
858
859	if (inserted.length > 0)
860	{
861		result[EditorUi.DIFF_INSERT] = inserted;
862	}
863
864	return result;
865};
866
867/**
868 * Removes all labels, user objects and styles from the given node in-place.
869 */
870EditorUi.prototype.diffViewState = function(oldPage, newPage)
871{
872	var source = oldPage.viewState;
873	var target = newPage.viewState;
874	var result = {};
875
876	if (newPage == this.currentPage)
877	{
878		target = this.editor.graph.getViewState();
879	}
880
881	if (source != null && target != null)
882	{
883		for (var key in this.viewStateProperties)
884		{
885			// LATER: Check if normalization is needed for
886			// object attribute order to compare JSON
887			var old = JSON.stringify(source[key]);
888			var now = JSON.stringify(target[key]);
889
890			if (old != now)
891			{
892				result[key] = now;
893			}
894		}
895	}
896
897	return result;
898};
899
900/**
901 * Removes all labels, user objects and styles from the given node in-place.
902 */
903EditorUi.prototype.getCellForJson = function(json)
904{
905	var geometry = (json.geometry != null) ? this.codec.decode(
906		mxUtils.parseXml(json.geometry).documentElement) : null;
907	var value = json.value;
908
909	if (json.xmlValue != null)
910	{
911		value = mxUtils.parseXml(json.xmlValue).documentElement;
912	}
913
914	var cell = new mxCell(value, geometry, json.style);
915	cell.connectable = json.connectable != 0;
916	cell.collapsed = json.collapsed == 1;
917	cell.visible = json.visible != 0;
918	cell.vertex = json.vertex == 1;
919	cell.edge = json.edge == 1;
920	cell.id = json.id;
921
922	for (var key in json)
923	{
924		if (!this.cellProperties[key])
925		{
926			cell[key] = json[key];
927		}
928	}
929
930	return cell;
931};
932
933/**
934 * Removes all labels, user objects and styles from the given node in-place.
935 */
936EditorUi.prototype.getJsonForCell = function(cell, previous)
937{
938	var result = {id: cell.getId()};
939
940	if (cell.vertex)
941	{
942		result.vertex = 1;
943	}
944
945	if (cell.edge)
946	{
947		result.edge = 1;
948	}
949
950	if (!cell.connectable)
951	{
952		result.connectable = 0;
953	}
954
955	if (cell.parent != null)
956	{
957		result.parent = cell.parent.getId();
958	}
959
960	if (previous != null)
961	{
962		result.previous = previous.getId();
963	}
964
965	if (cell.source != null)
966	{
967		result.source = cell.source.getId();
968	}
969
970	if (cell.target != null)
971	{
972		result.target = cell.target.getId();
973	}
974
975	if (cell.style != null)
976	{
977		result.style = cell.style;
978	}
979
980	if (cell.geometry != null)
981	{
982		result.geometry = mxUtils.getXml(this.codec.encode(cell.geometry));
983	}
984
985	if (cell.collapsed)
986	{
987		result.collapsed = 1;
988	}
989
990	if (!cell.visible)
991	{
992		result.visible = 0;
993	}
994
995	if (cell.value != null)
996	{
997		if (typeof cell.value === 'object' && typeof cell.value.nodeType === 'number' &&
998			typeof cell.value.nodeName === 'string' && typeof cell.value.getAttribute === 'function')
999		{
1000			result.xmlValue = mxUtils.getXml(cell.value);
1001		}
1002		else
1003		{
1004			result.value = cell.value;
1005		}
1006	}
1007
1008	for (var key in cell)
1009	{
1010		if (!this.cellProperties[key] &&
1011			typeof cell[key] !== 'function')
1012		{
1013			result[key] = cell[key];
1014		}
1015	}
1016
1017	return result;
1018};
1019
1020/**
1021 * Removes all labels, user objects and styles from the given node in-place.
1022 */
1023EditorUi.prototype.diffCell = function(oldCell, newCell)
1024{
1025	var diff = {};
1026
1027	if (oldCell.vertex != newCell.vertex)
1028	{
1029		diff.vertex = (newCell.vertex) ? 1 : 0;
1030	}
1031
1032	if (oldCell.edge != newCell.edge)
1033	{
1034		diff.edge = (newCell.edge) ? 1 : 0;
1035	}
1036
1037	if (oldCell.connectable != newCell.connectable)
1038	{
1039		diff.connectable = (newCell.connectable) ? 1 : 0;
1040	}
1041
1042	if (((oldCell.parent != null) ? newCell.parent == null : newCell.parent != null) ||
1043		(oldCell.parent != null && newCell.parent != null &&
1044		oldCell.parent.getId() != newCell.parent.getId()))
1045	{
1046		diff.parent = (newCell.parent != null) ? newCell.parent.getId() : '';
1047	}
1048
1049	if (((oldCell.source != null) ? newCell.source == null : newCell.source != null) ||
1050		(oldCell.source != null && newCell.source != null &&
1051		oldCell.source.getId() != newCell.source.getId()))
1052	{
1053		diff.source = (newCell.source != null) ? newCell.source.getId() : '';
1054	}
1055
1056	if (((oldCell.target != null) ? newCell.target == null : newCell.target != null) ||
1057		(oldCell.target != null && newCell.target != null &&
1058		oldCell.target.getId() != newCell.target.getId()))
1059	{
1060		diff.target = (newCell.target != null) ? newCell.target.getId() : '';
1061	}
1062
1063	function isNode(value)
1064	{
1065		return value != null && typeof value === 'object' && typeof value.nodeType === 'number' &&
1066			typeof value.nodeName === 'string' && typeof value.getAttribute === 'function';
1067	};
1068
1069	if (isNode(oldCell.value) && isNode(newCell.value))
1070	{
1071		if (!oldCell.value.isEqualNode(newCell.value))
1072		{
1073			diff.xmlValue = mxUtils.getXml(newCell.value);
1074		}
1075	}
1076	else if (oldCell.value != newCell.value)
1077	{
1078		if (isNode(newCell.value))
1079		{
1080			diff.xmlValue = mxUtils.getXml(newCell.value);
1081		}
1082		else
1083		{
1084			diff.value = (newCell.value != null) ? newCell.value : null;
1085		}
1086	}
1087
1088	if (oldCell.style != newCell.style)
1089	{
1090		// LATER: Split into keys and do fine-grained diff
1091		diff.style = newCell.style;
1092	}
1093
1094	if (oldCell.visible != newCell.visible)
1095	{
1096		diff.visible = (newCell.visible) ? 1 : 0;
1097	}
1098
1099	if (oldCell.collapsed != newCell.collapsed)
1100	{
1101		diff.collapsed = (newCell.collapsed) ? 1 : 0;
1102	}
1103
1104	// FIXME: Proto only needed because source.geometry has no constructor (wrong type?)
1105	if (!this.isObjectEqual(oldCell.geometry, newCell.geometry, new mxGeometry()))
1106	{
1107		var node = this.codec.encode(newCell.geometry);
1108
1109		if (node != null)
1110		{
1111			diff.geometry = mxUtils.getXml(node);
1112		}
1113	}
1114
1115	// Compares all keys from oldCell to newCell and uses null in the diff
1116	// to force the attribute to be removed in the receiving client
1117	for (var key in oldCell)
1118	{
1119		if (!this.cellProperties[key] && typeof oldCell[key] !== 'function' &&
1120			typeof newCell[key] !== 'function' && oldCell[key] != newCell[key])
1121		{
1122			diff[key] = (newCell[key] === undefined) ? null : newCell[key];
1123		}
1124	}
1125
1126	// Compares the remaining keys in newCell with oldCell
1127	for (var key in newCell)
1128	{
1129		if (!(key in oldCell) &&
1130			!this.cellProperties[key] && typeof oldCell[key] !== 'function' &&
1131			typeof newCell[key] !== 'function' && oldCell[key] != newCell[key])
1132		{
1133			diff[key] = (newCell[key] === undefined) ? null : newCell[key];
1134		}
1135	}
1136
1137	return diff;
1138};
1139
1140/**
1141 *
1142 */
1143EditorUi.prototype.isObjectEqual = function(source, target, proto)
1144{
1145	if (source == null && target == null)
1146	{
1147		return true;
1148	}
1149	else if ((source != null) ? target == null : target != null)
1150	{
1151		return false;
1152	}
1153	else
1154	{
1155		var replacer = function(key, value)
1156		{
1157			return (proto == null || proto[key] != value) ? ((value === true) ? 1 : value) : undefined;
1158		};
1159
1160		//console.log('eq', JSON.stringify(source, replacer), JSON.stringify(target, replacer));
1161
1162		return JSON.stringify(source, replacer) == JSON.stringify(target, replacer);
1163	}
1164};
1165