text: /**
   * InsertContent.js
   *
   * Released under LGPL License.
   * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
   *
   * License: http://www.tinymce.com/license
   * Contributing: http://www.tinymce.com/contributing
   */
  
  /**
   * Handles inserts of contents into the editor instance.
   *
   * @class tinymce.InsertContent
   * @private
   */
  define("tinymce/InsertContent", [
  	"tinymce/Env",
  	"tinymce/util/Tools",
  	"tinymce/html/Serializer",
  	"tinymce/caret/CaretWalker",
  	"tinymce/caret/CaretPosition",
  	"tinymce/dom/ElementUtils",
  	"tinymce/dom/NodeType",
  	"tinymce/InsertList"
  ], function(Env, Tools, Serializer, CaretWalker, CaretPosition, ElementUtils, NodeType, InsertList) {
  	var isTableCell = NodeType.matchNodeNames('td th');
  
  	var insertAtCaret = function(editor, value) {
  		var parser, serializer, parentNode, rootNode, fragment, args;
  		var marker, rng, node, node2, bookmarkHtml, merge, data;
  		var textInlineElements = editor.schema.getTextInlineElements();
  		var selection = editor.selection, dom = editor.dom;
  
  		function trimOrPaddLeftRight(html) {
  			var rng, container, offset;
  
  			rng = selection.getRng(true);
  			container = rng.startContainer;
  			offset = rng.startOffset;
  
  			function hasSiblingText(siblingName) {
  				return container[siblingName] && container[siblingName].nodeType == 3;
  			}
  
  			if (container.nodeType == 3) {
  				if (offset > 0) {
  					html = html.replace(/^&nbsp;/, ' ');
  				} else if (!hasSiblingText('previousSibling')) {
  					html = html.replace(/^ /, '&nbsp;');
  				}
  
  				if (offset < container.length) {
  					html = html.replace(/&nbsp;(<br>|)$/, ' ');
  				} else if (!hasSiblingText('nextSibling')) {
  					html = html.replace(/(&nbsp;| )(<br>|)$/, '&nbsp;');
  				}
  			}
  
  			return html;
  		}
  
  		// Removes &nbsp; from a [b] c -> a &nbsp;c -> a c
  		function trimNbspAfterDeleteAndPaddValue() {
  			var rng, container, offset;
  
  			rng = selection.getRng(true);
  			container = rng.startContainer;
  			offset = rng.startOffset;
  
  			if (container.nodeType == 3 && rng.collapsed) {
  				if (container.data[offset] === '\u00a0') {
  					container.deleteData(offset, 1);
  
  					if (!/[\u00a0| ]$/.test(value)) {
  						value += ' ';
  					}
  				} else if (container.data[offset - 1] === '\u00a0') {
  					container.deleteData(offset - 1, 1);
  
  					if (!/[\u00a0| ]$/.test(value)) {
  						value = ' ' + value;
  					}
  				}
  			}
  		}
  
  		function markInlineFormatElements(fragment) {
  			if (merge) {
  				for (node = fragment.firstChild; node; node = node.walk(true)) {
  					if (textInlineElements[node.name]) {
  						node.attr('data-mce-new', "true");
  					}
  				}
  			}
  		}
  
  		function reduceInlineTextElements() {
  			if (merge) {
  				var root = editor.getBody(), elementUtils = new ElementUtils(dom);
  
  				Tools.each(dom.select('*[data-mce-new]'), function(node) {
  					node.removeAttribute('data-mce-new');
  
  					for (var testNode = node.parentNode; testNode && testNode != root; testNode = testNode.parentNode) {
  						if (elementUtils.compare(testNode, node)) {
  							dom.remove(node, true);
  						}
  					}
  				});
  			}
  		}
  
  		function markFragmentElements(fragment) {
  			var node = fragment;
  
  			while ((node = node.walk())) {
  				if (node.type === 1) {
  					node.attr('data-mce-fragment', '1');
  				}
  			}
  		}
  
  		function umarkFragmentElements(elm) {
  			Tools.each(elm.getElementsByTagName('*'), function(elm) {
  				elm.removeAttribute('data-mce-fragment');
  			});
  		}
  
  		function isPartOfFragment(node) {
  			return !!node.getAttribute('data-mce-fragment');
  		}
  
  		function canHaveChildren(node) {
  			return node && !editor.schema.getShortEndedElements()[node.nodeName];
  		}
  
  		function moveSelectionToMarker(marker) {
  			var parentEditableFalseElm, parentBlock, nextRng;
  
  			function getContentEditableFalseParent(node) {
  				var root = editor.getBody();
  
  				for (; node && node !== root; node = node.parentNode) {
  					if (editor.dom.getContentEditable(node) === 'false') {
  						return node;
  					}
  				}
  
  				return null;
  			}
  
  			if (!marker) {
  				return;
  			}
  
  			selection.scrollIntoView(marker);
  
  			// If marker is in cE=false then move selection to that element instead
  			parentEditableFalseElm = getContentEditableFalseParent(marker);
  			if (parentEditableFalseElm) {
  				dom.remove(marker);
  				selection.select(parentEditableFalseElm);
  				return;
  			}
  
  			// Move selection before marker and remove it
  			rng = dom.createRng();
  
  			// If previous sibling is a text node set the selection to the end of that node
  			node = marker.previousSibling;
  			if (node && node.nodeType == 3) {
  				rng.setStart(node, node.nodeValue.length);
  
  				// TODO: Why can't we normalize on IE
  				if (!Env.ie) {
  					node2 = marker.nextSibling;
  					if (node2 && node2.nodeType == 3) {
  						node.appendData(node2.data);
  						node2.parentNode.removeChild(node2);
  					}
  				}
  			} else {
  				// If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
  				rng.setStartBefore(marker);
  				rng.setEndBefore(marker);
  			}
  
  			function findNextCaretRng(rng) {
  				var caretPos = CaretPosition.fromRangeStart(rng);
  				var caretWalker = new CaretWalker(editor.getBody());
  
  				caretPos = caretWalker.next(caretPos);
  				if (caretPos) {
  					return caretPos.toRange();
  				}
  			}
  
  			// Remove the marker node and set the new range
  			parentBlock = dom.getParent(marker, dom.isBlock);
  			dom.remove(marker);
  
  			if (parentBlock && dom.isEmpty(parentBlock)) {
  				editor.$(parentBlock).empty();
  
  				rng.setStart(parentBlock, 0);
  				rng.setEnd(parentBlock, 0);
  
  				if (!isTableCell(parentBlock) && !isPartOfFragment(parentBlock) && (nextRng = findNextCaretRng(rng))) {
  					rng = nextRng;
  					dom.remove(parentBlock);
  				} else {
  					dom.add(parentBlock, dom.create('br', {'data-mce-bogus': '1'}));
  				}
  			}
  
  			selection.setRng(rng);
  		}
  
  		if (typeof value != 'string') {
  			merge = value.merge;
  			data = value.data;
  			value = value.content;
  		}
  
  		// Check for whitespace before/after value
  		if (/^ | $/.test(value)) {
  			value = trimOrPaddLeftRight(value);
  		}
  
  		// Setup parser and serializer
  		parser = editor.parser;
  		serializer = new Serializer({
  			validate: editor.settings.validate
  		}, editor.schema);
  		bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">&#xFEFF;&#x200B;</span>';
  
  		// Run beforeSetContent handlers on the HTML to be inserted
  		args = {content: value, format: 'html', selection: true};
  		editor.fire('BeforeSetContent', args);
  		value = args.content;
  
  		// Add caret at end of contents if it's missing
  		if (value.indexOf('{$caret}') == -1) {
  			value += '{$caret}';
  		}
  
  		// Replace the caret marker with a span bookmark element
  		value = value.replace(/\{\$caret\}/, bookmarkHtml);
  
  		// If selection is at <body>|<p></p> then move it into <body><p>|</p>
  		rng = selection.getRng();
  		var caretElement = rng.startContainer || (rng.parentElement ? rng.parentElement() : null);
  		var body = editor.getBody();
  		if (caretElement === body && selection.isCollapsed()) {
  			if (dom.isBlock(body.firstChild) && canHaveChildren(body.firstChild) && dom.isEmpty(body.firstChild)) {
  				rng = dom.createRng();
  				rng.setStart(body.firstChild, 0);
  				rng.setEnd(body.firstChild, 0);
  				selection.setRng(rng);
  			}
  		}
  
  		// Insert node maker where we will insert the new HTML and get it's parent
  		if (!selection.isCollapsed()) {
  			// Fix for #2595 seems that delete removes one extra character on
  			// WebKit for some odd reason if you double click select a word
  			editor.selection.setRng(editor.selection.getRng());
  			editor.getDoc().execCommand('Delete', false, null);
  			trimNbspAfterDeleteAndPaddValue();
  		}
  
  		parentNode = selection.getNode();
  
  		// Parse the fragment within the context of the parent node
  		var parserArgs = {context: parentNode.nodeName.toLowerCase(), data: data};
  		fragment = parser.parse(value, parserArgs);
  
  		// Custom handling of lists
  		if (InsertList.isListFragment(fragment) && InsertList.isParentBlockLi(dom, parentNode)) {
  			rng = InsertList.insertAtCaret(serializer, dom, editor.selection.getRng(), fragment);
  			editor.selection.setRng(rng);
  			editor.fire('SetContent', args);
  			return;
  		}
  
  		markFragmentElements(fragment);
  		markInlineFormatElements(fragment);
  
  		// Move the caret to a more suitable location
  		node = fragment.lastChild;
  		if (node.attr('id') == 'mce_marker') {
  			marker = node;
  
  			for (node = node.prev; node; node = node.walk(true)) {
  				if (node.type == 3 || !dom.isBlock(node.name)) {
  					if (editor.schema.isValidChild(node.parent.name, 'span')) {
  						node.parent.insert(marker, node, node.name === 'br');
  					}
  					break;
  				}
  			}
  		}
  
  		editor._selectionOverrides.showBlockCaretContainer(parentNode);
  
  		// If parser says valid we can insert the contents into that parent
  		if (!parserArgs.invalid) {
  			value = serializer.serialize(fragment);
  
  			// Check if parent is empty or only has one BR element then set the innerHTML of that parent
  			node = parentNode.firstChild;
  			node2 = parentNode.lastChild;
  			if (!node || (node === node2 && node.nodeName === 'BR')) {
  				dom.setHTML(parentNode, value);
  			} else {
  				selection.setContent(value);
  			}
  		} else {
  			// If the fragment was invalid within that context then we need
  			// to parse and process the parent it's inserted into
  
  			// Insert bookmark node and get the parent
  			selection.setContent(bookmarkHtml);
  			parentNode = selection.getNode();
  			rootNode = editor.getBody();
  
  			// Opera will return the document node when selection is in root
  			if (parentNode.nodeType == 9) {
  				parentNode = node = rootNode;
  			} else {
  				node = parentNode;
  			}
  
  			// Find the ancestor just before the root element
  			while (node !== rootNode) {
  				parentNode = node;
  				node = node.parentNode;
  			}
  
  			// Get the outer/inner HTML depending on if we are in the root and parser and serialize that
  			value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
  			value = serializer.serialize(
  				parser.parse(
  					// Need to replace by using a function since $ in the contents would otherwise be a problem
  					value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
  						return serializer.serialize(fragment);
  					})
  				)
  			);
  
  			// Set the inner/outer HTML depending on if we are in the root or not
  			if (parentNode == rootNode) {
  				dom.setHTML(rootNode, value);
  			} else {
  				dom.setOuterHTML(parentNode, value);
  			}
  		}
  
  		reduceInlineTextElements();
  		moveSelectionToMarker(dom.get('mce_marker'));
  		umarkFragmentElements(editor.getBody());
  		editor.fire('SetContent', args);
  		editor.addVisual();
  	};
  
  	return {
  		insertAtCaret: insertAtCaret
  	};
  });