/** * @license Rangy, a cross-browser JavaScript range and selection library * http://code.google.com/p/rangy/ * * Copyright 2012, Tim Down * Licensed under the MIT license. * Version: 1.2.3 * Build date: 26 February 2012 */ window['rangy'] = (function() { var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined"; var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", "commonAncestorContainer", "START_TO_START", "START_TO_END", "END_TO_START", "END_TO_END"]; var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore", "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents", "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"]; var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"]; // Subset of TextRange's full set of methods that we're interested in var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "getBookmark", "moveToBookmark", "moveToElementText", "parentElement", "pasteHTML", "select", "setEndPoint", "getBoundingClientRect"]; /*----------------------------------------------------------------------------------------------------------------*/ // Trio of functions taken from Peter Michaux's article: // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting function isHostMethod(o, p) { var t = typeof o[p]; return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown"; } function isHostObject(o, p) { return !!(typeof o[p] == OBJECT && o[p]); } function isHostProperty(o, p) { return typeof o[p] != UNDEFINED; } // Creates a convenience function to save verbose repeated calls to tests functions function createMultiplePropertyTest(testFunc) { return function(o, props) { var i = props.length; while (i--) { if (!testFunc(o, props[i])) { return false; } } return true; }; } // Next trio of functions are a convenience to save verbose repeated calls to previous two functions var areHostMethods = createMultiplePropertyTest(isHostMethod); var areHostObjects = createMultiplePropertyTest(isHostObject); var areHostProperties = createMultiplePropertyTest(isHostProperty); function isTextRange(range) { return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties); } var api = { version: "1.2.3", initialized: false, supported: true, util: { isHostMethod: isHostMethod, isHostObject: isHostObject, isHostProperty: isHostProperty, areHostMethods: areHostMethods, areHostObjects: areHostObjects, areHostProperties: areHostProperties, isTextRange: isTextRange }, features: {}, modules: {}, config: { alertOnWarn: false, preferTextRange: false } }; function fail(reason) { window.alert("Rangy not supported in your browser. Reason: " + reason); api.initialized = true; api.supported = false; } api.fail = fail; function warn(msg) { var warningMessage = "Rangy warning: " + msg; if (api.config.alertOnWarn) { window.alert(warningMessage); } else if (typeof window.console != UNDEFINED && typeof window.console.log != UNDEFINED) { window.console.log(warningMessage); } } api.warn = warn; if ({}.hasOwnProperty) { api.util.extend = function(o, props) { for (var i in props) { if (props.hasOwnProperty(i)) { o[i] = props[i]; } } }; } else { fail("hasOwnProperty not supported"); } var initListeners = []; var moduleInitializers = []; // Initialization function init() { if (api.initialized) { return; } var testRange; var implementsDomRange = false, implementsTextRange = false; // First, perform basic feature tests if (isHostMethod(document, "createRange")) { testRange = document.createRange(); if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) { implementsDomRange = true; } testRange.detach(); } var body = isHostObject(document, "body") ? document.body : document.getElementsByTagName("body")[0]; if (body && isHostMethod(body, "createTextRange")) { testRange = body.createTextRange(); if (isTextRange(testRange)) { implementsTextRange = true; } } if (!implementsDomRange && !implementsTextRange) { fail("Neither Range nor TextRange are implemented"); } api.initialized = true; api.features = { implementsDomRange: implementsDomRange, implementsTextRange: implementsTextRange }; // Initialize modules and call init listeners var allListeners = moduleInitializers.concat(initListeners); for (var i = 0, len = allListeners.length; i < len; ++i) { try { allListeners[i](api); } catch (ex) { if (isHostObject(window, "console") && isHostMethod(window.console, "log")) { window.console.log("Init listener threw an exception. Continuing.", ex); } } } } // Allow external scripts to initialize this library in case it's loaded after the document has loaded api.init = init; // Execute listener immediately if already initialized api.addInitListener = function(listener) { if (api.initialized) { listener(api); } else { initListeners.push(listener); } }; var createMissingNativeApiListeners = []; api.addCreateMissingNativeApiListener = function(listener) { createMissingNativeApiListeners.push(listener); }; function createMissingNativeApi(win) { win = win || window; init(); // Notify listeners for (var i = 0, len = createMissingNativeApiListeners.length; i < len; ++i) { createMissingNativeApiListeners[i](win); } } api.createMissingNativeApi = createMissingNativeApi; /** * @constructor */ function Module(name) { this.name = name; this.initialized = false; this.supported = false; } Module.prototype.fail = function(reason) { this.initialized = true; this.supported = false; throw new Error("Module '" + this.name + "' failed to load: " + reason); }; Module.prototype.warn = function(msg) { api.warn("Module " + this.name + ": " + msg); }; Module.prototype.createError = function(msg) { return new Error("Error in Rangy " + this.name + " module: " + msg); }; api.createModule = function(name, initFunc) { var module = new Module(name); api.modules[name] = module; moduleInitializers.push(function(api) { initFunc(api, module); module.initialized = true; module.supported = true; }); }; api.requireModules = function(modules) { for (var i = 0, len = modules.length, module, moduleName; i < len; ++i) { moduleName = modules[i]; module = api.modules[moduleName]; if (!module || !(module instanceof Module)) { throw new Error("Module '" + moduleName + "' not found"); } if (!module.supported) { throw new Error("Module '" + moduleName + "' not supported"); } } }; /*----------------------------------------------------------------------------------------------------------------*/ // Wait for document to load before running tests var docReady = false; var loadHandler = function(e) { if (!docReady) { docReady = true; if (!api.initialized) { init(); } } }; // Test whether we have window and document objects that we will need if (typeof window == UNDEFINED) { fail("No window found"); return; } if (typeof document == UNDEFINED) { fail("No document found"); return; } if (isHostMethod(document, "addEventListener")) { document.addEventListener("DOMContentLoaded", loadHandler, false); } // Add a fallback in case the DOMContentLoaded event isn't supported if (isHostMethod(window, "addEventListener")) { window.addEventListener("load", loadHandler, false); } else if (isHostMethod(window, "attachEvent")) { window.attachEvent("onload", loadHandler); } else { fail("Window does not have required addEventListener or attachEvent method"); } return api; })(); rangy.createModule("DomUtil", function(api, module) { var UNDEF = "undefined"; var util = api.util; // Perform feature tests if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) { module.fail("document missing a Node creation method"); } if (!util.isHostMethod(document, "getElementsByTagName")) { module.fail("document missing getElementsByTagName method"); } var el = document.createElement("div"); if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] || !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) { module.fail("Incomplete Element implementation"); } // innerHTML is required for Range's createContextualFragment method if (!util.isHostProperty(el, "innerHTML")) { module.fail("Element is missing innerHTML property"); } var textNode = document.createTextNode("test"); if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] || !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) || !util.areHostProperties(textNode, ["data"]))) { module.fail("Incomplete Text Node implementation"); } /*----------------------------------------------------------------------------------------------------------------*/ // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that // contains just the document as a single element and the value searched for is the document. var arrayContains = /*Array.prototype.indexOf ? function(arr, val) { return arr.indexOf(val) > -1; }:*/ function(arr, val) { var i = arr.length; while (i--) { if (arr[i] === val) { return true; } } return false; }; // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI function isHtmlNamespace(node) { var ns; return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml"); } function parentElement(node) { var parent = node.parentNode; return (parent.nodeType == 1) ? parent : null; } function getNodeIndex(node) { var i = 0; while( (node = node.previousSibling) ) { i++; } return i; } function getNodeLength(node) { var childNodes; return isCharacterDataNode(node) ? node.length : ((childNodes = node.childNodes) ? childNodes.length : 0); } function getCommonAncestor(node1, node2) { var ancestors = [], n; for (n = node1; n; n = n.parentNode) { ancestors.push(n); } for (n = node2; n; n = n.parentNode) { if (arrayContains(ancestors, n)) { return n; } } return null; } function isAncestorOf(ancestor, descendant, selfIsAncestor) { var n = selfIsAncestor ? descendant : descendant.parentNode; while (n) { if (n === ancestor) { return true; } else { n = n.parentNode; } } return false; } function getClosestAncestorIn(node, ancestor, selfIsAncestor) { var p, n = selfIsAncestor ? node : node.parentNode; while (n) { p = n.parentNode; if (p === ancestor) { return n; } n = p; } return null; } function isCharacterDataNode(node) { var t = node.nodeType; return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment } function insertAfter(node, precedingNode) { var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode; if (nextNode) { parent.insertBefore(node, nextNode); } else { parent.appendChild(node); } return node; } // Note that we cannot use splitText() because it is bugridden in IE 9. function splitDataNode(node, index) { var newNode = node.cloneNode(false); newNode.deleteData(0, index); node.deleteData(index, node.length - index); insertAfter(newNode, node); return newNode; } function getDocument(node) { if (node.nodeType == 9) { return node; } else if (typeof node.ownerDocument != UNDEF) { return node.ownerDocument; } else if (typeof node.document != UNDEF) { return node.document; } else if (node.parentNode) { return getDocument(node.parentNode); } else { throw new Error("getDocument: no document found for node"); } } function getWindow(node) { var doc = getDocument(node); if (typeof doc.defaultView != UNDEF) { return doc.defaultView; } else if (typeof doc.parentWindow != UNDEF) { return doc.parentWindow; } else { throw new Error("Cannot get a window object for node"); } } function getIframeDocument(iframeEl) { if (typeof iframeEl.contentDocument != UNDEF) { return iframeEl.contentDocument; } else if (typeof iframeEl.contentWindow != UNDEF) { return iframeEl.contentWindow.document; } else { throw new Error("getIframeWindow: No Document object found for iframe element"); } } function getIframeWindow(iframeEl) { if (typeof iframeEl.contentWindow != UNDEF) { return iframeEl.contentWindow; } else if (typeof iframeEl.contentDocument != UNDEF) { return iframeEl.contentDocument.defaultView; } else { throw new Error("getIframeWindow: No Window object found for iframe element"); } } function getBody(doc) { return util.isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0]; } function getRootContainer(node) { var parent; while ( (parent = node.parentNode) ) { node = parent; } return node; } function comparePoints(nodeA, offsetA, nodeB, offsetB) { // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing var nodeC, root, childA, childB, n; if (nodeA == nodeB) { // Case 1: nodes are the same return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1; } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) { // Case 2: node C (container B or an ancestor) is a child node of A return offsetA <= getNodeIndex(nodeC) ? -1 : 1; } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) { // Case 3: node C (container A or an ancestor) is a child node of B return getNodeIndex(nodeC) < offsetB ? -1 : 1; } else { // Case 4: containers are siblings or descendants of siblings root = getCommonAncestor(nodeA, nodeB); childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true); childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true); if (childA === childB) { // This shouldn't be possible throw new Error("comparePoints got to case 4 and childA and childB are the same!"); } else { n = root.firstChild; while (n) { if (n === childA) { return -1; } else if (n === childB) { return 1; } n = n.nextSibling; } throw new Error("Should not be here!"); } } } function fragmentFromNodeChildren(node) { var fragment = getDocument(node).createDocumentFragment(), child; while ( (child = node.firstChild) ) { fragment.appendChild(child); } return fragment; } function inspectNode(node) { if (!node) { return "[No node]"; } if (isCharacterDataNode(node)) { return '"' + node.data + '"'; } else if (node.nodeType == 1) { var idAttr = node.id ? ' id="' + node.id + '"' : ""; return "<" + node.nodeName + idAttr + ">[" + node.childNodes.length + "]"; } else { return node.nodeName; } } /** * @constructor */ function NodeIterator(root) { this.root = root; this._next = root; } NodeIterator.prototype = { _current: null, hasNext: function() { return !!this._next; }, next: function() { var n = this._current = this._next; var child, next; if (this._current) { child = n.firstChild; if (child) { this._next = child; } else { next = null; while ((n !== this.root) && !(next = n.nextSibling)) { n = n.parentNode; } this._next = next; } } return this._current; }, detach: function() { this._current = this._next = this.root = null; } }; function createIterator(root) { return new NodeIterator(root); } /** * @constructor */ function DomPosition(node, offset) { this.node = node; this.offset = offset; } DomPosition.prototype = { equals: function(pos) { return this.node === pos.node & this.offset == pos.offset; }, inspect: function() { return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]"; } }; /** * @constructor */ function DOMException(codeName) { this.code = this[codeName]; this.codeName = codeName; this.message = "DOMException: " + this.codeName; } DOMException.prototype = { INDEX_SIZE_ERR: 1, HIERARCHY_REQUEST_ERR: 3, WRONG_DOCUMENT_ERR: 4, NO_MODIFICATION_ALLOWED_ERR: 7, NOT_FOUND_ERR: 8, NOT_SUPPORTED_ERR: 9, INVALID_STATE_ERR: 11 }; DOMException.prototype.toString = function() { return this.message; }; api.dom = { arrayContains: arrayContains, isHtmlNamespace: isHtmlNamespace, parentElement: parentElement, getNodeIndex: getNodeIndex, getNodeLength: getNodeLength, getCommonAncestor: getCommonAncestor, isAncestorOf: isAncestorOf, getClosestAncestorIn: getClosestAncestorIn, isCharacterDataNode: isCharacterDataNode, insertAfter: insertAfter, splitDataNode: splitDataNode, getDocument: getDocument, getWindow: getWindow, getIframeWindow: getIframeWindow, getIframeDocument: getIframeDocument, getBody: getBody, getRootContainer: getRootContainer, comparePoints: comparePoints, inspectNode: inspectNode, fragmentFromNodeChildren: fragmentFromNodeChildren, createIterator: createIterator, DomPosition: DomPosition }; api.DOMException = DOMException; });rangy.createModule("DomRange", function(api, module) { api.requireModules( ["DomUtil"] ); var dom = api.dom; var DomPosition = dom.DomPosition; var DOMException = api.DOMException; /*----------------------------------------------------------------------------------------------------------------*/ // Utility functions function isNonTextPartiallySelected(node, range) { return (node.nodeType != 3) && (dom.isAncestorOf(node, range.startContainer, true) || dom.isAncestorOf(node, range.endContainer, true)); } function getRangeDocument(range) { return dom.getDocument(range.startContainer); } function dispatchEvent(range, type, args) { var listeners = range._listeners[type]; if (listeners) { for (var i = 0, len = listeners.length; i < len; ++i) { listeners[i].call(range, {target: range, args: args}); } } } function getBoundaryBeforeNode(node) { return new DomPosition(node.parentNode, dom.getNodeIndex(node)); } function getBoundaryAfterNode(node) { return new DomPosition(node.parentNode, dom.getNodeIndex(node) + 1); } function insertNodeAtPosition(node, n, o) { var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node; if (dom.isCharacterDataNode(n)) { if (o == n.length) { dom.insertAfter(node, n); } else { n.parentNode.insertBefore(node, o == 0 ? n : dom.splitDataNode(n, o)); } } else if (o >= n.childNodes.length) { n.appendChild(node); } else { n.insertBefore(node, n.childNodes[o]); } return firstNodeInserted; } function cloneSubtree(iterator) { var partiallySelected; for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) { partiallySelected = iterator.isPartiallySelectedSubtree(); node = node.cloneNode(!partiallySelected); if (partiallySelected) { subIterator = iterator.getSubtreeIterator(); node.appendChild(cloneSubtree(subIterator)); subIterator.detach(true); } if (node.nodeType == 10) { // DocumentType throw new DOMException("HIERARCHY_REQUEST_ERR"); } frag.appendChild(node); } return frag; } function iterateSubtree(rangeIterator, func, iteratorState) { var it, n; iteratorState = iteratorState || { stop: false }; for (var node, subRangeIterator; node = rangeIterator.next(); ) { //log.debug("iterateSubtree, partially selected: " + rangeIterator.isPartiallySelectedSubtree(), nodeToString(node)); if (rangeIterator.isPartiallySelectedSubtree()) { // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of the // node selected by the Range. if (func(node) === false) { iteratorState.stop = true; return; } else { subRangeIterator = rangeIterator.getSubtreeIterator(); iterateSubtree(subRangeIterator, func, iteratorState); subRangeIterator.detach(true); if (iteratorState.stop) { return; } } } else { // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its // descendant it = dom.createIterator(node); while ( (n = it.next()) ) { if (func(n) === false) { iteratorState.stop = true; return; } } } } } function deleteSubtree(iterator) { var subIterator; while (iterator.next()) { if (iterator.isPartiallySelectedSubtree()) { subIterator = iterator.getSubtreeIterator(); deleteSubtree(subIterator); subIterator.detach(true); } else { iterator.remove(); } } } function extractSubtree(iterator) { for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) { if (iterator.isPartiallySelectedSubtree()) { node = node.cloneNode(false); subIterator = iterator.getSubtreeIterator(); node.appendChild(extractSubtree(subIterator)); subIterator.detach(true); } else { iterator.remove(); } if (node.nodeType == 10) { // DocumentType throw new DOMException("HIERARCHY_REQUEST_ERR"); } frag.appendChild(node); } return frag; } function getNodesInRange(range, nodeTypes, filter) { //log.info("getNodesInRange, " + nodeTypes.join(",")); var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex; var filterExists = !!filter; if (filterNodeTypes) { regex = new RegExp("^(" + nodeTypes.join("|") + ")$"); } var nodes = []; iterateSubtree(new RangeIterator(range, false), function(node) { if ((!filterNodeTypes || regex.test(node.nodeType)) && (!filterExists || filter(node))) { nodes.push(node); } }); return nodes; } function inspect(range) { var name = (typeof range.getName == "undefined") ? "Range" : range.getName(); return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " + dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]"; } /*----------------------------------------------------------------------------------------------------------------*/ // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange) /** * @constructor */ function RangeIterator(range, clonePartiallySelectedTextNodes) { this.range = range; this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes; if (!range.collapsed) { this.sc = range.startContainer; this.so = range.startOffset; this.ec = range.endContainer; this.eo = range.endOffset; var root = range.commonAncestorContainer; if (this.sc === this.ec && dom.isCharacterDataNode(this.sc)) { this.isSingleCharacterDataNode = true; this._first = this._last = this._next = this.sc; } else { this._first = this._next = (this.sc === root && !dom.isCharacterDataNode(this.sc)) ? this.sc.childNodes[this.so] : dom.getClosestAncestorIn(this.sc, root, true); this._last = (this.ec === root && !dom.isCharacterDataNode(this.ec)) ? this.ec.childNodes[this.eo - 1] : dom.getClosestAncestorIn(this.ec, root, true); } } } RangeIterator.prototype = { _current: null, _next: null, _first: null, _last: null, isSingleCharacterDataNode: false, reset: function() { this._current = null; this._next = this._first; }, hasNext: function() { return !!this._next; }, next: function() { // Move to next node var current = this._current = this._next; if (current) { this._next = (current !== this._last) ? current.nextSibling : null; // Check for partially selected text nodes if (dom.isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) { if (current === this.ec) { (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo); } if (this._current === this.sc) { (current = current.cloneNode(true)).deleteData(0, this.so); } } } return current; }, remove: function() { var current = this._current, start, end; if (dom.isCharacterDataNode(current) && (current === this.sc || current === this.ec)) { start = (current === this.sc) ? this.so : 0; end = (current === this.ec) ? this.eo : current.length; if (start != end) { current.deleteData(start, end - start); } } else { if (current.parentNode) { current.parentNode.removeChild(current); } else { } } }, // Checks if the current node is partially selected isPartiallySelectedSubtree: function() { var current = this._current; return isNonTextPartiallySelected(current, this.range); }, getSubtreeIterator: function() { var subRange; if (this.isSingleCharacterDataNode) { subRange = this.range.cloneRange(); subRange.collapse(); } else { subRange = new Range(getRangeDocument(this.range)); var current = this._current; var startContainer = current, startOffset = 0, endContainer = current, endOffset = dom.getNodeLength(current); if (dom.isAncestorOf(current, this.sc, true)) { startContainer = this.sc; startOffset = this.so; } if (dom.isAncestorOf(current, this.ec, true)) { endContainer = this.ec; endOffset = this.eo; } updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset); } return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes); }, detach: function(detachRange) { if (detachRange) { this.range.detach(); } this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null; } }; /*----------------------------------------------------------------------------------------------------------------*/ // Exceptions /** * @constructor */ function RangeException(codeName) { this.code = this[codeName]; this.codeName = codeName; this.message = "RangeException: " + this.codeName; } RangeException.prototype = { BAD_BOUNDARYPOINTS_ERR: 1, INVALID_NODE_TYPE_ERR: 2 }; RangeException.prototype.toString = function() { return this.message; }; /*----------------------------------------------------------------------------------------------------------------*/ /** * Currently iterates through all nodes in the range on creation until I think of a decent way to do it * TODO: Look into making this a proper iterator, not requiring preloading everything first * @constructor */ function RangeNodeIterator(range, nodeTypes, filter) { this.nodes = getNodesInRange(range, nodeTypes, filter); this._next = this.nodes[0]; this._position = 0; } RangeNodeIterator.prototype = { _current: null, hasNext: function() { return !!this._next; }, next: function() { this._current = this._next; this._next = this.nodes[ ++this._position ]; return this._current; }, detach: function() { this._current = this._next = this.nodes = null; } }; var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10]; var rootContainerNodeTypes = [2, 9, 11]; var readonlyNodeTypes = [5, 6, 10, 12]; var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11]; var surroundNodeTypes = [1, 3, 4, 5, 7, 8]; function createAncestorFinder(nodeTypes) { return function(node, selfIsAncestor) { var t, n = selfIsAncestor ? node : node.parentNode; while (n) { t = n.nodeType; if (dom.arrayContains(nodeTypes, t)) { return n; } n = n.parentNode; } return null; }; } var getRootContainer = dom.getRootContainer; var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] ); var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes); var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] ); function assertNoDocTypeNotationEntityAncestor(node, allowSelf) { if (getDocTypeNotationEntityAncestor(node, allowSelf)) { throw new RangeException("INVALID_NODE_TYPE_ERR"); } } function assertNotDetached(range) { if (!range.startContainer) { throw new DOMException("INVALID_STATE_ERR"); } } function assertValidNodeType(node, invalidTypes) { if (!dom.arrayContains(invalidTypes, node.nodeType)) { throw new RangeException("INVALID_NODE_TYPE_ERR"); } } function assertValidOffset(node, offset) { if (offset < 0 || offset > (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length)) { throw new DOMException("INDEX_SIZE_ERR"); } } function assertSameDocumentOrFragment(node1, node2) { if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) { throw new DOMException("WRONG_DOCUMENT_ERR"); } } function assertNodeNotReadOnly(node) { if (getReadonlyAncestor(node, true)) { throw new DOMException("NO_MODIFICATION_ALLOWED_ERR"); } } function assertNode(node, codeName) { if (!node) { throw new DOMException(codeName); } } function isOrphan(node) { return !dom.arrayContains(rootContainerNodeTypes, node.nodeType) && !getDocumentOrFragmentContainer(node, true); } function isValidOffset(node, offset) { return offset <= (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length); } function isRangeValid(range) { return (!!range.startContainer && !!range.endContainer && !isOrphan(range.startContainer) && !isOrphan(range.endContainer) && isValidOffset(range.startContainer, range.startOffset) && isValidOffset(range.endContainer, range.endOffset)); } function assertRangeValid(range) { assertNotDetached(range); if (!isRangeValid(range)) { throw new Error("Range error: Range is no longer valid after DOM mutation (" + range.inspect() + ")"); } } /*----------------------------------------------------------------------------------------------------------------*/ // Test the browser's innerHTML support to decide how to implement createContextualFragment var styleEl = document.createElement("style"); var htmlParsingConforms = false; try { styleEl.innerHTML = "x"; htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node } catch (e) { // IE 6 and 7 throw } api.features.htmlParsingConforms = htmlParsingConforms; var createContextualFragment = htmlParsingConforms ? // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See // discussion and base code for this implementation at issue 67. // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface // Thanks to Aleks Williams. function(fragmentStr) { // "Let node the context object's start's node." var node = this.startContainer; var doc = dom.getDocument(node); // "If the context object's start's node is null, raise an INVALID_STATE_ERR // exception and abort these steps." if (!node) { throw new DOMException("INVALID_STATE_ERR"); } // "Let element be as follows, depending on node's interface:" // Document, Document Fragment: null var el = null; // "Element: node" if (node.nodeType == 1) { el = node; // "Text, Comment: node's parentElement" } else if (dom.isCharacterDataNode(node)) { el = dom.parentElement(node); } // "If either element is null or element's ownerDocument is an HTML document // and element's local name is "html" and element's namespace is the HTML // namespace" if (el === null || ( el.nodeName == "HTML" && dom.isHtmlNamespace(dom.getDocument(el).documentElement) && dom.isHtmlNamespace(el) )) { // "let element be a new Element with "body" as its local name and the HTML // namespace as its namespace."" el = doc.createElement("body"); } else { el = el.cloneNode(false); } // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm." // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm." // "In either case, the algorithm must be invoked with fragment as the input // and element as the context element." el.innerHTML = fragmentStr; // "If this raises an exception, then abort these steps. Otherwise, let new // children be the nodes returned." // "Let fragment be a new DocumentFragment." // "Append all new children to fragment." // "Return fragment." return dom.fragmentFromNodeChildren(el); } : // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that // previous versions of Rangy used (with the exception of using a body element rather than a div) function(fragmentStr) { assertNotDetached(this); var doc = getRangeDocument(this); var el = doc.createElement("body"); el.innerHTML = fragmentStr; return dom.fragmentFromNodeChildren(el); }; /*----------------------------------------------------------------------------------------------------------------*/ var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", "commonAncestorContainer"]; var s2s = 0, s2e = 1, e2e = 2, e2s = 3; var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3; function RangePrototype() {} RangePrototype.prototype = { attachListener: function(type, listener) { this._listeners[type].push(listener); }, compareBoundaryPoints: function(how, range) { assertRangeValid(this); assertSameDocumentOrFragment(this.startContainer, range.startContainer); var nodeA, offsetA, nodeB, offsetB; var prefixA = (how == e2s || how == s2s) ? "start" : "end"; var prefixB = (how == s2e || how == s2s) ? "start" : "end"; nodeA = this[prefixA + "Container"]; offsetA = this[prefixA + "Offset"]; nodeB = range[prefixB + "Container"]; offsetB = range[prefixB + "Offset"]; return dom.comparePoints(nodeA, offsetA, nodeB, offsetB); }, insertNode: function(node) { assertRangeValid(this); assertValidNodeType(node, insertableNodeTypes); assertNodeNotReadOnly(this.startContainer); if (dom.isAncestorOf(node, this.startContainer, true)) { throw new DOMException("HIERARCHY_REQUEST_ERR"); } // No check for whether the container of the start of the Range is of a type that does not allow // children of the type of node: the browser's DOM implementation should do this for us when we attempt // to add the node var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset); this.setStartBefore(firstNodeInserted); }, cloneContents: function() { assertRangeValid(this); var clone, frag; if (this.collapsed) { return getRangeDocument(this).createDocumentFragment(); } else { if (this.startContainer === this.endContainer && dom.isCharacterDataNode(this.startContainer)) { clone = this.startContainer.cloneNode(true); clone.data = clone.data.slice(this.startOffset, this.endOffset); frag = getRangeDocument(this).createDocumentFragment(); frag.appendChild(clone); return frag; } else { var iterator = new RangeIterator(this, true); clone = cloneSubtree(iterator); iterator.detach(); } return clone; } }, canSurroundContents: function() { assertRangeValid(this); assertNodeNotReadOnly(this.startContainer); assertNodeNotReadOnly(this.endContainer); // Check if the contents can be surrounded. Specifically, this means whether the range partially selects // no non-text nodes. var iterator = new RangeIterator(this, true); var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) || (iterator._last && isNonTextPartiallySelected(iterator._last, this))); iterator.detach(); return !boundariesInvalid; }, surroundContents: function(node) { assertValidNodeType(node, surroundNodeTypes); if (!this.canSurroundContents()) { throw new RangeException("BAD_BOUNDARYPOINTS_ERR"); } // Extract the contents var content = this.extractContents(); // Clear the children of the node if (node.hasChildNodes()) { while (node.lastChild) { node.removeChild(node.lastChild); } } // Insert the new node and add the extracted contents insertNodeAtPosition(node, this.startContainer, this.startOffset); node.appendChild(content); this.selectNode(node); }, cloneRange: function() { assertRangeValid(this); var range = new Range(getRangeDocument(this)); var i = rangeProperties.length, prop; while (i--) { prop = rangeProperties[i]; range[prop] = this[prop]; } return range; }, toString: function() { assertRangeValid(this); var sc = this.startContainer; if (sc === this.endContainer && dom.isCharacterDataNode(sc)) { return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : ""; } else { var textBits = [], iterator = new RangeIterator(this, true); iterateSubtree(iterator, function(node) { // Accept only text or CDATA nodes, not comments if (node.nodeType == 3 || node.nodeType == 4) { textBits.push(node.data); } }); iterator.detach(); return textBits.join(""); } }, // The methods below are all non-standard. The following batch were introduced by Mozilla but have since // been removed from Mozilla. compareNode: function(node) { assertRangeValid(this); var parent = node.parentNode; var nodeIndex = dom.getNodeIndex(node); if (!parent) { throw new DOMException("NOT_FOUND_ERR"); } var startComparison = this.comparePoint(parent, nodeIndex), endComparison = this.comparePoint(parent, nodeIndex + 1); if (startComparison < 0) { // Node starts before return (endComparison > 0) ? n_b_a : n_b; } else { return (endComparison > 0) ? n_a : n_i; } }, comparePoint: function(node, offset) { assertRangeValid(this); assertNode(node, "HIERARCHY_REQUEST_ERR"); assertSameDocumentOrFragment(node, this.startContainer); if (dom.comparePoints(node, offset, this.startContainer, this.startOffset) < 0) { return -1; } else if (dom.comparePoints(node, offset, this.endContainer, this.endOffset) > 0) { return 1; } return 0; }, createContextualFragment: createContextualFragment, toHtml: function() { assertRangeValid(this); var container = getRangeDocument(this).createElement("div"); container.appendChild(this.cloneContents()); return container.innerHTML; }, // touchingIsIntersecting determines whether this method considers a node that borders a range intersects // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default) intersectsNode: function(node, touchingIsIntersecting) { assertRangeValid(this); assertNode(node, "NOT_FOUND_ERR"); if (dom.getDocument(node) !== getRangeDocument(this)) { return false; } var parent = node.parentNode, offset = dom.getNodeIndex(node); assertNode(parent, "NOT_FOUND_ERR"); var startComparison = dom.comparePoints(parent, offset, this.endContainer, this.endOffset), endComparison = dom.comparePoints(parent, offset + 1, this.startContainer, this.startOffset); return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; }, isPointInRange: function(node, offset) { assertRangeValid(this); assertNode(node, "HIERARCHY_REQUEST_ERR"); assertSameDocumentOrFragment(node, this.startContainer); return (dom.comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) && (dom.comparePoints(node, offset, this.endContainer, this.endOffset) <= 0); }, // The methods below are non-standard and invented by me. // Sharing a boundary start-to-end or end-to-start does not count as intersection. intersectsRange: function(range, touchingIsIntersecting) { assertRangeValid(this); if (getRangeDocument(range) != getRangeDocument(this)) { throw new DOMException("WRONG_DOCUMENT_ERR"); } var startComparison = dom.comparePoints(this.startContainer, this.startOffset, range.endContainer, range.endOffset), endComparison = dom.comparePoints(this.endContainer, this.endOffset, range.startContainer, range.startOffset); return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; }, intersection: function(range) { if (this.intersectsRange(range)) { var startComparison = dom.comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset), endComparison = dom.comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset); var intersectionRange = this.cloneRange(); if (startComparison == -1) { intersectionRange.setStart(range.startContainer, range.startOffset); } if (endComparison == 1) { intersectionRange.setEnd(range.endContainer, range.endOffset); } return intersectionRange; } return null; }, union: function(range) { if (this.intersectsRange(range, true)) { var unionRange = this.cloneRange(); if (dom.comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) { unionRange.setStart(range.startContainer, range.startOffset); } if (dom.comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) { unionRange.setEnd(range.endContainer, range.endOffset); } return unionRange; } else { throw new RangeException("Ranges do not intersect"); } }, containsNode: function(node, allowPartial) { if (allowPartial) { return this.intersectsNode(node, false); } else { return this.compareNode(node) == n_i; } }, containsNodeContents: function(node) { return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, dom.getNodeLength(node)) <= 0; }, containsRange: function(range) { return this.intersection(range).equals(range); }, containsNodeText: function(node) { var nodeRange = this.cloneRange(); nodeRange.selectNode(node); var textNodes = nodeRange.getNodes([3]); if (textNodes.length > 0) { nodeRange.setStart(textNodes[0], 0); var lastTextNode = textNodes.pop(); nodeRange.setEnd(lastTextNode, lastTextNode.length); var contains = this.containsRange(nodeRange); nodeRange.detach(); return contains; } else { return this.containsNodeContents(node); } }, createNodeIterator: function(nodeTypes, filter) { assertRangeValid(this); return new RangeNodeIterator(this, nodeTypes, filter); }, getNodes: function(nodeTypes, filter) { assertRangeValid(this); return getNodesInRange(this, nodeTypes, filter); }, getDocument: function() { return getRangeDocument(this); }, collapseBefore: function(node) { assertNotDetached(this); this.setEndBefore(node); this.collapse(false); }, collapseAfter: function(node) { assertNotDetached(this); this.setStartAfter(node); this.collapse(true); }, getName: function() { return "DomRange"; }, equals: function(range) { return Range.rangesEqual(this, range); }, isValid: function() { return isRangeValid(this); }, inspect: function() { return inspect(this); } }; function copyComparisonConstantsToObject(obj) { obj.START_TO_START = s2s; obj.START_TO_END = s2e; obj.END_TO_END = e2e; obj.END_TO_START = e2s; obj.NODE_BEFORE = n_b; obj.NODE_AFTER = n_a; obj.NODE_BEFORE_AND_AFTER = n_b_a; obj.NODE_INSIDE = n_i; } function copyComparisonConstants(constructor) { copyComparisonConstantsToObject(constructor); copyComparisonConstantsToObject(constructor.prototype); } function createRangeContentRemover(remover, boundaryUpdater) { return function() { assertRangeValid(this); var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer; var iterator = new RangeIterator(this, true); // Work out where to position the range after content removal var node, boundary; if (sc !== root) { node = dom.getClosestAncestorIn(sc, root, true); boundary = getBoundaryAfterNode(node); sc = boundary.node; so = boundary.offset; } // Check none of the range is read-only iterateSubtree(iterator, assertNodeNotReadOnly); iterator.reset(); // Remove the content var returnValue = remover(iterator); iterator.detach(); // Move to the new position boundaryUpdater(this, sc, so, sc, so); return returnValue; }; } function createPrototypeRange(constructor, boundaryUpdater, detacher) { function createBeforeAfterNodeSetter(isBefore, isStart) { return function(node) { assertNotDetached(this); assertValidNodeType(node, beforeAfterNodeTypes); assertValidNodeType(getRootContainer(node), rootContainerNodeTypes); var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node); (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset); }; } function setRangeStart(range, node, offset) { var ec = range.endContainer, eo = range.endOffset; if (node !== range.startContainer || offset !== range.startOffset) { // Check the root containers of the range and the new boundary, and also check whether the new boundary // is after the current end. In either case, collapse the range to the new position if (getRootContainer(node) != getRootContainer(ec) || dom.comparePoints(node, offset, ec, eo) == 1) { ec = node; eo = offset; } boundaryUpdater(range, node, offset, ec, eo); } } function setRangeEnd(range, node, offset) { var sc = range.startContainer, so = range.startOffset; if (node !== range.endContainer || offset !== range.endOffset) { // Check the root containers of the range and the new boundary, and also check whether the new boundary // is after the current end. In either case, collapse the range to the new position if (getRootContainer(node) != getRootContainer(sc) || dom.comparePoints(node, offset, sc, so) == -1) { sc = node; so = offset; } boundaryUpdater(range, sc, so, node, offset); } } function setRangeStartAndEnd(range, node, offset) { if (node !== range.startContainer || offset !== range.startOffset || node !== range.endContainer || offset !== range.endOffset) { boundaryUpdater(range, node, offset, node, offset); } } constructor.prototype = new RangePrototype(); api.util.extend(constructor.prototype, { setStart: function(node, offset) { assertNotDetached(this); assertNoDocTypeNotationEntityAncestor(node, true); assertValidOffset(node, offset); setRangeStart(this, node, offset); }, setEnd: function(node, offset) { assertNotDetached(this); assertNoDocTypeNotationEntityAncestor(node, true); assertValidOffset(node, offset); setRangeEnd(this, node, offset); }, setStartBefore: createBeforeAfterNodeSetter(true, true), setStartAfter: createBeforeAfterNodeSetter(false, true), setEndBefore: createBeforeAfterNodeSetter(true, false), setEndAfter: createBeforeAfterNodeSetter(false, false), collapse: function(isStart) { assertRangeValid(this); if (isStart) { boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset); } else { boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset); } }, selectNodeContents: function(node) { // This doesn't seem well specified: the spec talks only about selecting the node's contents, which // could be taken to mean only its children. However, browsers implement this the same as selectNode for // text nodes, so I shall do likewise assertNotDetached(this); assertNoDocTypeNotationEntityAncestor(node, true); boundaryUpdater(this, node, 0, node, dom.getNodeLength(node)); }, selectNode: function(node) { assertNotDetached(this); assertNoDocTypeNotationEntityAncestor(node, false); assertValidNodeType(node, beforeAfterNodeTypes); var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node); boundaryUpdater(this, start.node, start.offset, end.node, end.offset); }, extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater), deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater), canSurroundContents: function() { assertRangeValid(this); assertNodeNotReadOnly(this.startContainer); assertNodeNotReadOnly(this.endContainer); // Check if the contents can be surrounded. Specifically, this means whether the range partially selects // no non-text nodes. var iterator = new RangeIterator(this, true); var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) || (iterator._last && isNonTextPartiallySelected(iterator._last, this))); iterator.detach(); return !boundariesInvalid; }, detach: function() { detacher(this); }, splitBoundaries: function() { assertRangeValid(this); var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset; var startEndSame = (sc === ec); if (dom.isCharacterDataNode(ec) && eo > 0 && eo < ec.length) { dom.splitDataNode(ec, eo); } if (dom.isCharacterDataNode(sc) && so > 0 && so < sc.length) { sc = dom.splitDataNode(sc, so); if (startEndSame) { eo -= so; ec = sc; } else if (ec == sc.parentNode && eo >= dom.getNodeIndex(sc)) { eo++; } so = 0; } boundaryUpdater(this, sc, so, ec, eo); }, normalizeBoundaries: function() { assertRangeValid(this); var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset; var mergeForward = function(node) { var sibling = node.nextSibling; if (sibling && sibling.nodeType == node.nodeType) { ec = node; eo = node.length; node.appendData(sibling.data); sibling.parentNode.removeChild(sibling); } }; var mergeBackward = function(node) { var sibling = node.previousSibling; if (sibling && sibling.nodeType == node.nodeType) { sc = node; var nodeLength = node.length; so = sibling.length; node.insertData(0, sibling.data); sibling.parentNode.removeChild(sibling); if (sc == ec) { eo += so; ec = sc; } else if (ec == node.parentNode) { var nodeIndex = dom.getNodeIndex(node); if (eo == nodeIndex) { ec = node; eo = nodeLength; } else if (eo > nodeIndex) { eo--; } } } }; var normalizeStart = true; if (dom.isCharacterDataNode(ec)) { if (ec.length == eo) { mergeForward(ec); } } else { if (eo > 0) { var endNode = ec.childNodes[eo - 1]; if (endNode && dom.isCharacterDataNode(endNode)) { mergeForward(endNode); } } normalizeStart = !this.collapsed; } if (normalizeStart) { if (dom.isCharacterDataNode(sc)) { if (so == 0) { mergeBackward(sc); } } else { if (so < sc.childNodes.length) { var startNode = sc.childNodes[so]; if (startNode && dom.isCharacterDataNode(startNode)) { mergeBackward(startNode); } } } } else { sc = ec; so = eo; } boundaryUpdater(this, sc, so, ec, eo); }, collapseToPoint: function(node, offset) { assertNotDetached(this); assertNoDocTypeNotationEntityAncestor(node, true); assertValidOffset(node, offset); setRangeStartAndEnd(this, node, offset); } }); copyComparisonConstants(constructor); } /*----------------------------------------------------------------------------------------------------------------*/ // Updates commonAncestorContainer and collapsed after boundary change function updateCollapsedAndCommonAncestor(range) { range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset); range.commonAncestorContainer = range.collapsed ? range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer); } function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) { var startMoved = (range.startContainer !== startContainer || range.startOffset !== startOffset); var endMoved = (range.endContainer !== endContainer || range.endOffset !== endOffset); range.startContainer = startContainer; range.startOffset = startOffset; range.endContainer = endContainer; range.endOffset = endOffset; updateCollapsedAndCommonAncestor(range); dispatchEvent(range, "boundarychange", {startMoved: startMoved, endMoved: endMoved}); } function detach(range) { assertNotDetached(range); range.startContainer = range.startOffset = range.endContainer = range.endOffset = null; range.collapsed = range.commonAncestorContainer = null; dispatchEvent(range, "detach", null); range._listeners = null; } /** * @constructor */ function Range(doc) { this.startContainer = doc; this.startOffset = 0; this.endContainer = doc; this.endOffset = 0; this._listeners = { boundarychange: [], detach: [] }; updateCollapsedAndCommonAncestor(this); } createPrototypeRange(Range, updateBoundaries, detach); api.rangePrototype = RangePrototype.prototype; Range.rangeProperties = rangeProperties; Range.RangeIterator = RangeIterator; Range.copyComparisonConstants = copyComparisonConstants; Range.createPrototypeRange = createPrototypeRange; Range.inspect = inspect; Range.getRangeDocument = getRangeDocument; Range.rangesEqual = function(r1, r2) { return r1.startContainer === r2.startContainer && r1.startOffset === r2.startOffset && r1.endContainer === r2.endContainer && r1.endOffset === r2.endOffset; }; api.DomRange = Range; api.RangeException = RangeException; });rangy.createModule("WrappedRange", function(api, module) { api.requireModules( ["DomUtil", "DomRange"] ); /** * @constructor */ var WrappedRange; var dom = api.dom; var DomPosition = dom.DomPosition; var DomRange = api.DomRange; /*----------------------------------------------------------------------------------------------------------------*/ /* This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement() method. For example, in the following (where pipes denote the selection boundaries): var range = document.selection.createRange(); alert(range.parentElement().id); // Should alert "ul" but alerts "b" This method returns the common ancestor node of the following: - the parentElement() of the textRange - the parentElement() of the textRange after calling collapse(true) - the parentElement() of the textRange after calling collapse(false) */ function getTextRangeContainerElement(textRange) { var parentEl = textRange.parentElement(); var range = textRange.duplicate(); range.collapse(true); var startEl = range.parentElement(); range = textRange.duplicate(); range.collapse(false); var endEl = range.parentElement(); var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl); return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer); } function textRangeIsCollapsed(textRange) { return textRange.compareEndPoints("StartToEnd", textRange) == 0; } // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started out as // an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/) but has // grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange bugs, handling // for inputs and images, plus optimizations. function getTextRangeBoundaryPosition(textRange, wholeRangeContainerElement, isStart, isCollapsed) { var workingRange = textRange.duplicate(); workingRange.collapse(isStart); var containerElement = workingRange.parentElement(); // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so // check for that // TODO: Find out when. Workaround for wholeRangeContainerElement may break this if (!dom.isAncestorOf(wholeRangeContainerElement, containerElement, true)) { containerElement = wholeRangeContainerElement; } // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx if (!containerElement.canHaveHTML) { return new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement)); } var workingNode = dom.getDocument(containerElement).createElement("span"); var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd"; var previousNode, nextNode, boundaryPosition, boundaryNode; // Move the working range through the container's children, starting at the end and working backwards, until the // working range reaches or goes past the boundary we're interested in do { containerElement.insertBefore(workingNode, workingNode.previousSibling); workingRange.moveToElementText(workingNode); } while ( (comparison = workingRange.compareEndPoints(workingComparisonType, textRange)) > 0 && workingNode.previousSibling); // We've now reached or gone past the boundary of the text range we're interested in // so have identified the node we want boundaryNode = workingNode.nextSibling; if (comparison == -1 && boundaryNode && dom.isCharacterDataNode(boundaryNode)) { // This is a character data node (text, comment, cdata). The working range is collapsed at the start of the // node containing the text range's boundary, so we move the end of the working range to the boundary point // and measure the length of its text to get the boundary's offset within the node. workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange); var offset; if (/[\r\n]/.test(boundaryNode.data)) { /* For the particular case of a boundary within a text node containing line breaks (within a
 element,
                for example), we need a slightly complicated approach to get the boundary's offset in IE. The facts:

                - Each line break is represented as \r in the text node's data/nodeValue properties
                - Each line break is represented as \r\n in the TextRange's 'text' property
                - The 'text' property of the TextRange does not contain trailing line breaks

                To get round the problem presented by the final fact above, we can use the fact that TextRange's
                moveStart() and moveEnd() methods return the actual number of characters moved, which is not necessarily
                the same as the number of characters it was instructed to move. The simplest approach is to use this to
                store the characters moved when moving both the start and end of the range to the start of the document
                body and subtracting the start offset from the end offset (the "move-negative-gazillion" method).
                However, this is extremely slow when the document is large and the range is near the end of it. Clearly
                doing the mirror image (i.e. moving the range boundaries to the end of the document) has the same
                problem.

                Another approach that works is to use moveStart() to move the start boundary of the range up to the end
                boundary one character at a time and incrementing a counter with the value returned by the moveStart()
                call. However, the check for whether the start boundary has reached the end boundary is expensive, so
                this method is slow (although unlike "move-negative-gazillion" is largely unaffected by the location of
                the range within the document).

                The method below is a hybrid of the two methods above. It uses the fact that a string containing the
                TextRange's 'text' property with each \r\n converted to a single \r character cannot be longer than the
                text of the TextRange, so the start of the range is moved that length initially and then a character at
                a time to make up for any trailing line breaks not contained in the 'text' property. This has good
                performance in most situations compared to the previous two methods.
                */
                var tempRange = workingRange.duplicate();
                var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;

                offset = tempRange.moveStart("character", rangeLength);
                while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) {
                    offset++;
                    tempRange.moveStart("character", 1);
                }
            } else {
                offset = workingRange.text.length;
            }
            boundaryPosition = new DomPosition(boundaryNode, offset);
        } else {


            // If the boundary immediately follows a character data node and this is the end boundary, we should favour
            // a position within that, and likewise for a start boundary preceding a character data node
            previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
            nextNode = (isCollapsed || isStart) && workingNode.nextSibling;



            if (nextNode && dom.isCharacterDataNode(nextNode)) {
                boundaryPosition = new DomPosition(nextNode, 0);
            } else if (previousNode && dom.isCharacterDataNode(previousNode)) {
                boundaryPosition = new DomPosition(previousNode, previousNode.length);
            } else {
                boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
            }
        }

        // Clean up
        workingNode.parentNode.removeChild(workingNode);

        return boundaryPosition;
    }

    // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that node.
    // This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
    // (http://code.google.com/p/ierange/)
    function createBoundaryTextRange(boundaryPosition, isStart) {
        var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
        var doc = dom.getDocument(boundaryPosition.node);
        var workingNode, childNodes, workingRange = doc.body.createTextRange();
        var nodeIsDataNode = dom.isCharacterDataNode(boundaryPosition.node);

        if (nodeIsDataNode) {
            boundaryNode = boundaryPosition.node;
            boundaryParent = boundaryNode.parentNode;
        } else {
            childNodes = boundaryPosition.node.childNodes;
            boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;
            boundaryParent = boundaryPosition.node;
        }

        // Position the range immediately before the node containing the boundary
        workingNode = doc.createElement("span");

        // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within the
        // element rather than immediately before or after it, which is what we want
        workingNode.innerHTML = "&#feff;";

        // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
        // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
        if (boundaryNode) {
            boundaryParent.insertBefore(workingNode, boundaryNode);
        } else {
            boundaryParent.appendChild(workingNode);
        }

        workingRange.moveToElementText(workingNode);
        workingRange.collapse(!isStart);

        // Clean up
        boundaryParent.removeChild(workingNode);

        // Move the working range to the text offset, if required
        if (nodeIsDataNode) {
            workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);
        }

        return workingRange;
    }

    /*----------------------------------------------------------------------------------------------------------------*/

    if (api.features.implementsDomRange && (!api.features.implementsTextRange || !api.config.preferTextRange)) {
        // This is a wrapper around the browser's native DOM Range. It has two aims:
        // - Provide workarounds for specific browser bugs
        // - provide convenient extensions, which are inherited from Rangy's DomRange

        (function() {
            var rangeProto;
            var rangeProperties = DomRange.rangeProperties;
            var canSetRangeStartAfterEnd;

            function updateRangeProperties(range) {
                var i = rangeProperties.length, prop;
                while (i--) {
                    prop = rangeProperties[i];
                    range[prop] = range.nativeRange[prop];
                }
            }

            function updateNativeRange(range, startContainer, startOffset, endContainer,endOffset) {
                var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset);
                var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset);

                // Always set both boundaries for the benefit of IE9 (see issue 35)
                if (startMoved || endMoved) {
                    range.setEnd(endContainer, endOffset);
                    range.setStart(startContainer, startOffset);
                }
            }

            function detach(range) {
                range.nativeRange.detach();
                range.detached = true;
                var i = rangeProperties.length, prop;
                while (i--) {
                    prop = rangeProperties[i];
                    range[prop] = null;
                }
            }

            var createBeforeAfterNodeSetter;

            WrappedRange = function(range) {
                if (!range) {
                    throw new Error("Range must be specified");
                }
                this.nativeRange = range;
                updateRangeProperties(this);
            };

            DomRange.createPrototypeRange(WrappedRange, updateNativeRange, detach);

            rangeProto = WrappedRange.prototype;

            rangeProto.selectNode = function(node) {
                this.nativeRange.selectNode(node);
                updateRangeProperties(this);
            };

            rangeProto.deleteContents = function() {
                this.nativeRange.deleteContents();
                updateRangeProperties(this);
            };

            rangeProto.extractContents = function() {
                var frag = this.nativeRange.extractContents();
                updateRangeProperties(this);
                return frag;
            };

            rangeProto.cloneContents = function() {
                return this.nativeRange.cloneContents();
            };

            // TODO: Until I can find a way to programmatically trigger the Firefox bug (apparently long-standing, still
            // present in 3.6.8) that throws "Index or size is negative or greater than the allowed amount" for
            // insertNode in some circumstances, all browsers will have to use the Rangy's own implementation of
            // insertNode, which works but is almost certainly slower than the native implementation.
/*
            rangeProto.insertNode = function(node) {
                this.nativeRange.insertNode(node);
                updateRangeProperties(this);
            };
*/

            rangeProto.surroundContents = function(node) {
                this.nativeRange.surroundContents(node);
                updateRangeProperties(this);
            };

            rangeProto.collapse = function(isStart) {
                this.nativeRange.collapse(isStart);
                updateRangeProperties(this);
            };

            rangeProto.cloneRange = function() {
                return new WrappedRange(this.nativeRange.cloneRange());
            };

            rangeProto.refresh = function() {
                updateRangeProperties(this);
            };

            rangeProto.toString = function() {
                return this.nativeRange.toString();
            };

            // Create test range and node for feature detection

            var testTextNode = document.createTextNode("test");
            dom.getBody(document).appendChild(testTextNode);
            var range = document.createRange();

            /*--------------------------------------------------------------------------------------------------------*/

            // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and
            // correct for it

            range.setStart(testTextNode, 0);
            range.setEnd(testTextNode, 0);

            try {
                range.setStart(testTextNode, 1);
                canSetRangeStartAfterEnd = true;

                rangeProto.setStart = function(node, offset) {
                    this.nativeRange.setStart(node, offset);
                    updateRangeProperties(this);
                };

                rangeProto.setEnd = function(node, offset) {
                    this.nativeRange.setEnd(node, offset);
                    updateRangeProperties(this);
                };

                createBeforeAfterNodeSetter = function(name) {
                    return function(node) {
                        this.nativeRange[name](node);
                        updateRangeProperties(this);
                    };
                };

            } catch(ex) {


                canSetRangeStartAfterEnd = false;

                rangeProto.setStart = function(node, offset) {
                    try {
                        this.nativeRange.setStart(node, offset);
                    } catch (ex) {
                        this.nativeRange.setEnd(node, offset);
                        this.nativeRange.setStart(node, offset);
                    }
                    updateRangeProperties(this);
                };

                rangeProto.setEnd = function(node, offset) {
                    try {
                        this.nativeRange.setEnd(node, offset);
                    } catch (ex) {
                        this.nativeRange.setStart(node, offset);
                        this.nativeRange.setEnd(node, offset);
                    }
                    updateRangeProperties(this);
                };

                createBeforeAfterNodeSetter = function(name, oppositeName) {
                    return function(node) {
                        try {
                            this.nativeRange[name](node);
                        } catch (ex) {
                            this.nativeRange[oppositeName](node);
                            this.nativeRange[name](node);
                        }
                        updateRangeProperties(this);
                    };
                };
            }

            rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore");
            rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter");
            rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore");
            rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter");

            /*--------------------------------------------------------------------------------------------------------*/

            // Test for and correct Firefox 2 behaviour with selectNodeContents on text nodes: it collapses the range to
            // the 0th character of the text node
            range.selectNodeContents(testTextNode);
            if (range.startContainer == testTextNode && range.endContainer == testTextNode &&
                    range.startOffset == 0 && range.endOffset == testTextNode.length) {
                rangeProto.selectNodeContents = function(node) {
                    this.nativeRange.selectNodeContents(node);
                    updateRangeProperties(this);
                };
            } else {
                rangeProto.selectNodeContents = function(node) {
                    this.setStart(node, 0);
                    this.setEnd(node, DomRange.getEndOffset(node));
                };
            }

            /*--------------------------------------------------------------------------------------------------------*/

            // Test for WebKit bug that has the beahviour of compareBoundaryPoints round the wrong way for constants
            // START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738

            range.selectNodeContents(testTextNode);
            range.setEnd(testTextNode, 3);

            var range2 = document.createRange();
            range2.selectNodeContents(testTextNode);
            range2.setEnd(testTextNode, 4);
            range2.setStart(testTextNode, 2);

            if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 &
                    range.compareBoundaryPoints(range.END_TO_START, range2) == 1) {
                // This is the wrong way round, so correct for it


                rangeProto.compareBoundaryPoints = function(type, range) {
                    range = range.nativeRange || range;
                    if (type == range.START_TO_END) {
                        type = range.END_TO_START;
                    } else if (type == range.END_TO_START) {
                        type = range.START_TO_END;
                    }
                    return this.nativeRange.compareBoundaryPoints(type, range);
                };
            } else {
                rangeProto.compareBoundaryPoints = function(type, range) {
                    return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range);
                };
            }

            /*--------------------------------------------------------------------------------------------------------*/

            // Test for existence of createContextualFragment and delegate to it if it exists
            if (api.util.isHostMethod(range, "createContextualFragment")) {
                rangeProto.createContextualFragment = function(fragmentStr) {
                    return this.nativeRange.createContextualFragment(fragmentStr);
                };
            }

            /*--------------------------------------------------------------------------------------------------------*/

            // Clean up
            dom.getBody(document).removeChild(testTextNode);
            range.detach();
            range2.detach();
        })();

        api.createNativeRange = function(doc) {
            doc = doc || document;
            return doc.createRange();
        };
    } else if (api.features.implementsTextRange) {
        // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
        // prototype

        WrappedRange = function(textRange) {
            this.textRange = textRange;
            this.refresh();
        };

        WrappedRange.prototype = new DomRange(document);

        WrappedRange.prototype.refresh = function() {
            var start, end;

            // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.
            var rangeContainerElement = getTextRangeContainerElement(this.textRange);

            if (textRangeIsCollapsed(this.textRange)) {
                end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, true);
            } else {

                start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);
                end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false);
            }

            this.setStart(start.node, start.offset);
            this.setEnd(end.node, end.offset);
        };

        DomRange.copyComparisonConstants(WrappedRange);

        // Add WrappedRange as the Range property of the global object to allow expression like Range.END_TO_END to work
        var globalObj = (function() { return this; })();
        if (typeof globalObj.Range == "undefined") {
            globalObj.Range = WrappedRange;
        }

        api.createNativeRange = function(doc) {
            doc = doc || document;
            return doc.body.createTextRange();
        };
    }

    if (api.features.implementsTextRange) {
        WrappedRange.rangeToTextRange = function(range) {
            if (range.collapsed) {
                var tr = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);



                return tr;

                //return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
            } else {
                var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
                var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);
                var textRange = dom.getDocument(range.startContainer).body.createTextRange();
                textRange.setEndPoint("StartToStart", startRange);
                textRange.setEndPoint("EndToEnd", endRange);
                return textRange;
            }
        };
    }

    WrappedRange.prototype.getName = function() {
        return "WrappedRange";
    };

    api.WrappedRange = WrappedRange;

    api.createRange = function(doc) {
        doc = doc || document;
        return new WrappedRange(api.createNativeRange(doc));
    };

    api.createRangyRange = function(doc) {
        doc = doc || document;
        return new DomRange(doc);
    };

    api.createIframeRange = function(iframeEl) {
        return api.createRange(dom.getIframeDocument(iframeEl));
    };

    api.createIframeRangyRange = function(iframeEl) {
        return api.createRangyRange(dom.getIframeDocument(iframeEl));
    };

    api.addCreateMissingNativeApiListener(function(win) {
        var doc = win.document;
        if (typeof doc.createRange == "undefined") {
            doc.createRange = function() {
                return api.createRange(this);
            };
        }
        doc = win = null;
    });
});rangy.createModule("WrappedSelection", function(api, module) {
    // This will create a selection object wrapper that follows the Selection object found in the WHATWG draft DOM Range
    // spec (http://html5.org/specs/dom-range.html)

    api.requireModules( ["DomUtil", "DomRange", "WrappedRange"] );

    api.config.checkSelectionRanges = true;

    var BOOLEAN = "boolean",
        windowPropertyName = "_rangySelection",
        dom = api.dom,
        util = api.util,
        DomRange = api.DomRange,
        WrappedRange = api.WrappedRange,
        DOMException = api.DOMException,
        DomPosition = dom.DomPosition,
        getSelection,
        selectionIsCollapsed,
        CONTROL = "Control";



    function getWinSelection(winParam) {
        return (winParam || window).getSelection();
    }

    function getDocSelection(winParam) {
        return (winParam || window).document.selection;
    }

    // Test for the Range/TextRange and Selection features required
    // Test for ability to retrieve selection
    var implementsWinGetSelection = api.util.isHostMethod(window, "getSelection"),
        implementsDocSelection = api.util.isHostObject(document, "selection");

    var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);

    if (useDocumentSelection) {
        getSelection = getDocSelection;
        api.isSelectionValid = function(winParam) {
            var doc = (winParam || window).document, nativeSel = doc.selection;

            // Check whether the selection TextRange is actually contained within the correct document
            return (nativeSel.type != "None" || dom.getDocument(nativeSel.createRange().parentElement()) == doc);
        };
    } else if (implementsWinGetSelection) {
        getSelection = getWinSelection;
        api.isSelectionValid = function() {
            return true;
        };
    } else {
        module.fail("Neither document.selection or window.getSelection() detected.");
    }

    api.getNativeSelection = getSelection;

    var testSelection = getSelection();
    var testRange = api.createNativeRange(document);
    var body = dom.getBody(document);

    // Obtaining a range from a selection
    var selectionHasAnchorAndFocus = util.areHostObjects(testSelection, ["anchorNode", "focusNode"] &&
                                     util.areHostProperties(testSelection, ["anchorOffset", "focusOffset"]));
    api.features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;

    // Test for existence of native selection extend() method
    var selectionHasExtend = util.isHostMethod(testSelection, "extend");
    api.features.selectionHasExtend = selectionHasExtend;

    // Test if rangeCount exists
    var selectionHasRangeCount = (typeof testSelection.rangeCount == "number");
    api.features.selectionHasRangeCount = selectionHasRangeCount;

    var selectionSupportsMultipleRanges = false;
    var collapsedNonEditableSelectionsSupported = true;

    if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) &&
            typeof testSelection.rangeCount == "number" && api.features.implementsDomRange) {

        (function() {
            var iframe = document.createElement("iframe");
            iframe.frameBorder = 0;
            iframe.style.position = "absolute";
            iframe.style.left = "-10000px";
            body.appendChild(iframe);

            var iframeDoc = dom.getIframeDocument(iframe);
            iframeDoc.open();
            iframeDoc.write("12");
            iframeDoc.close();

            var sel = dom.getIframeWindow(iframe).getSelection();
            var docEl = iframeDoc.documentElement;
            var iframeBody = docEl.lastChild, textNode = iframeBody.firstChild;

            // Test whether the native selection will allow a collapsed selection within a non-editable element
            var r1 = iframeDoc.createRange();
            r1.setStart(textNode, 1);
            r1.collapse(true);
            sel.addRange(r1);
            collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);
            sel.removeAllRanges();

            // Test whether the native selection is capable of supporting multiple ranges
            var r2 = r1.cloneRange();
            r1.setStart(textNode, 0);
            r2.setEnd(textNode, 2);
            sel.addRange(r1);
            sel.addRange(r2);

            selectionSupportsMultipleRanges = (sel.rangeCount == 2);

            // Clean up
            r1.detach();
            r2.detach();

            body.removeChild(iframe);
        })();
    }

    api.features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;
    api.features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;

    // ControlRanges
    var implementsControlRange = false, testControlRange;

    if (body && util.isHostMethod(body, "createControlRange")) {
        testControlRange = body.createControlRange();
        if (util.areHostProperties(testControlRange, ["item", "add"])) {
            implementsControlRange = true;
        }
    }
    api.features.implementsControlRange = implementsControlRange;

    // Selection collapsedness
    if (selectionHasAnchorAndFocus) {
        selectionIsCollapsed = function(sel) {
            return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
        };
    } else {
        selectionIsCollapsed = function(sel) {
            return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;
        };
    }

    function updateAnchorAndFocusFromRange(sel, range, backwards) {
        var anchorPrefix = backwards ? "end" : "start", focusPrefix = backwards ? "start" : "end";
        sel.anchorNode = range[anchorPrefix + "Container"];
        sel.anchorOffset = range[anchorPrefix + "Offset"];
        sel.focusNode = range[focusPrefix + "Container"];
        sel.focusOffset = range[focusPrefix + "Offset"];
    }

    function updateAnchorAndFocusFromNativeSelection(sel) {
        var nativeSel = sel.nativeSelection;
        sel.anchorNode = nativeSel.anchorNode;
        sel.anchorOffset = nativeSel.anchorOffset;
        sel.focusNode = nativeSel.focusNode;
        sel.focusOffset = nativeSel.focusOffset;
    }

    function updateEmptySelection(sel) {
        sel.anchorNode = sel.focusNode = null;
        sel.anchorOffset = sel.focusOffset = 0;
        sel.rangeCount = 0;
        sel.isCollapsed = true;
        sel._ranges.length = 0;
    }

    function getNativeRange(range) {
        var nativeRange;
        if (range instanceof DomRange) {
            nativeRange = range._selectionNativeRange;
            if (!nativeRange) {
                nativeRange = api.createNativeRange(dom.getDocument(range.startContainer));
                nativeRange.setEnd(range.endContainer, range.endOffset);
                nativeRange.setStart(range.startContainer, range.startOffset);
                range._selectionNativeRange = nativeRange;
                range.attachListener("detach", function() {

                    this._selectionNativeRange = null;
                });
            }
        } else if (range instanceof WrappedRange) {
            nativeRange = range.nativeRange;
        } else if (api.features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {
            nativeRange = range;
        }
        return nativeRange;
    }

    function rangeContainsSingleElement(rangeNodes) {
        if (!rangeNodes.length || rangeNodes[0].nodeType != 1) {
            return false;
        }
        for (var i = 1, len = rangeNodes.length; i < len; ++i) {
            if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {
                return false;
            }
        }
        return true;
    }

    function getSingleElementFromRange(range) {
        var nodes = range.getNodes();
        if (!rangeContainsSingleElement(nodes)) {
            throw new Error("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");
        }
        return nodes[0];
    }

    function isTextRange(range) {
        return !!range && typeof range.text != "undefined";
    }

    function updateFromTextRange(sel, range) {
        // Create a Range from the selected TextRange
        var wrappedRange = new WrappedRange(range);
        sel._ranges = [wrappedRange];

        updateAnchorAndFocusFromRange(sel, wrappedRange, false);
        sel.rangeCount = 1;
        sel.isCollapsed = wrappedRange.collapsed;
    }

    function updateControlSelection(sel) {
        // Update the wrapped selection based on what's now in the native selection
        sel._ranges.length = 0;
        if (sel.docSelection.type == "None") {
            updateEmptySelection(sel);
        } else {
            var controlRange = sel.docSelection.createRange();
            if (isTextRange(controlRange)) {
                // This case (where the selection type is "Control" and calling createRange() on the selection returns
                // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
                // ControlRange have been removed from the ControlRange and removed from the document.
                updateFromTextRange(sel, controlRange);
            } else {
                sel.rangeCount = controlRange.length;
                var range, doc = dom.getDocument(controlRange.item(0));
                for (var i = 0; i < sel.rangeCount; ++i) {
                    range = api.createRange(doc);
                    range.selectNode(controlRange.item(i));
                    sel._ranges.push(range);
                }
                sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed;
                updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);
            }
        }
    }

    function addRangeToControlSelection(sel, range) {
        var controlRange = sel.docSelection.createRange();
        var rangeElement = getSingleElementFromRange(range);

        // Create a new ControlRange containing all the elements in the selected ControlRange plus the element
        // contained by the supplied range
        var doc = dom.getDocument(controlRange.item(0));
        var newControlRange = dom.getBody(doc).createControlRange();
        for (var i = 0, len = controlRange.length; i < len; ++i) {
            newControlRange.add(controlRange.item(i));
        }
        try {
            newControlRange.add(rangeElement);
        } catch (ex) {
            throw new Error("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");
        }
        newControlRange.select();

        // Update the wrapped selection based on what's now in the native selection
        updateControlSelection(sel);
    }

    var getSelectionRangeAt;

    if (util.isHostMethod(testSelection,  "getRangeAt")) {
        getSelectionRangeAt = function(sel, index) {
            try {
                return sel.getRangeAt(index);
            } catch(ex) {
                return null;
            }
        };
    } else if (selectionHasAnchorAndFocus) {
        getSelectionRangeAt = function(sel) {
            var doc = dom.getDocument(sel.anchorNode);
            var range = api.createRange(doc);
            range.setStart(sel.anchorNode, sel.anchorOffset);
            range.setEnd(sel.focusNode, sel.focusOffset);

            // Handle the case when the selection was selected backwards (from the end to the start in the
            // document)
            if (range.collapsed !== this.isCollapsed) {
                range.setStart(sel.focusNode, sel.focusOffset);
                range.setEnd(sel.anchorNode, sel.anchorOffset);
            }

            return range;
        };
    }

    /**
     * @constructor
     */
    function WrappedSelection(selection, docSelection, win) {
        this.nativeSelection = selection;
        this.docSelection = docSelection;
        this._ranges = [];
        this.win = win;
        this.refresh();
    }

    api.getSelection = function(win) {
        win = win || window;
        var sel = win[windowPropertyName];
        var nativeSel = getSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;
        if (sel) {
            sel.nativeSelection = nativeSel;
            sel.docSelection = docSel;
            sel.refresh(win);
        } else {
            sel = new WrappedSelection(nativeSel, docSel, win);
            win[windowPropertyName] = sel;
        }
        return sel;
    };

    api.getIframeSelection = function(iframeEl) {
        return api.getSelection(dom.getIframeWindow(iframeEl));
    };

    var selProto = WrappedSelection.prototype;

    function createControlSelection(sel, ranges) {
        // Ensure that the selection becomes of type "Control"
        var doc = dom.getDocument(ranges[0].startContainer);
        var controlRange = dom.getBody(doc).createControlRange();
        for (var i = 0, el; i < rangeCount; ++i) {
            el = getSingleElementFromRange(ranges[i]);
            try {
                controlRange.add(el);
            } catch (ex) {
                throw new Error("setRanges(): Element within the one of the specified Ranges could not be added to control selection (does it have layout?)");
            }
        }
        controlRange.select();

        // Update the wrapped selection based on what's now in the native selection
        updateControlSelection(sel);
    }

    // Selecting a range
    if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) {
        selProto.removeAllRanges = function() {
            this.nativeSelection.removeAllRanges();
            updateEmptySelection(this);
        };

        var addRangeBackwards = function(sel, range) {
            var doc = DomRange.getRangeDocument(range);
            var endRange = api.createRange(doc);
            endRange.collapseToPoint(range.endContainer, range.endOffset);
            sel.nativeSelection.addRange(getNativeRange(endRange));
            sel.nativeSelection.extend(range.startContainer, range.startOffset);
            sel.refresh();
        };

        if (selectionHasRangeCount) {
            selProto.addRange = function(range, backwards) {
                if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
                    addRangeToControlSelection(this, range);
                } else {
                    if (backwards && selectionHasExtend) {
                        addRangeBackwards(this, range);
                    } else {
                        var previousRangeCount;
                        if (selectionSupportsMultipleRanges) {
                            previousRangeCount = this.rangeCount;
                        } else {
                            this.removeAllRanges();
                            previousRangeCount = 0;
                        }
                        this.nativeSelection.addRange(getNativeRange(range));

                        // Check whether adding the range was successful
                        this.rangeCount = this.nativeSelection.rangeCount;

                        if (this.rangeCount == previousRangeCount + 1) {
                            // The range was added successfully

                            // Check whether the range that we added to the selection is reflected in the last range extracted from
                            // the selection
                            if (api.config.checkSelectionRanges) {
                                var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1);
                                if (nativeRange && !DomRange.rangesEqual(nativeRange, range)) {
                                    // Happens in WebKit with, for example, a selection placed at the start of a text node
                                    range = new WrappedRange(nativeRange);
                                }
                            }
                            this._ranges[this.rangeCount - 1] = range;
                            updateAnchorAndFocusFromRange(this, range, selectionIsBackwards(this.nativeSelection));
                            this.isCollapsed = selectionIsCollapsed(this);
                        } else {
                            // The range was not added successfully. The simplest thing is to refresh
                            this.refresh();
                        }
                    }
                }
            };
        } else {
            selProto.addRange = function(range, backwards) {
                if (backwards && selectionHasExtend) {
                    addRangeBackwards(this, range);
                } else {
                    this.nativeSelection.addRange(getNativeRange(range));
                    this.refresh();
                }
            };
        }

        selProto.setRanges = function(ranges) {
            if (implementsControlRange && ranges.length > 1) {
                createControlSelection(this, ranges);
            } else {
                this.removeAllRanges();
                for (var i = 0, len = ranges.length; i < len; ++i) {
                    this.addRange(ranges[i]);
                }
            }
        };
    } else if (util.isHostMethod(testSelection, "empty") && util.isHostMethod(testRange, "select") &&
               implementsControlRange && useDocumentSelection) {

        selProto.removeAllRanges = function() {
            // Added try/catch as fix for issue #21
            try {
                this.docSelection.empty();

                // Check for empty() not working (issue #24)
                if (this.docSelection.type != "None") {
                    // Work around failure to empty a control selection by instead selecting a TextRange and then
                    // calling empty()
                    var doc;
                    if (this.anchorNode) {
                        doc = dom.getDocument(this.anchorNode);
                    } else if (this.docSelection.type == CONTROL) {
                        var controlRange = this.docSelection.createRange();
                        if (controlRange.length) {
                            doc = dom.getDocument(controlRange.item(0)).body.createTextRange();
                        }
                    }
                    if (doc) {
                        var textRange = doc.body.createTextRange();
                        textRange.select();
                        this.docSelection.empty();
                    }
                }
            } catch(ex) {}
            updateEmptySelection(this);
        };

        selProto.addRange = function(range) {
            if (this.docSelection.type == CONTROL) {
                addRangeToControlSelection(this, range);
            } else {
                WrappedRange.rangeToTextRange(range).select();
                this._ranges[0] = range;
                this.rangeCount = 1;
                this.isCollapsed = this._ranges[0].collapsed;
                updateAnchorAndFocusFromRange(this, range, false);
            }
        };

        selProto.setRanges = function(ranges) {
            this.removeAllRanges();
            var rangeCount = ranges.length;
            if (rangeCount > 1) {
                createControlSelection(this, ranges);
            } else if (rangeCount) {
                this.addRange(ranges[0]);
            }
        };
    } else {
        module.fail("No means of selecting a Range or TextRange was found");
        return false;
    }

    selProto.getRangeAt = function(index) {
        if (index < 0 || index >= this.rangeCount) {
            throw new DOMException("INDEX_SIZE_ERR");
        } else {
            return this._ranges[index];
        }
    };

    var refreshSelection;

    if (useDocumentSelection) {
        refreshSelection = function(sel) {
            var range;
            if (api.isSelectionValid(sel.win)) {
                range = sel.docSelection.createRange();
            } else {
                range = dom.getBody(sel.win.document).createTextRange();
                range.collapse(true);
            }


            if (sel.docSelection.type == CONTROL) {
                updateControlSelection(sel);
            } else if (isTextRange(range)) {
                updateFromTextRange(sel, range);
            } else {
                updateEmptySelection(sel);
            }
        };
    } else if (util.isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == "number") {
        refreshSelection = function(sel) {
            if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
                updateControlSelection(sel);
            } else {
                sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;
                if (sel.rangeCount) {
                    for (var i = 0, len = sel.rangeCount; i < len; ++i) {
                        sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));
                    }
                    updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackwards(sel.nativeSelection));
                    sel.isCollapsed = selectionIsCollapsed(sel);
                } else {
                    updateEmptySelection(sel);
                }
            }
        };
    } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && api.features.implementsDomRange) {
        refreshSelection = function(sel) {
            var range, nativeSel = sel.nativeSelection;
            if (nativeSel.anchorNode) {
                range = getSelectionRangeAt(nativeSel, 0);
                sel._ranges = [range];
                sel.rangeCount = 1;
                updateAnchorAndFocusFromNativeSelection(sel);
                sel.isCollapsed = selectionIsCollapsed(sel);
            } else {
                updateEmptySelection(sel);
            }
        };
    } else {
        module.fail("No means of obtaining a Range or TextRange from the user's selection was found");
        return false;
    }

    selProto.refresh = function(checkForChanges) {
        var oldRanges = checkForChanges ? this._ranges.slice(0) : null;
        refreshSelection(this);
        if (checkForChanges) {
            var i = oldRanges.length;
            if (i != this._ranges.length) {
                return false;
            }
            while (i--) {
                if (!DomRange.rangesEqual(oldRanges[i], this._ranges[i])) {
                    return false;
                }
            }
            return true;
        }
    };

    // Removal of a single range
    var removeRangeManually = function(sel, range) {
        var ranges = sel.getAllRanges(), removed = false;
        sel.removeAllRanges();
        for (var i = 0, len = ranges.length; i < len; ++i) {
            if (removed || range !== ranges[i]) {
                sel.addRange(ranges[i]);
            } else {
                // According to the draft WHATWG Range spec, the same range may be added to the selection multiple
                // times. removeRange should only remove the first instance, so the following ensures only the first
                // instance is removed
                removed = true;
            }
        }
        if (!sel.rangeCount) {
            updateEmptySelection(sel);
        }
    };

    if (implementsControlRange) {
        selProto.removeRange = function(range) {
            if (this.docSelection.type == CONTROL) {
                var controlRange = this.docSelection.createRange();
                var rangeElement = getSingleElementFromRange(range);

                // Create a new ControlRange containing all the elements in the selected ControlRange minus the
                // element contained by the supplied range
                var doc = dom.getDocument(controlRange.item(0));
                var newControlRange = dom.getBody(doc).createControlRange();
                var el, removed = false;
                for (var i = 0, len = controlRange.length; i < len; ++i) {
                    el = controlRange.item(i);
                    if (el !== rangeElement || removed) {
                        newControlRange.add(controlRange.item(i));
                    } else {
                        removed = true;
                    }
                }
                newControlRange.select();

                // Update the wrapped selection based on what's now in the native selection
                updateControlSelection(this);
            } else {
                removeRangeManually(this, range);
            }
        };
    } else {
        selProto.removeRange = function(range) {
            removeRangeManually(this, range);
        };
    }

    // Detecting if a selection is backwards
    var selectionIsBackwards;
    if (!useDocumentSelection && selectionHasAnchorAndFocus && api.features.implementsDomRange) {
        selectionIsBackwards = function(sel) {
            var backwards = false;
            if (sel.anchorNode) {
                backwards = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);
            }
            return backwards;
        };

        selProto.isBackwards = function() {
            return selectionIsBackwards(this);
        };
    } else {
        selectionIsBackwards = selProto.isBackwards = function() {
            return false;
        };
    }

    // Selection text
    // This is conformant to the new WHATWG DOM Range draft spec but differs from WebKit and Mozilla's implementation
    selProto.toString = function() {

        var rangeTexts = [];
        for (var i = 0, len = this.rangeCount; i < len; ++i) {
            rangeTexts[i] = "" + this._ranges[i];
        }
        return rangeTexts.join("");
    };

    function assertNodeInSameDocument(sel, node) {
        if (sel.anchorNode && (dom.getDocument(sel.anchorNode) !== dom.getDocument(node))) {
            throw new DOMException("WRONG_DOCUMENT_ERR");
        }
    }

    // No current browsers conform fully to the HTML 5 draft spec for this method, so Rangy's own method is always used
    selProto.collapse = function(node, offset) {
        assertNodeInSameDocument(this, node);
        var range = api.createRange(dom.getDocument(node));
        range.collapseToPoint(node, offset);
        this.removeAllRanges();
        this.addRange(range);
        this.isCollapsed = true;
    };

    selProto.collapseToStart = function() {
        if (this.rangeCount) {
            var range = this._ranges[0];
            this.collapse(range.startContainer, range.startOffset);
        } else {
            throw new DOMException("INVALID_STATE_ERR");
        }
    };

    selProto.collapseToEnd = function() {
        if (this.rangeCount) {
            var range = this._ranges[this.rangeCount - 1];
            this.collapse(range.endContainer, range.endOffset);
        } else {
            throw new DOMException("INVALID_STATE_ERR");
        }
    };

    // The HTML 5 spec is very specific on how selectAllChildren should be implemented so the native implementation is
    // never used by Rangy.
    selProto.selectAllChildren = function(node) {
        assertNodeInSameDocument(this, node);
        var range = api.createRange(dom.getDocument(node));
        range.selectNodeContents(node);
        this.removeAllRanges();
        this.addRange(range);
    };

    selProto.deleteFromDocument = function() {
        // Sepcial behaviour required for Control selections
        if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
            var controlRange = this.docSelection.createRange();
            var element;
            while (controlRange.length) {
                element = controlRange.item(0);
                controlRange.remove(element);
                element.parentNode.removeChild(element);
            }
            this.refresh();
        } else if (this.rangeCount) {
            var ranges = this.getAllRanges();
            this.removeAllRanges();
            for (var i = 0, len = ranges.length; i < len; ++i) {
                ranges[i].deleteContents();
            }
            // The HTML5 spec says nothing about what the selection should contain after calling deleteContents on each
            // range. Firefox moves the selection to where the final selected range was, so we emulate that
            this.addRange(ranges[len - 1]);
        }
    };

    // The following are non-standard extensions
    selProto.getAllRanges = function() {
        return this._ranges.slice(0);
    };

    selProto.setSingleRange = function(range) {
        this.setRanges( [range] );
    };

    selProto.containsNode = function(node, allowPartial) {
        for (var i = 0, len = this._ranges.length; i < len; ++i) {
            if (this._ranges[i].containsNode(node, allowPartial)) {
                return true;
            }
        }
        return false;
    };

    selProto.toHtml = function() {
        var html = "";
        if (this.rangeCount) {
            var container = DomRange.getRangeDocument(this._ranges[0]).createElement("div");
            for (var i = 0, len = this._ranges.length; i < len; ++i) {
                container.appendChild(this._ranges[i].cloneContents());
            }
            html = container.innerHTML;
        }
        return html;
    };

    function inspect(sel) {
        var rangeInspects = [];
        var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
        var focus = new DomPosition(sel.focusNode, sel.focusOffset);
        var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";

        if (typeof sel.rangeCount != "undefined") {
            for (var i = 0, len = sel.rangeCount; i < len; ++i) {
                rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
            }
        }
        return "[" + name + "(Ranges: " + rangeInspects.join(", ") +
                ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";

    }

    selProto.getName = function() {
        return "WrappedSelection";
    };

    selProto.inspect = function() {
        return inspect(this);
    };

    selProto.detach = function() {
        this.win[windowPropertyName] = null;
        this.win = this.anchorNode = this.focusNode = null;
    };

    WrappedSelection.inspect = inspect;

    api.Selection = WrappedSelection;

    api.selectionPrototype = selProto;

    api.addCreateMissingNativeApiListener(function(win) {
        if (typeof win.getSelection == "undefined") {
            win.getSelection = function() {
                return api.getSelection(this);
            };
        }
        win = null;
    });
});

define("rangy", (function (global) {
    return function () {
        var ret, fn;
        return ret || global.rangy;
    };
}(this)));

//     Underscore.js 1.5.1
//     http://underscorejs.org
//     (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
//     Underscore may be freely distributed under the MIT license.

(function() {

  // Baseline setup
  // --------------

  // Establish the root object, `window` in the browser, or `global` on the server.
  var root = this;

  // Save the previous value of the `_` variable.
  var previousUnderscore = root._;

  // Establish the object that gets returned to break out of a loop iteration.
  var breaker = {};

  // Save bytes in the minified (but not gzipped) version:
  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;

  // Create quick reference variables for speed access to core prototypes.
  var
    push             = ArrayProto.push,
    slice            = ArrayProto.slice,
    concat           = ArrayProto.concat,
    toString         = ObjProto.toString,
    hasOwnProperty   = ObjProto.hasOwnProperty;

  // All **ECMAScript 5** native function implementations that we hope to use
  // are declared here.
  var
    nativeForEach      = ArrayProto.forEach,
    nativeMap          = ArrayProto.map,
    nativeReduce       = ArrayProto.reduce,
    nativeReduceRight  = ArrayProto.reduceRight,
    nativeFilter       = ArrayProto.filter,
    nativeEvery        = ArrayProto.every,
    nativeSome         = ArrayProto.some,
    nativeIndexOf      = ArrayProto.indexOf,
    nativeLastIndexOf  = ArrayProto.lastIndexOf,
    nativeIsArray      = Array.isArray,
    nativeKeys         = Object.keys,
    nativeBind         = FuncProto.bind;

  // Create a safe reference to the Underscore object for use below.
  var _ = function(obj) {
    if (obj instanceof _) return obj;
    if (!(this instanceof _)) return new _(obj);
    this._wrapped = obj;
  };

  // Export the Underscore object for **Node.js**, with
  // backwards-compatibility for the old `require()` API. If we're in
  // the browser, add `_` as a global object via a string identifier,
  // for Closure Compiler "advanced" mode.
  if (typeof exports !== 'undefined') {
    if (typeof module !== 'undefined' && module.exports) {
      exports = module.exports = _;
    }
    exports._ = _;
  } else {
    root._ = _;
  }

  // Current version.
  _.VERSION = '1.5.1';

  // Collection Functions
  // --------------------

  // The cornerstone, an `each` implementation, aka `forEach`.
  // Handles objects with the built-in `forEach`, arrays, and raw objects.
  // Delegates to **ECMAScript 5**'s native `forEach` if available.
  var each = _.each = _.forEach = function(obj, iterator, context) {
    if (obj == null) return;
    if (nativeForEach && obj.forEach === nativeForEach) {
      obj.forEach(iterator, context);
    } else if (obj.length === +obj.length) {
      for (var i = 0, l = obj.length; i < l; i++) {
        if (iterator.call(context, obj[i], i, obj) === breaker) return;
      }
    } else {
      for (var key in obj) {
        if (_.has(obj, key)) {
          if (iterator.call(context, obj[key], key, obj) === breaker) return;
        }
      }
    }
  };

  // Return the results of applying the iterator to each element.
  // Delegates to **ECMAScript 5**'s native `map` if available.
  _.map = _.collect = function(obj, iterator, context) {
    var results = [];
    if (obj == null) return results;
    if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
    each(obj, function(value, index, list) {
      results.push(iterator.call(context, value, index, list));
    });
    return results;
  };

  var reduceError = 'Reduce of empty array with no initial value';

  // **Reduce** builds up a single result from a list of values, aka `inject`,
  // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
  _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
    var initial = arguments.length > 2;
    if (obj == null) obj = [];
    if (nativeReduce && obj.reduce === nativeReduce) {
      if (context) iterator = _.bind(iterator, context);
      return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
    }
    each(obj, function(value, index, list) {
      if (!initial) {
        memo = value;
        initial = true;
      } else {
        memo = iterator.call(context, memo, value, index, list);
      }
    });
    if (!initial) throw new TypeError(reduceError);
    return memo;
  };

  // The right-associative version of reduce, also known as `foldr`.
  // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
  _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
    var initial = arguments.length > 2;
    if (obj == null) obj = [];
    if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
      if (context) iterator = _.bind(iterator, context);
      return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
    }
    var length = obj.length;
    if (length !== +length) {
      var keys = _.keys(obj);
      length = keys.length;
    }
    each(obj, function(value, index, list) {
      index = keys ? keys[--length] : --length;
      if (!initial) {
        memo = obj[index];
        initial = true;
      } else {
        memo = iterator.call(context, memo, obj[index], index, list);
      }
    });
    if (!initial) throw new TypeError(reduceError);
    return memo;
  };

  // Return the first value which passes a truth test. Aliased as `detect`.
  _.find = _.detect = function(obj, iterator, context) {
    var result;
    any(obj, function(value, index, list) {
      if (iterator.call(context, value, index, list)) {
        result = value;
        return true;
      }
    });
    return result;
  };

  // Return all the elements that pass a truth test.
  // Delegates to **ECMAScript 5**'s native `filter` if available.
  // Aliased as `select`.
  _.filter = _.select = function(obj, iterator, context) {
    var results = [];
    if (obj == null) return results;
    if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
    each(obj, function(value, index, list) {
      if (iterator.call(context, value, index, list)) results.push(value);
    });
    return results;
  };

  // Return all the elements for which a truth test fails.
  _.reject = function(obj, iterator, context) {
    return _.filter(obj, function(value, index, list) {
      return !iterator.call(context, value, index, list);
    }, context);
  };

  // Determine whether all of the elements match a truth test.
  // Delegates to **ECMAScript 5**'s native `every` if available.
  // Aliased as `all`.
  _.every = _.all = function(obj, iterator, context) {
    iterator || (iterator = _.identity);
    var result = true;
    if (obj == null) return result;
    if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
    each(obj, function(value, index, list) {
      if (!(result = result && iterator.call(context, value, index, list))) return breaker;
    });
    return !!result;
  };

  // Determine if at least one element in the object matches a truth test.
  // Delegates to **ECMAScript 5**'s native `some` if available.
  // Aliased as `any`.
  var any = _.some = _.any = function(obj, iterator, context) {
    iterator || (iterator = _.identity);
    var result = false;
    if (obj == null) return result;
    if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
    each(obj, function(value, index, list) {
      if (result || (result = iterator.call(context, value, index, list))) return breaker;
    });
    return !!result;
  };

  // Determine if the array or object contains a given value (using `===`).
  // Aliased as `include`.
  _.contains = _.include = function(obj, target) {
    if (obj == null) return false;
    if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
    return any(obj, function(value) {
      return value === target;
    });
  };

  // Invoke a method (with arguments) on every item in a collection.
  _.invoke = function(obj, method) {
    var args = slice.call(arguments, 2);
    var isFunc = _.isFunction(method);
    return _.map(obj, function(value) {
      return (isFunc ? method : value[method]).apply(value, args);
    });
  };

  // Convenience version of a common use case of `map`: fetching a property.
  _.pluck = function(obj, key) {
    return _.map(obj, function(value){ return value[key]; });
  };

  // Convenience version of a common use case of `filter`: selecting only objects
  // containing specific `key:value` pairs.
  _.where = function(obj, attrs, first) {
    if (_.isEmpty(attrs)) return first ? void 0 : [];
    return _[first ? 'find' : 'filter'](obj, function(value) {
      for (var key in attrs) {
        if (attrs[key] !== value[key]) return false;
      }
      return true;
    });
  };

  // Convenience version of a common use case of `find`: getting the first object
  // containing specific `key:value` pairs.
  _.findWhere = function(obj, attrs) {
    return _.where(obj, attrs, true);
  };

  // Return the maximum element or (element-based computation).
  // Can't optimize arrays of integers longer than 65,535 elements.
  // See [WebKit Bug 80797](https://bugs.webkit.org/show_bug.cgi?id=80797)
  _.max = function(obj, iterator, context) {
    if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
      return Math.max.apply(Math, obj);
    }
    if (!iterator && _.isEmpty(obj)) return -Infinity;
    var result = {computed : -Infinity, value: -Infinity};
    each(obj, function(value, index, list) {
      var computed = iterator ? iterator.call(context, value, index, list) : value;
      computed > result.computed && (result = {value : value, computed : computed});
    });
    return result.value;
  };

  // Return the minimum element (or element-based computation).
  _.min = function(obj, iterator, context) {
    if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
      return Math.min.apply(Math, obj);
    }
    if (!iterator && _.isEmpty(obj)) return Infinity;
    var result = {computed : Infinity, value: Infinity};
    each(obj, function(value, index, list) {
      var computed = iterator ? iterator.call(context, value, index, list) : value;
      computed < result.computed && (result = {value : value, computed : computed});
    });
    return result.value;
  };

  // Shuffle an array.
  _.shuffle = function(obj) {
    var rand;
    var index = 0;
    var shuffled = [];
    each(obj, function(value) {
      rand = _.random(index++);
      shuffled[index - 1] = shuffled[rand];
      shuffled[rand] = value;
    });
    return shuffled;
  };

  // An internal function to generate lookup iterators.
  var lookupIterator = function(value) {
    return _.isFunction(value) ? value : function(obj){ return obj[value]; };
  };

  // Sort the object's values by a criterion produced by an iterator.
  _.sortBy = function(obj, value, context) {
    var iterator = lookupIterator(value);
    return _.pluck(_.map(obj, function(value, index, list) {
      return {
        value : value,
        index : index,
        criteria : iterator.call(context, value, index, list)
      };
    }).sort(function(left, right) {
      var a = left.criteria;
      var b = right.criteria;
      if (a !== b) {
        if (a > b || a === void 0) return 1;
        if (a < b || b === void 0) return -1;
      }
      return left.index < right.index ? -1 : 1;
    }), 'value');
  };

  // An internal function used for aggregate "group by" operations.
  var group = function(obj, value, context, behavior) {
    var result = {};
    var iterator = lookupIterator(value == null ? _.identity : value);
    each(obj, function(value, index) {
      var key = iterator.call(context, value, index, obj);
      behavior(result, key, value);
    });
    return result;
  };

  // Groups the object's values by a criterion. Pass either a string attribute
  // to group by, or a function that returns the criterion.
  _.groupBy = function(obj, value, context) {
    return group(obj, value, context, function(result, key, value) {
      (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
    });
  };

  // Counts instances of an object that group by a certain criterion. Pass
  // either a string attribute to count by, or a function that returns the
  // criterion.
  _.countBy = function(obj, value, context) {
    return group(obj, value, context, function(result, key) {
      if (!_.has(result, key)) result[key] = 0;
      result[key]++;
    });
  };

  // Use a comparator function to figure out the smallest index at which
  // an object should be inserted so as to maintain order. Uses binary search.
  _.sortedIndex = function(array, obj, iterator, context) {
    iterator = iterator == null ? _.identity : lookupIterator(iterator);
    var value = iterator.call(context, obj);
    var low = 0, high = array.length;
    while (low < high) {
      var mid = (low + high) >>> 1;
      iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
    }
    return low;
  };

  // Safely create a real, live array from anything iterable.
  _.toArray = function(obj) {
    if (!obj) return [];
    if (_.isArray(obj)) return slice.call(obj);
    if (obj.length === +obj.length) return _.map(obj, _.identity);
    return _.values(obj);
  };

  // Return the number of elements in an object.
  _.size = function(obj) {
    if (obj == null) return 0;
    return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
  };

  // Array Functions
  // ---------------

  // Get the first element of an array. Passing **n** will return the first N
  // values in the array. Aliased as `head` and `take`. The **guard** check
  // allows it to work with `_.map`.
  _.first = _.head = _.take = function(array, n, guard) {
    if (array == null) return void 0;
    return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
  };

  // Returns everything but the last entry of the array. Especially useful on
  // the arguments object. Passing **n** will return all the values in
  // the array, excluding the last N. The **guard** check allows it to work with
  // `_.map`.
  _.initial = function(array, n, guard) {
    return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
  };

  // Get the last element of an array. Passing **n** will return the last N
  // values in the array. The **guard** check allows it to work with `_.map`.
  _.last = function(array, n, guard) {
    if (array == null) return void 0;
    if ((n != null) && !guard) {
      return slice.call(array, Math.max(array.length - n, 0));
    } else {
      return array[array.length - 1];
    }
  };

  // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
  // Especially useful on the arguments object. Passing an **n** will return
  // the rest N values in the array. The **guard**
  // check allows it to work with `_.map`.
  _.rest = _.tail = _.drop = function(array, n, guard) {
    return slice.call(array, (n == null) || guard ? 1 : n);
  };

  // Trim out all falsy values from an array.
  _.compact = function(array) {
    return _.filter(array, _.identity);
  };

  // Internal implementation of a recursive `flatten` function.
  var flatten = function(input, shallow, output) {
    if (shallow && _.every(input, _.isArray)) {
      return concat.apply(output, input);
    }
    each(input, function(value) {
      if (_.isArray(value) || _.isArguments(value)) {
        shallow ? push.apply(output, value) : flatten(value, shallow, output);
      } else {
        output.push(value);
      }
    });
    return output;
  };

  // Return a completely flattened version of an array.
  _.flatten = function(array, shallow) {
    return flatten(array, shallow, []);
  };

  // Return a version of the array that does not contain the specified value(s).
  _.without = function(array) {
    return _.difference(array, slice.call(arguments, 1));
  };

  // Produce a duplicate-free version of the array. If the array has already
  // been sorted, you have the option of using a faster algorithm.
  // Aliased as `unique`.
  _.uniq = _.unique = function(array, isSorted, iterator, context) {
    if (_.isFunction(isSorted)) {
      context = iterator;
      iterator = isSorted;
      isSorted = false;
    }
    var initial = iterator ? _.map(array, iterator, context) : array;
    var results = [];
    var seen = [];
    each(initial, function(value, index) {
      if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
        seen.push(value);
        results.push(array[index]);
      }
    });
    return results;
  };

  // Produce an array that contains the union: each distinct element from all of
  // the passed-in arrays.
  _.union = function() {
    return _.uniq(_.flatten(arguments, true));
  };

  // Produce an array that contains every item shared between all the
  // passed-in arrays.
  _.intersection = function(array) {
    var rest = slice.call(arguments, 1);
    return _.filter(_.uniq(array), function(item) {
      return _.every(rest, function(other) {
        return _.indexOf(other, item) >= 0;
      });
    });
  };

  // Take the difference between one array and a number of other arrays.
  // Only the elements present in just the first array will remain.
  _.difference = function(array) {
    var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
    return _.filter(array, function(value){ return !_.contains(rest, value); });
  };

  // Zip together multiple lists into a single array -- elements that share
  // an index go together.
  _.zip = function() {
    var length = _.max(_.pluck(arguments, "length").concat(0));
    var results = new Array(length);
    for (var i = 0; i < length; i++) {
      results[i] = _.pluck(arguments, '' + i);
    }
    return results;
  };

  // Converts lists into objects. Pass either a single array of `[key, value]`
  // pairs, or two parallel arrays of the same length -- one of keys, and one of
  // the corresponding values.
  _.object = function(list, values) {
    if (list == null) return {};
    var result = {};
    for (var i = 0, l = list.length; i < l; i++) {
      if (values) {
        result[list[i]] = values[i];
      } else {
        result[list[i][0]] = list[i][1];
      }
    }
    return result;
  };

  // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
  // we need this function. Return the position of the first occurrence of an
  // item in an array, or -1 if the item is not included in the array.
  // Delegates to **ECMAScript 5**'s native `indexOf` if available.
  // If the array is large and already in sort order, pass `true`
  // for **isSorted** to use binary search.
  _.indexOf = function(array, item, isSorted) {
    if (array == null) return -1;
    var i = 0, l = array.length;
    if (isSorted) {
      if (typeof isSorted == 'number') {
        i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
      } else {
        i = _.sortedIndex(array, item);
        return array[i] === item ? i : -1;
      }
    }
    if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
    for (; i < l; i++) if (array[i] === item) return i;
    return -1;
  };

  // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
  _.lastIndexOf = function(array, item, from) {
    if (array == null) return -1;
    var hasIndex = from != null;
    if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
      return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
    }
    var i = (hasIndex ? from : array.length);
    while (i--) if (array[i] === item) return i;
    return -1;
  };

  // Generate an integer Array containing an arithmetic progression. A port of
  // the native Python `range()` function. See
  // [the Python documentation](http://docs.python.org/library/functions.html#range).
  _.range = function(start, stop, step) {
    if (arguments.length <= 1) {
      stop = start || 0;
      start = 0;
    }
    step = arguments[2] || 1;

    var len = Math.max(Math.ceil((stop - start) / step), 0);
    var idx = 0;
    var range = new Array(len);

    while(idx < len) {
      range[idx++] = start;
      start += step;
    }

    return range;
  };

  // Function (ahem) Functions
  // ------------------

  // Reusable constructor function for prototype setting.
  var ctor = function(){};

  // Create a function bound to a given object (assigning `this`, and arguments,
  // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
  // available.
  _.bind = function(func, context) {
    var args, bound;
    if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
    if (!_.isFunction(func)) throw new TypeError;
    args = slice.call(arguments, 2);
    return bound = function() {
      if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
      ctor.prototype = func.prototype;
      var self = new ctor;
      ctor.prototype = null;
      var result = func.apply(self, args.concat(slice.call(arguments)));
      if (Object(result) === result) return result;
      return self;
    };
  };

  // Partially apply a function by creating a version that has had some of its
  // arguments pre-filled, without changing its dynamic `this` context.
  _.partial = function(func) {
    var args = slice.call(arguments, 1);
    return function() {
      return func.apply(this, args.concat(slice.call(arguments)));
    };
  };

  // Bind all of an object's methods to that object. Useful for ensuring that
  // all callbacks defined on an object belong to it.
  _.bindAll = function(obj) {
    var funcs = slice.call(arguments, 1);
    if (funcs.length === 0) throw new Error("bindAll must be passed function names");
    each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
    return obj;
  };

  // Memoize an expensive function by storing its results.
  _.memoize = function(func, hasher) {
    var memo = {};
    hasher || (hasher = _.identity);
    return function() {
      var key = hasher.apply(this, arguments);
      return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
    };
  };

  // Delays a function for the given number of milliseconds, and then calls
  // it with the arguments supplied.
  _.delay = function(func, wait) {
    var args = slice.call(arguments, 2);
    return setTimeout(function(){ return func.apply(null, args); }, wait);
  };

  // Defers a function, scheduling it to run after the current call stack has
  // cleared.
  _.defer = function(func) {
    return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
  };

  // Returns a function, that, when invoked, will only be triggered at most once
  // during a given window of time. Normally, the throttled function will run
  // as much as it can, without ever going more than once per `wait` duration;
  // but if you'd like to disable the execution on the leading edge, pass
  // `{leading: false}`. To disable execution on the trailing edge, ditto.
  _.throttle = function(func, wait, options) {
    var context, args, result;
    var timeout = null;
    var previous = 0;
    options || (options = {});
    var later = function() {
      previous = options.leading === false ? 0 : new Date;
      timeout = null;
      result = func.apply(context, args);
    };
    return function() {
      var now = new Date;
      if (!previous && options.leading === false) previous = now;
      var remaining = wait - (now - previous);
      context = this;
      args = arguments;
      if (remaining <= 0) {
        clearTimeout(timeout);
        timeout = null;
        previous = now;
        result = func.apply(context, args);
      } else if (!timeout && options.trailing !== false) {
        timeout = setTimeout(later, remaining);
      }
      return result;
    };
  };

  // Returns a function, that, as long as it continues to be invoked, will not
  // be triggered. The function will be called after it stops being called for
  // N milliseconds. If `immediate` is passed, trigger the function on the
  // leading edge, instead of the trailing.
  _.debounce = function(func, wait, immediate) {
    var result;
    var timeout = null;
    return function() {
      var context = this, args = arguments;
      var later = function() {
        timeout = null;
        if (!immediate) result = func.apply(context, args);
      };
      var callNow = immediate && !timeout;
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
      if (callNow) result = func.apply(context, args);
      return result;
    };
  };

  // Returns a function that will be executed at most one time, no matter how
  // often you call it. Useful for lazy initialization.
  _.once = function(func) {
    var ran = false, memo;
    return function() {
      if (ran) return memo;
      ran = true;
      memo = func.apply(this, arguments);
      func = null;
      return memo;
    };
  };

  // Returns the first function passed as an argument to the second,
  // allowing you to adjust arguments, run code before and after, and
  // conditionally execute the original function.
  _.wrap = function(func, wrapper) {
    return function() {
      var args = [func];
      push.apply(args, arguments);
      return wrapper.apply(this, args);
    };
  };

  // Returns a function that is the composition of a list of functions, each
  // consuming the return value of the function that follows.
  _.compose = function() {
    var funcs = arguments;
    return function() {
      var args = arguments;
      for (var i = funcs.length - 1; i >= 0; i--) {
        args = [funcs[i].apply(this, args)];
      }
      return args[0];
    };
  };

  // Returns a function that will only be executed after being called N times.
  _.after = function(times, func) {
    return function() {
      if (--times < 1) {
        return func.apply(this, arguments);
      }
    };
  };

  // Object Functions
  // ----------------

  // Retrieve the names of an object's properties.
  // Delegates to **ECMAScript 5**'s native `Object.keys`
  _.keys = nativeKeys || function(obj) {
    if (obj !== Object(obj)) throw new TypeError('Invalid object');
    var keys = [];
    for (var key in obj) if (_.has(obj, key)) keys.push(key);
    return keys;
  };

  // Retrieve the values of an object's properties.
  _.values = function(obj) {
    var values = [];
    for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
    return values;
  };

  // Convert an object into a list of `[key, value]` pairs.
  _.pairs = function(obj) {
    var pairs = [];
    for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
    return pairs;
  };

  // Invert the keys and values of an object. The values must be serializable.
  _.invert = function(obj) {
    var result = {};
    for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
    return result;
  };

  // Return a sorted list of the function names available on the object.
  // Aliased as `methods`
  _.functions = _.methods = function(obj) {
    var names = [];
    for (var key in obj) {
      if (_.isFunction(obj[key])) names.push(key);
    }
    return names.sort();
  };

  // Extend a given object with all the properties in passed-in object(s).
  _.extend = function(obj) {
    each(slice.call(arguments, 1), function(source) {
      if (source) {
        for (var prop in source) {
          obj[prop] = source[prop];
        }
      }
    });
    return obj;
  };

  // Return a copy of the object only containing the whitelisted properties.
  _.pick = function(obj) {
    var copy = {};
    var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
    each(keys, function(key) {
      if (key in obj) copy[key] = obj[key];
    });
    return copy;
  };

   // Return a copy of the object without the blacklisted properties.
  _.omit = function(obj) {
    var copy = {};
    var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
    for (var key in obj) {
      if (!_.contains(keys, key)) copy[key] = obj[key];
    }
    return copy;
  };

  // Fill in a given object with default properties.
  _.defaults = function(obj) {
    each(slice.call(arguments, 1), function(source) {
      if (source) {
        for (var prop in source) {
          if (obj[prop] === void 0) obj[prop] = source[prop];
        }
      }
    });
    return obj;
  };

  // Create a (shallow-cloned) duplicate of an object.
  _.clone = function(obj) {
    if (!_.isObject(obj)) return obj;
    return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
  };

  // Invokes interceptor with the obj, and then returns obj.
  // The primary purpose of this method is to "tap into" a method chain, in
  // order to perform operations on intermediate results within the chain.
  _.tap = function(obj, interceptor) {
    interceptor(obj);
    return obj;
  };

  // Internal recursive comparison function for `isEqual`.
  var eq = function(a, b, aStack, bStack) {
    // Identical objects are equal. `0 === -0`, but they aren't identical.
    // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
    if (a === b) return a !== 0 || 1 / a == 1 / b;
    // A strict comparison is necessary because `null == undefined`.
    if (a == null || b == null) return a === b;
    // Unwrap any wrapped objects.
    if (a instanceof _) a = a._wrapped;
    if (b instanceof _) b = b._wrapped;
    // Compare `[[Class]]` names.
    var className = toString.call(a);
    if (className != toString.call(b)) return false;
    switch (className) {
      // Strings, numbers, dates, and booleans are compared by value.
      case '[object String]':
        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
        // equivalent to `new String("5")`.
        return a == String(b);
      case '[object Number]':
        // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
        // other numeric values.
        return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
      case '[object Date]':
      case '[object Boolean]':
        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
        // millisecond representations. Note that invalid dates with millisecond representations
        // of `NaN` are not equivalent.
        return +a == +b;
      // RegExps are compared by their source patterns and flags.
      case '[object RegExp]':
        return a.source == b.source &&
               a.global == b.global &&
               a.multiline == b.multiline &&
               a.ignoreCase == b.ignoreCase;
    }
    if (typeof a != 'object' || typeof b != 'object') return false;
    // Assume equality for cyclic structures. The algorithm for detecting cyclic
    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
    var length = aStack.length;
    while (length--) {
      // Linear search. Performance is inversely proportional to the number of
      // unique nested structures.
      if (aStack[length] == a) return bStack[length] == b;
    }
    // Objects with different constructors are not equivalent, but `Object`s
    // from different frames are.
    var aCtor = a.constructor, bCtor = b.constructor;
    if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
                             _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
      return false;
    }
    // Add the first object to the stack of traversed objects.
    aStack.push(a);
    bStack.push(b);
    var size = 0, result = true;
    // Recursively compare objects and arrays.
    if (className == '[object Array]') {
      // Compare array lengths to determine if a deep comparison is necessary.
      size = a.length;
      result = size == b.length;
      if (result) {
        // Deep compare the contents, ignoring non-numeric properties.
        while (size--) {
          if (!(result = eq(a[size], b[size], aStack, bStack))) break;
        }
      }
    } else {
      // Deep compare objects.
      for (var key in a) {
        if (_.has(a, key)) {
          // Count the expected number of properties.
          size++;
          // Deep compare each member.
          if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
        }
      }
      // Ensure that both objects contain the same number of properties.
      if (result) {
        for (key in b) {
          if (_.has(b, key) && !(size--)) break;
        }
        result = !size;
      }
    }
    // Remove the first object from the stack of traversed objects.
    aStack.pop();
    bStack.pop();
    return result;
  };

  // Perform a deep comparison to check if two objects are equal.
  _.isEqual = function(a, b) {
    return eq(a, b, [], []);
  };

  // Is a given array, string, or object empty?
  // An "empty" object has no enumerable own-properties.
  _.isEmpty = function(obj) {
    if (obj == null) return true;
    if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
    for (var key in obj) if (_.has(obj, key)) return false;
    return true;
  };

  // Is a given value a DOM element?
  _.isElement = function(obj) {
    return !!(obj && obj.nodeType === 1);
  };

  // Is a given value an array?
  // Delegates to ECMA5's native Array.isArray
  _.isArray = nativeIsArray || function(obj) {
    return toString.call(obj) == '[object Array]';
  };

  // Is a given variable an object?
  _.isObject = function(obj) {
    return obj === Object(obj);
  };

  // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
  each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
    _['is' + name] = function(obj) {
      return toString.call(obj) == '[object ' + name + ']';
    };
  });

  // Define a fallback version of the method in browsers (ahem, IE), where
  // there isn't any inspectable "Arguments" type.
  if (!_.isArguments(arguments)) {
    _.isArguments = function(obj) {
      return !!(obj && _.has(obj, 'callee'));
    };
  }

  // Optimize `isFunction` if appropriate.
  if (typeof (/./) !== 'function') {
    _.isFunction = function(obj) {
      return typeof obj === 'function';
    };
  }

  // Is a given object a finite number?
  _.isFinite = function(obj) {
    return isFinite(obj) && !isNaN(parseFloat(obj));
  };

  // Is the given value `NaN`? (NaN is the only number which does not equal itself).
  _.isNaN = function(obj) {
    return _.isNumber(obj) && obj != +obj;
  };

  // Is a given value a boolean?
  _.isBoolean = function(obj) {
    return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
  };

  // Is a given value equal to null?
  _.isNull = function(obj) {
    return obj === null;
  };

  // Is a given variable undefined?
  _.isUndefined = function(obj) {
    return obj === void 0;
  };

  // Shortcut function for checking if an object has a given property directly
  // on itself (in other words, not on a prototype).
  _.has = function(obj, key) {
    return hasOwnProperty.call(obj, key);
  };

  // Utility Functions
  // -----------------

  // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
  // previous owner. Returns a reference to the Underscore object.
  _.noConflict = function() {
    root._ = previousUnderscore;
    return this;
  };

  // Keep the identity function around for default iterators.
  _.identity = function(value) {
    return value;
  };

  // Run a function **n** times.
  _.times = function(n, iterator, context) {
    var accum = Array(Math.max(0, n));
    for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);
    return accum;
  };

  // Return a random integer between min and max (inclusive).
  _.random = function(min, max) {
    if (max == null) {
      max = min;
      min = 0;
    }
    return min + Math.floor(Math.random() * (max - min + 1));
  };

  // List of HTML entities for escaping.
  var entityMap = {
    escape: {
      '&': '&',
      '<': '<',
      '>': '>',
      '"': '"',
      "'": ''',
      '/': '/'
    }
  };
  entityMap.unescape = _.invert(entityMap.escape);

  // Regexes containing the keys and values listed immediately above.
  var entityRegexes = {
    escape:   new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
    unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
  };

  // Functions for escaping and unescaping strings to/from HTML interpolation.
  _.each(['escape', 'unescape'], function(method) {
    _[method] = function(string) {
      if (string == null) return '';
      return ('' + string).replace(entityRegexes[method], function(match) {
        return entityMap[method][match];
      });
    };
  });

  // If the value of the named `property` is a function then invoke it with the
  // `object` as context; otherwise, return it.
  _.result = function(object, property) {
    if (object == null) return void 0;
    var value = object[property];
    return _.isFunction(value) ? value.call(object) : value;
  };

  // Add your own custom functions to the Underscore object.
  _.mixin = function(obj) {
    each(_.functions(obj), function(name){
      var func = _[name] = obj[name];
      _.prototype[name] = function() {
        var args = [this._wrapped];
        push.apply(args, arguments);
        return result.call(this, func.apply(_, args));
      };
    });
  };

  // Generate a unique integer id (unique within the entire client session).
  // Useful for temporary DOM ids.
  var idCounter = 0;
  _.uniqueId = function(prefix) {
    var id = ++idCounter + '';
    return prefix ? prefix + id : id;
  };

  // By default, Underscore uses ERB-style template delimiters, change the
  // following template settings to use alternative delimiters.
  _.templateSettings = {
    evaluate    : /<%([\s\S]+?)%>/g,
    interpolate : /<%=([\s\S]+?)%>/g,
    escape      : /<%-([\s\S]+?)%>/g
  };

  // When customizing `templateSettings`, if you don't want to define an
  // interpolation, evaluation or escaping regex, we need one that is
  // guaranteed not to match.
  var noMatch = /(.)^/;

  // Certain characters need to be escaped so that they can be put into a
  // string literal.
  var escapes = {
    "'":      "'",
    '\\':     '\\',
    '\r':     'r',
    '\n':     'n',
    '\t':     't',
    '\u2028': 'u2028',
    '\u2029': 'u2029'
  };

  var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;

  // JavaScript micro-templating, similar to John Resig's implementation.
  // Underscore templating handles arbitrary delimiters, preserves whitespace,
  // and correctly escapes quotes within interpolated code.
  _.template = function(text, data, settings) {
    var render;
    settings = _.defaults({}, settings, _.templateSettings);

    // Combine delimiters into one regular expression via alternation.
    var matcher = new RegExp([
      (settings.escape || noMatch).source,
      (settings.interpolate || noMatch).source,
      (settings.evaluate || noMatch).source
    ].join('|') + '|$', 'g');

    // Compile the template source, escaping string literals appropriately.
    var index = 0;
    var source = "__p+='";
    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
      source += text.slice(index, offset)
        .replace(escaper, function(match) { return '\\' + escapes[match]; });

      if (escape) {
        source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
      }
      if (interpolate) {
        source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
      }
      if (evaluate) {
        source += "';\n" + evaluate + "\n__p+='";
      }
      index = offset + match.length;
      return match;
    });
    source += "';\n";

    // If a variable is not specified, place data values in local scope.
    if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';

    source = "var __t,__p='',__j=Array.prototype.join," +
      "print=function(){__p+=__j.call(arguments,'');};\n" +
      source + "return __p;\n";

    try {
      render = new Function(settings.variable || 'obj', '_', source);
    } catch (e) {
      e.source = source;
      throw e;
    }

    if (data) return render(data, _);
    var template = function(data) {
      return render.call(this, data, _);
    };

    // Provide the compiled function source as a convenience for precompilation.
    template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';

    return template;
  };

  // Add a "chain" function, which will delegate to the wrapper.
  _.chain = function(obj) {
    return _(obj).chain();
  };

  // OOP
  // ---------------
  // If Underscore is called as a function, it returns a wrapped object that
  // can be used OO-style. This wrapper holds altered versions of all the
  // underscore functions. Wrapped objects may be chained.

  // Helper function to continue chaining intermediate results.
  var result = function(obj) {
    return this._chain ? _(obj).chain() : obj;
  };

  // Add all of the Underscore functions to the wrapper object.
  _.mixin(_);

  // Add all mutator Array functions to the wrapper.
  each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
    var method = ArrayProto[name];
    _.prototype[name] = function() {
      var obj = this._wrapped;
      method.apply(obj, arguments);
      if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
      return result.call(this, obj);
    };
  });

  // Add all accessor Array functions to the wrapper.
  each(['concat', 'join', 'slice'], function(name) {
    var method = ArrayProto[name];
    _.prototype[name] = function() {
      return result.call(this, method.apply(this._wrapped, arguments));
    };
  });

  _.extend(_.prototype, {

    // Start chaining a wrapped Underscore object.
    chain: function() {
      this._chain = true;
      return this;
    },

    // Extracts the result from a wrapped and chained object.
    value: function() {
      return this._wrapped;
    }

  });

}).call(this);

define("underscore", (function (global) {
    return function () {
        var ret, fn;
        return ret || global._;
    };
}(this)));

//Copyright (C) 2012 Kory Nunn

//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

//The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

/*

    This code is not formatted for readability, but rather run-speed and to assist compilers.
    
    However, the code's intention should be transparent.
    
    *** IE SUPPORT ***
    
    If you require this library to work in IE7, add the following after declaring crel.
    
    var testDiv = document.createElement('div'),
        testLabel = document.createElement('label');

    testDiv.setAttribute('class', 'a');    
    testDiv['className'] !== 'a' ? crel.attrMap['class'] = 'className':undefined;
    testDiv.setAttribute('name','a');
    testDiv['name'] !== 'a' ? crel.attrMap['name'] = function(element, value){
        element.id = value;
    }:undefined;
    

    testLabel.setAttribute('for', 'a');
    testLabel['htmlFor'] !== 'a' ? crel.attrMap['for'] = 'htmlFor':undefined;
    
    

*/

(function (root, factory) {
    if (typeof exports === 'object') {
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        define('crel',factory);
    } else {
        root.crel = factory();
    }
}(this, function () {
    // based on http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object
    var isNode = typeof Node === 'object'
        ? function (object) { return object instanceof Node }
        : function (object) {
            return object
                && typeof object === 'object'
                && typeof object.nodeType === 'number'
                && typeof object.nodeName === 'string';
        };

    function crel(){
        var document = window.document,
            args = arguments, //Note: assigned to a variable to assist compilers. Saves about 40 bytes in closure compiler. Has negligable effect on performance.
            element = document.createElement(args[0]),
            child,
            settings = args[1],
            childIndex = 2,
            argumentsLength = args.length,
            attributeMap = crel.attrMap;

        // shortcut
        if(argumentsLength === 1){
            return element;
        }

        if(typeof settings !== 'object' || isNode(settings)) {
            --childIndex;
            settings = null;
        }

        // shortcut if there is only one child that is a string    
        if((argumentsLength - childIndex) === 1 && typeof args[childIndex] === 'string' && element.textContent !== undefined){
            element.textContent = args[childIndex];
        }else{    
            for(; childIndex < argumentsLength; ++childIndex){
                child = args[childIndex];
                
                if(child == null){
                    continue;
                }
                
                if(!isNode(child)){
                    child = document.createTextNode(child);
                }
                
                element.appendChild(child);
            }
        }
        
        for(var key in settings){
            if(!attributeMap[key]){
                element.setAttribute(key, settings[key]);
            }else{
                var attr = crel.attrMap[key];
                if(typeof attr === 'function'){     
                    attr(element, settings[key]);               
                }else{            
                    element.setAttribute(attr, settings[key]);
                }
            }
        }
        
        return element;
    }
    
    // Used for mapping one kind of attribute to the supported version of that in bad browsers.
    // String referenced so that compilers maintain the property name.
    crel['attrMap'] = {};
    
    // String referenced so that compilers maintain the property name.
    crel["isNode"] = isNode;
    
    return crel;
}));

/*!
 * XRegExp-All 3.0.0-pre
 * 
 * Steven Levithan � 2012 MIT License
 */

// Module systems magic dance
;(function(definition) {
    // Don't turn on strict mode for this function, so it can assign to global
    var self;

    // RequireJS
    if (typeof define === 'function') {
        define('xregexp',definition);
    // CommonJS
    } else if (typeof exports === 'object') {
        self = definition();
        // Use Node.js's `module.exports`. This supports both `require('xregexp')` and
        // `require('xregexp').XRegExp`
        (typeof module === 'object' ? (module.exports = self) : exports).XRegExp = self;
    // }
 * 
  • define style rules. See the example page for examples. *
  • mark the {@code
    } and {@code } tags in your source with
     *    {@code class=prettyprint.}
     *    You can also use the (html deprecated) {@code } tag, but the pretty
     *    printer needs to do more substantial DOM manipulations to support that, so
     *    some css styles may not be preserved.
     * </ol>
     * That's it.  I wanted to keep the API as simple as possible, so there's no
     * need to specify which language the code is in, but if you wish, you can add
     * another class to the {@code <pre>} or {@code <code>} element to specify the
     * language, as in {@code <pre class="prettyprint lang-java">}.  Any class that
     * starts with "lang-" followed by a file extension, specifies the file type.
     * See the "lang-*.js" files in this directory for code that implements
     * per-language file handlers.
     * <p>
     * Change log:<br>
     * cbeust, 2006/08/22
     * <blockquote>
     *   Java annotations (start with "@") are now captured as literals ("lit")
     * </blockquote>
     * @requires console
     */
    
    // JSLint declarations
    /*global console, document, navigator, setTimeout, window, define */
    
    /** @define {boolean} */
    var IN_GLOBAL_SCOPE = true;
    
    /**
     * Split {@code prettyPrint} into multiple timeouts so as not to interfere with
     * UI events.
     * If set to {@code false}, {@code prettyPrint()} is synchronous.
     */
    window['PR_SHOULD_USE_CONTINUATION'] = true;
    
    /**
     * Pretty print a chunk of code.
     * @param {string} sourceCodeHtml The HTML to pretty print.
     * @param {string} opt_langExtension The language name to use.
     *     Typically, a filename extension like 'cpp' or 'java'.
     * @param {number|boolean} opt_numberLines True to number lines,
     *     or the 1-indexed number of the first line in sourceCodeHtml.
     * @return {string} code as html, but prettier
     */
    var prettyPrintOne;
    /**
     * Find all the {@code <pre>} and {@code <code>} tags in the DOM with
     * {@code class=prettyprint} and prettify them.
     *
     * @param {Function} opt_whenDone called when prettifying is done.
     * @param {HTMLElement|HTMLDocument} opt_root an element or document
     *   containing all the elements to pretty print.
     *   Defaults to {@code document.body}.
     */
    var prettyPrint;
    
    
    (function () {
      var win = window;
      // Keyword lists for various languages.
      // We use things that coerce to strings to make them compact when minified
      // and to defeat aggressive optimizers that fold large string constants.
      var FLOW_CONTROL_KEYWORDS = ["break,continue,do,else,for,if,return,while"];
      var C_KEYWORDS = [FLOW_CONTROL_KEYWORDS,"auto,case,char,const,default," + 
          "double,enum,extern,float,goto,inline,int,long,register,short,signed," +
          "sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];
      var COMMON_KEYWORDS = [C_KEYWORDS,"catch,class,delete,false,import," +
          "new,operator,private,protected,public,this,throw,true,try,typeof"];
      var CPP_KEYWORDS = [COMMON_KEYWORDS,"alignof,align_union,asm,axiom,bool," +
          "concept,concept_map,const_cast,constexpr,decltype,delegate," +
          "dynamic_cast,explicit,export,friend,generic,late_check," +
          "mutable,namespace,nullptr,property,reinterpret_cast,static_assert," +
          "static_cast,template,typeid,typename,using,virtual,where"];
      var JAVA_KEYWORDS = [COMMON_KEYWORDS,
          "abstract,assert,boolean,byte,extends,final,finally,implements,import," +
          "instanceof,interface,null,native,package,strictfp,super,synchronized," +
          "throws,transient"];
      var CSHARP_KEYWORDS = [JAVA_KEYWORDS,
          "as,base,by,checked,decimal,delegate,descending,dynamic,event," +
          "fixed,foreach,from,group,implicit,in,internal,into,is,let," +
          "lock,object,out,override,orderby,params,partial,readonly,ref,sbyte," +
          "sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort," +
          "var,virtual,where"];
      var COFFEE_KEYWORDS = "all,and,by,catch,class,else,extends,false,finally," +
          "for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then," +
          "throw,true,try,unless,until,when,while,yes";
      var JSCRIPT_KEYWORDS = [COMMON_KEYWORDS,
          "debugger,eval,export,function,get,null,set,undefined,var,with," +
          "Infinity,NaN"];
      var PERL_KEYWORDS = "caller,delete,die,do,dump,elsif,eval,exit,foreach,for," +
          "goto,if,import,last,local,my,next,no,our,print,package,redo,require," +
          "sub,undef,unless,until,use,wantarray,while,BEGIN,END";
      var PYTHON_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "and,as,assert,class,def,del," +
          "elif,except,exec,finally,from,global,import,in,is,lambda," +
          "nonlocal,not,or,pass,print,raise,try,with,yield," +
          "False,True,None"];
      var RUBY_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "alias,and,begin,case,class," +
          "def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo," +
          "rescue,retry,self,super,then,true,undef,unless,until,when,yield," +
          "BEGIN,END"];
       var RUST_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "as,assert,const,copy,drop," +
          "enum,extern,fail,false,fn,impl,let,log,loop,match,mod,move,mut,priv," +
          "pub,pure,ref,self,static,struct,true,trait,type,unsafe,use"];
      var SH_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "case,done,elif,esac,eval,fi," +
          "function,in,local,set,then,until"];
      var ALL_KEYWORDS = [
          CPP_KEYWORDS, CSHARP_KEYWORDS, JSCRIPT_KEYWORDS, PERL_KEYWORDS,
          PYTHON_KEYWORDS, RUBY_KEYWORDS, SH_KEYWORDS];
      var C_TYPES = /^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/;
    
      // token style names.  correspond to css classes
      /**
       * token style for a string literal
       * @const
       */
      var PR_STRING = 'str';
      /**
       * token style for a keyword
       * @const
       */
      var PR_KEYWORD = 'kwd';
      /**
       * token style for a comment
       * @const
       */
      var PR_COMMENT = 'com';
      /**
       * token style for a type
       * @const
       */
      var PR_TYPE = 'typ';
      /**
       * token style for a literal value.  e.g. 1, null, true.
       * @const
       */
      var PR_LITERAL = 'lit';
      /**
       * token style for a punctuation string.
       * @const
       */
      var PR_PUNCTUATION = 'pun';
      /**
       * token style for plain text.
       * @const
       */
      var PR_PLAIN = 'pln';
    
      /**
       * token style for an sgml tag.
       * @const
       */
      var PR_TAG = 'tag';
      /**
       * token style for a markup declaration such as a DOCTYPE.
       * @const
       */
      var PR_DECLARATION = 'dec';
      /**
       * token style for embedded source.
       * @const
       */
      var PR_SOURCE = 'src';
      /**
       * token style for an sgml attribute name.
       * @const
       */
      var PR_ATTRIB_NAME = 'atn';
      /**
       * token style for an sgml attribute value.
       * @const
       */
      var PR_ATTRIB_VALUE = 'atv';
    
      /**
       * A class that indicates a section of markup that is not code, e.g. to allow
       * embedding of line numbers within code listings.
       * @const
       */
      var PR_NOCODE = 'nocode';
    
      
      
      /**
       * A set of tokens that can precede a regular expression literal in
       * javascript
       * http://web.archive.org/web/20070717142515/http://www.mozilla.org/js/language/js20/rationale/syntax.html
       * has the full list, but I've removed ones that might be problematic when
       * seen in languages that don't support regular expression literals.
       *
       * <p>Specifically, I've removed any keywords that can't precede a regexp
       * literal in a syntactically legal javascript program, and I've removed the
       * "in" keyword since it's not a keyword in many languages, and might be used
       * as a count of inches.
       *
       * <p>The link above does not accurately describe EcmaScript rules since
       * it fails to distinguish between (a=++/b/i) and (a++/b/i) but it works
       * very well in practice.
       *
       * @private
       * @const
       */
      var REGEXP_PRECEDER_PATTERN = '(?:^^\\.?|[+-]|[!=]=?=?|\\#|%=?|&&?=?|\\(|\\*=?|[+\\-]=|->|\\/=?|::?|<<?=?|>>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*';
      
      // CAVEAT: this does not properly handle the case where a regular
      // expression immediately follows another since a regular expression may
      // have flags for case-sensitivity and the like.  Having regexp tokens
      // adjacent is not valid in any language I'm aware of, so I'm punting.
      // TODO: maybe style special characters inside a regexp as punctuation.
    
      /**
       * Given a group of {@link RegExp}s, returns a {@code RegExp} that globally
       * matches the union of the sets of strings matched by the input RegExp.
       * Since it matches globally, if the input strings have a start-of-input
       * anchor (/^.../), it is ignored for the purposes of unioning.
       * @param {Array.<RegExp>} regexs non multiline, non-global regexs.
       * @return {RegExp} a global regex.
       */
      function combinePrefixPatterns(regexs) {
        var capturedGroupIndex = 0;
      
        var needToFoldCase = false;
        var ignoreCase = false;
        for (var i = 0, n = regexs.length; i < n; ++i) {
          var regex = regexs[i];
          if (regex.ignoreCase) {
            ignoreCase = true;
          } else if (/[a-z]/i.test(regex.source.replace(
                         /\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi, ''))) {
            needToFoldCase = true;
            ignoreCase = false;
            break;
          }
        }
      
        var escapeCharToCodeUnit = {
          'b': 8,
          't': 9,
          'n': 0xa,
          'v': 0xb,
          'f': 0xc,
          'r': 0xd
        };
      
        function decodeEscape(charsetPart) {
          var cc0 = charsetPart.charCodeAt(0);
          if (cc0 !== 92 /* \\ */) {
            return cc0;
          }
          var c1 = charsetPart.charAt(1);
          cc0 = escapeCharToCodeUnit[c1];
          if (cc0) {
            return cc0;
          } else if ('0' <= c1 && c1 <= '7') {
            return parseInt(charsetPart.substring(1), 8);
          } else if (c1 === 'u' || c1 === 'x') {
            return parseInt(charsetPart.substring(2), 16);
          } else {
            return charsetPart.charCodeAt(1);
          }
        }
      
        function encodeEscape(charCode) {
          if (charCode < 0x20) {
            return (charCode < 0x10 ? '\\x0' : '\\x') + charCode.toString(16);
          }
          var ch = String.fromCharCode(charCode);
          return (ch === '\\' || ch === '-' || ch === ']' || ch === '^')
              ? "\\" + ch : ch;
        }
      
        function caseFoldCharset(charSet) {
          var charsetParts = charSet.substring(1, charSet.length - 1).match(
              new RegExp(
                  '\\\\u[0-9A-Fa-f]{4}'
                  + '|\\\\x[0-9A-Fa-f]{2}'
                  + '|\\\\[0-3][0-7]{0,2}'
                  + '|\\\\[0-7]{1,2}'
                  + '|\\\\[\\s\\S]'
                  + '|-'
                  + '|[^-\\\\]',
                  'g'));
          var ranges = [];
          var inverse = charsetParts[0] === '^';
      
          var out = ['['];
          if (inverse) { out.push('^'); }
      
          for (var i = inverse ? 1 : 0, n = charsetParts.length; i < n; ++i) {
            var p = charsetParts[i];
            if (/\\[bdsw]/i.test(p)) {  // Don't muck with named groups.
              out.push(p);
            } else {
              var start = decodeEscape(p);
              var end;
              if (i + 2 < n && '-' === charsetParts[i + 1]) {
                end = decodeEscape(charsetParts[i + 2]);
                i += 2;
              } else {
                end = start;
              }
              ranges.push([start, end]);
              // If the range might intersect letters, then expand it.
              // This case handling is too simplistic.
              // It does not deal with non-latin case folding.
              // It works for latin source code identifiers though.
              if (!(end < 65 || start > 122)) {
                if (!(end < 65 || start > 90)) {
                  ranges.push([Math.max(65, start) | 32, Math.min(end, 90) | 32]);
                }
                if (!(end < 97 || start > 122)) {
                  ranges.push([Math.max(97, start) & ~32, Math.min(end, 122) & ~32]);
                }
              }
            }
          }
      
          // [[1, 10], [3, 4], [8, 12], [14, 14], [16, 16], [17, 17]]
          // -> [[1, 12], [14, 14], [16, 17]]
          ranges.sort(function (a, b) { return (a[0] - b[0]) || (b[1]  - a[1]); });
          var consolidatedRanges = [];
          var lastRange = [];
          for (var i = 0; i < ranges.length; ++i) {
            var range = ranges[i];
            if (range[0] <= lastRange[1] + 1) {
              lastRange[1] = Math.max(lastRange[1], range[1]);
            } else {
              consolidatedRanges.push(lastRange = range);
            }
          }
      
          for (var i = 0; i < consolidatedRanges.length; ++i) {
            var range = consolidatedRanges[i];
            out.push(encodeEscape(range[0]));
            if (range[1] > range[0]) {
              if (range[1] + 1 > range[0]) { out.push('-'); }
              out.push(encodeEscape(range[1]));
            }
          }
          out.push(']');
          return out.join('');
        }
      
        function allowAnywhereFoldCaseAndRenumberGroups(regex) {
          // Split into character sets, escape sequences, punctuation strings
          // like ('(', '(?:', ')', '^'), and runs of characters that do not
          // include any of the above.
          var parts = regex.source.match(
              new RegExp(
                  '(?:'
                  + '\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]'  // a character set
                  + '|\\\\u[A-Fa-f0-9]{4}'  // a unicode escape
                  + '|\\\\x[A-Fa-f0-9]{2}'  // a hex escape
                  + '|\\\\[0-9]+'  // a back-reference or octal escape
                  + '|\\\\[^ux0-9]'  // other escape sequence
                  + '|\\(\\?[:!=]'  // start of a non-capturing group
                  + '|[\\(\\)\\^]'  // start/end of a group, or line start
                  + '|[^\\x5B\\x5C\\(\\)\\^]+'  // run of other characters
                  + ')',
                  'g'));
          var n = parts.length;
      
          // Maps captured group numbers to the number they will occupy in
          // the output or to -1 if that has not been determined, or to
          // undefined if they need not be capturing in the output.
          var capturedGroups = [];
      
          // Walk over and identify back references to build the capturedGroups
          // mapping.
          for (var i = 0, groupIndex = 0; i < n; ++i) {
            var p = parts[i];
            if (p === '(') {
              // groups are 1-indexed, so max group index is count of '('
              ++groupIndex;
            } else if ('\\' === p.charAt(0)) {
              var decimalValue = +p.substring(1);
              if (decimalValue) {
                if (decimalValue <= groupIndex) {
                  capturedGroups[decimalValue] = -1;
                } else {
                  // Replace with an unambiguous escape sequence so that
                  // an octal escape sequence does not turn into a backreference
                  // to a capturing group from an earlier regex.
                  parts[i] = encodeEscape(decimalValue);
                }
              }
            }
          }
      
          // Renumber groups and reduce capturing groups to non-capturing groups
          // where possible.
          for (var i = 1; i < capturedGroups.length; ++i) {
            if (-1 === capturedGroups[i]) {
              capturedGroups[i] = ++capturedGroupIndex;
            }
          }
          for (var i = 0, groupIndex = 0; i < n; ++i) {
            var p = parts[i];
            if (p === '(') {
              ++groupIndex;
              if (!capturedGroups[groupIndex]) {
                parts[i] = '(?:';
              }
            } else if ('\\' === p.charAt(0)) {
              var decimalValue = +p.substring(1);
              if (decimalValue && decimalValue <= groupIndex) {
                parts[i] = '\\' + capturedGroups[decimalValue];
              }
            }
          }
      
          // Remove any prefix anchors so that the output will match anywhere.
          // ^^ really does mean an anchored match though.
          for (var i = 0; i < n; ++i) {
            if ('^' === parts[i] && '^' !== parts[i + 1]) { parts[i] = ''; }
          }
      
          // Expand letters to groups to handle mixing of case-sensitive and
          // case-insensitive patterns if necessary.
          if (regex.ignoreCase && needToFoldCase) {
            for (var i = 0; i < n; ++i) {
              var p = parts[i];
              var ch0 = p.charAt(0);
              if (p.length >= 2 && ch0 === '[') {
                parts[i] = caseFoldCharset(p);
              } else if (ch0 !== '\\') {
                // TODO: handle letters in numeric escapes.
                parts[i] = p.replace(
                    /[a-zA-Z]/g,
                    function (ch) {
                      var cc = ch.charCodeAt(0);
                      return '[' + String.fromCharCode(cc & ~32, cc | 32) + ']';
                    });
              }
            }
          }
      
          return parts.join('');
        }
      
        var rewritten = [];
        for (var i = 0, n = regexs.length; i < n; ++i) {
          var regex = regexs[i];
          if (regex.global || regex.multiline) { throw new Error('' + regex); }
          rewritten.push(
              '(?:' + allowAnywhereFoldCaseAndRenumberGroups(regex) + ')');
        }
      
        return new RegExp(rewritten.join('|'), ignoreCase ? 'gi' : 'g');
      }
    
      /**
       * Split markup into a string of source code and an array mapping ranges in
       * that string to the text nodes in which they appear.
       *
       * <p>
       * The HTML DOM structure:</p>
       * <pre>
       * (Element   "p"
       *   (Element "b"
       *     (Text  "print "))       ; #1
       *   (Text    "'Hello '")      ; #2
       *   (Element "br")            ; #3
       *   (Text    "  + 'World';")) ; #4
       * </pre>
       * <p>
       * corresponds to the HTML
       * {@code <p><b>print </b>'Hello '<br>  + 'World';</p>}.</p>
       *
       * <p>
       * It will produce the output:</p>
       * <pre>
       * {
       *   sourceCode: "print 'Hello '\n  + 'World';",
       *   //                     1          2
       *   //           012345678901234 5678901234567
       *   spans: [0, #1, 6, #2, 14, #3, 15, #4]
       * }
       * </pre>
       * <p>
       * where #1 is a reference to the {@code "print "} text node above, and so
       * on for the other text nodes.
       * </p>
       *
       * <p>
       * The {@code} spans array is an array of pairs.  Even elements are the start
       * indices of substrings, and odd elements are the text nodes (or BR elements)
       * that contain the text for those substrings.
       * Substrings continue until the next index or the end of the source.
       * </p>
       *
       * @param {Node} node an HTML DOM subtree containing source-code.
       * @param {boolean} isPreformatted true if white-space in text nodes should
       *    be considered significant.
       * @return {Object} source code and the text nodes in which they occur.
       */
      function extractSourceSpans(node, isPreformatted) {
        var nocode = /(?:^|\s)nocode(?:\s|$)/;
      
        var chunks = [];
        var length = 0;
        var spans = [];
        var k = 0;
      
        function walk(node) {
          var type = node.nodeType;
          if (type == 1) {  // Element
            if (nocode.test(node.className)) { return; }
            for (var child = node.firstChild; child; child = child.nextSibling) {
              walk(child);
            }
            var nodeName = node.nodeName.toLowerCase();
            if ('br' === nodeName || 'li' === nodeName) {
              chunks[k] = '\n';
              spans[k << 1] = length++;
              spans[(k++ << 1) | 1] = node;
            }
          } else if (type == 3 || type == 4) {  // Text
            var text = node.nodeValue;
            if (text.length) {
              if (!isPreformatted) {
                text = text.replace(/[ \t\r\n]+/g, ' ');
              } else {
                text = text.replace(/\r\n?/g, '\n');  // Normalize newlines.
              }
              // TODO: handle tabs here?
              chunks[k] = text;
              spans[k << 1] = length;
              length += text.length;
              spans[(k++ << 1) | 1] = node;
            }
          }
        }
      
        walk(node);
      
        return {
          sourceCode: chunks.join('').replace(/\n$/, ''),
          spans: spans
        };
      }
    
      /**
       * Apply the given language handler to sourceCode and add the resulting
       * decorations to out.
       * @param {number} basePos the index of sourceCode within the chunk of source
       *    whose decorations are already present on out.
       */
      function appendDecorations(basePos, sourceCode, langHandler, out) {
        if (!sourceCode) { return; }
        var job = {
          sourceCode: sourceCode,
          basePos: basePos
        };
        langHandler(job);
        out.push.apply(out, job.decorations);
      }
    
      var notWs = /\S/;
    
      /**
       * Given an element, if it contains only one child element and any text nodes
       * it contains contain only space characters, return the sole child element.
       * Otherwise returns undefined.
       * <p>
       * This is meant to return the CODE element in {@code <pre><code ...>} when
       * there is a single child element that contains all the non-space textual
       * content, but not to return anything where there are multiple child elements
       * as in {@code <pre><code>...</code><code>...</code></pre>} or when there
       * is textual content.
       */
      function childContentWrapper(element) {
        var wrapper = undefined;
        for (var c = element.firstChild; c; c = c.nextSibling) {
          var type = c.nodeType;
          wrapper = (type === 1)  // Element Node
              ? (wrapper ? element : c)
              : (type === 3)  // Text Node
              ? (notWs.test(c.nodeValue) ? element : wrapper)
              : wrapper;
        }
        return wrapper === element ? undefined : wrapper;
      }
    
      /** Given triples of [style, pattern, context] returns a lexing function,
        * The lexing function interprets the patterns to find token boundaries and
        * returns a decoration list of the form
        * [index_0, style_0, index_1, style_1, ..., index_n, style_n]
        * where index_n is an index into the sourceCode, and style_n is a style
        * constant like PR_PLAIN.  index_n-1 <= index_n, and style_n-1 applies to
        * all characters in sourceCode[index_n-1:index_n].
        *
        * The stylePatterns is a list whose elements have the form
        * [style : string, pattern : RegExp, DEPRECATED, shortcut : string].
        *
        * Style is a style constant like PR_PLAIN, or can be a string of the
        * form 'lang-FOO', where FOO is a language extension describing the
        * language of the portion of the token in $1 after pattern executes.
        * E.g., if style is 'lang-lisp', and group 1 contains the text
        * '(hello (world))', then that portion of the token will be passed to the
        * registered lisp handler for formatting.
        * The text before and after group 1 will be restyled using this decorator
        * so decorators should take care that this doesn't result in infinite
        * recursion.  For example, the HTML lexer rule for SCRIPT elements looks
        * something like ['lang-js', /<[s]cript>(.+?)<\/script>/].  This may match
        * '<script>foo()<\/script>', which would cause the current decorator to
        * be called with '<script>' which would not match the same rule since
        * group 1 must not be empty, so it would be instead styled as PR_TAG by
        * the generic tag rule.  The handler registered for the 'js' extension would
        * then be called with 'foo()', and finally, the current decorator would
        * be called with '<\/script>' which would not match the original rule and
        * so the generic tag rule would identify it as a tag.
        *
        * Pattern must only match prefixes, and if it matches a prefix, then that
        * match is considered a token with the same style.
        *
        * Context is applied to the last non-whitespace, non-comment token
        * recognized.
        *
        * Shortcut is an optional string of characters, any of which, if the first
        * character, gurantee that this pattern and only this pattern matches.
        *
        * @param {Array} shortcutStylePatterns patterns that always start with
        *   a known character.  Must have a shortcut string.
        * @param {Array} fallthroughStylePatterns patterns that will be tried in
        *   order if the shortcut ones fail.  May have shortcuts.
        *
        * @return {function (Object)} a
        *   function that takes source code and returns a list of decorations.
        */
      function createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns) {
        var shortcuts = {};
        var tokenizer;
        (function () {
          var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns);
          var allRegexs = [];
          var regexKeys = {};
          for (var i = 0, n = allPatterns.length; i < n; ++i) {
            var patternParts = allPatterns[i];
            var shortcutChars = patternParts[3];
            if (shortcutChars) {
              for (var c = shortcutChars.length; --c >= 0;) {
                shortcuts[shortcutChars.charAt(c)] = patternParts;
              }
            }
            var regex = patternParts[1];
            var k = '' + regex;
            if (!regexKeys.hasOwnProperty(k)) {
              allRegexs.push(regex);
              regexKeys[k] = null;
            }
          }
          allRegexs.push(/[\0-\uffff]/);
          tokenizer = combinePrefixPatterns(allRegexs);
        })();
    
        var nPatterns = fallthroughStylePatterns.length;
    
        /**
         * Lexes job.sourceCode and produces an output array job.decorations of
         * style classes preceded by the position at which they start in
         * job.sourceCode in order.
         *
         * @param {Object} job an object like <pre>{
         *    sourceCode: {string} sourceText plain text,
         *    basePos: {int} position of job.sourceCode in the larger chunk of
         *        sourceCode.
         * }</pre>
         */
        var decorate = function (job) {
          var sourceCode = job.sourceCode, basePos = job.basePos;
          /** Even entries are positions in source in ascending order.  Odd enties
            * are style markers (e.g., PR_COMMENT) that run from that position until
            * the end.
            * @type {Array.<number|string>}
            */
          var decorations = [basePos, PR_PLAIN];
          var pos = 0;  // index into sourceCode
          var tokens = sourceCode.match(tokenizer) || [];
          var styleCache = {};
    
          for (var ti = 0, nTokens = tokens.length; ti < nTokens; ++ti) {
            var token = tokens[ti];
            var style = styleCache[token];
            var match = void 0;
    
            var isEmbedded;
            if (typeof style === 'string') {
              isEmbedded = false;
            } else {
              var patternParts = shortcuts[token.charAt(0)];
              if (patternParts) {
                match = token.match(patternParts[1]);
                style = patternParts[0];
              } else {
                for (var i = 0; i < nPatterns; ++i) {
                  patternParts = fallthroughStylePatterns[i];
                  match = token.match(patternParts[1]);
                  if (match) {
                    style = patternParts[0];
                    break;
                  }
                }
    
                if (!match) {  // make sure that we make progress
                  style = PR_PLAIN;
                }
              }
    
              isEmbedded = style.length >= 5 && 'lang-' === style.substring(0, 5);
              if (isEmbedded && !(match && typeof match[1] === 'string')) {
                isEmbedded = false;
                style = PR_SOURCE;
              }
    
              if (!isEmbedded) { styleCache[token] = style; }
            }
    
            var tokenStart = pos;
            pos += token.length;
    
            if (!isEmbedded) {
              decorations.push(basePos + tokenStart, style);
            } else {  // Treat group 1 as an embedded block of source code.
              var embeddedSource = match[1];
              var embeddedSourceStart = token.indexOf(embeddedSource);
              var embeddedSourceEnd = embeddedSourceStart + embeddedSource.length;
              if (match[2]) {
                // If embeddedSource can be blank, then it would match at the
                // beginning which would cause us to infinitely recurse on the
                // entire token, so we catch the right context in match[2].
                embeddedSourceEnd = token.length - match[2].length;
                embeddedSourceStart = embeddedSourceEnd - embeddedSource.length;
              }
              var lang = style.substring(5);
              // Decorate the left of the embedded source
              appendDecorations(
                  basePos + tokenStart,
                  token.substring(0, embeddedSourceStart),
                  decorate, decorations);
              // Decorate the embedded source
              appendDecorations(
                  basePos + tokenStart + embeddedSourceStart,
                  embeddedSource,
                  langHandlerForExtension(lang, embeddedSource),
                  decorations);
              // Decorate the right of the embedded section
              appendDecorations(
                  basePos + tokenStart + embeddedSourceEnd,
                  token.substring(embeddedSourceEnd),
                  decorate, decorations);
            }
          }
          job.decorations = decorations;
        };
        return decorate;
      }
    
      /** returns a function that produces a list of decorations from source text.
        *
        * This code treats ", ', and ` as string delimiters, and \ as a string
        * escape.  It does not recognize perl's qq() style strings.
        * It has no special handling for double delimiter escapes as in basic, or
        * the tripled delimiters used in python, but should work on those regardless
        * although in those cases a single string literal may be broken up into
        * multiple adjacent string literals.
        *
        * It recognizes C, C++, and shell style comments.
        *
        * @param {Object} options a set of optional parameters.
        * @return {function (Object)} a function that examines the source code
        *     in the input job and builds the decoration list.
        */
      function sourceDecorator(options) {
        var shortcutStylePatterns = [], fallthroughStylePatterns = [];
        if (options['tripleQuotedStrings']) {
          // '''multi-line-string''', 'single-line-string', and double-quoted
          shortcutStylePatterns.push(
              [PR_STRING,  /^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,
               null, '\'"']);
        } else if (options['multiLineStrings']) {
          // 'multi-line-string', "multi-line-string"
          shortcutStylePatterns.push(
              [PR_STRING,  /^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,
               null, '\'"`']);
        } else {
          // 'single-line-string', "single-line-string"
          shortcutStylePatterns.push(
              [PR_STRING,
               /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,
               null, '"\'']);
        }
        if (options['verbatimStrings']) {
          // verbatim-string-literal production from the C# grammar.  See issue 93.
          fallthroughStylePatterns.push(
              [PR_STRING, /^@\"(?:[^\"]|\"\")*(?:\"|$)/, null]);
        }
        var hc = options['hashComments'];
        if (hc) {
          if (options['cStyleComments']) {
            if (hc > 1) {  // multiline hash comments
              shortcutStylePatterns.push(
                  [PR_COMMENT, /^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/, null, '#']);
            } else {
              // Stop C preprocessor declarations at an unclosed open comment
              shortcutStylePatterns.push(
                  [PR_COMMENT, /^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\r\n]*)/,
                   null, '#']);
            }
            // #include <stdio.h>
            fallthroughStylePatterns.push(
                [PR_STRING,
                 /^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/,
                 null]);
          } else {
            shortcutStylePatterns.push([PR_COMMENT, /^#[^\r\n]*/, null, '#']);
          }
        }
        if (options['cStyleComments']) {
          fallthroughStylePatterns.push([PR_COMMENT, /^\/\/[^\r\n]*/, null]);
          fallthroughStylePatterns.push(
              [PR_COMMENT, /^\/\*[\s\S]*?(?:\*\/|$)/, null]);
        }
        var regexLiterals = options['regexLiterals'];
        if (regexLiterals) {
          /**
           * @const
           */
          var regexExcls = regexLiterals > 1
            ? ''  // Multiline regex literals
            : '\n\r';
          /**
           * @const
           */
          var regexAny = regexExcls ? '.' : '[\\S\\s]';
          /**
           * @const
           */
          var REGEX_LITERAL = (
              // A regular expression literal starts with a slash that is
              // not followed by * or / so that it is not confused with
              // comments.
              '/(?=[^/*' + regexExcls + '])'
              // and then contains any number of raw characters,
              + '(?:[^/\\x5B\\x5C' + regexExcls + ']'
              // escape sequences (\x5C),
              +    '|\\x5C' + regexAny
              // or non-nesting character sets (\x5B\x5D);
              +    '|\\x5B(?:[^\\x5C\\x5D' + regexExcls + ']'
              +             '|\\x5C' + regexAny + ')*(?:\\x5D|$))+'
              // finally closed by a /.
              + '/');
          fallthroughStylePatterns.push(
              ['lang-regex',
               RegExp('^' + REGEXP_PRECEDER_PATTERN + '(' + REGEX_LITERAL + ')')
               ]);
        }
    
        var types = options['types'];
        if (types) {
          fallthroughStylePatterns.push([PR_TYPE, types]);
        }
    
        var keywords = ("" + options['keywords']).replace(/^ | $/g, '');
        if (keywords.length) {
          fallthroughStylePatterns.push(
              [PR_KEYWORD,
               new RegExp('^(?:' + keywords.replace(/[\s,]+/g, '|') + ')\\b'),
               null]);
        }
    
        shortcutStylePatterns.push([PR_PLAIN,       /^\s+/, null, ' \r\n\t\xA0']);
    
        var punctuation =
          // The Bash man page says
    
          // A word is a sequence of characters considered as a single
          // unit by GRUB. Words are separated by metacharacters,
          // which are the following plus space, tab, and newline: { }
          // | & $ ; < >
          // ...
          
          // A word beginning with # causes that word and all remaining
          // characters on that line to be ignored.
    
          // which means that only a '#' after /(?:^|[{}|&$;<>\s])/ starts a
          // comment but empirically
          // $ echo {#}
          // {#}
          // $ echo \$#
          // $#
          // $ echo }#
          // }#
    
          // so /(?:^|[|&;<>\s])/ is more appropriate.
    
          // http://gcc.gnu.org/onlinedocs/gcc-2.95.3/cpp_1.html#SEC3
          // suggests that this definition is compatible with a
          // default mode that tries to use a single token definition
          // to recognize both bash/python style comments and C
          // preprocessor directives.
    
          // This definition of punctuation does not include # in the list of
          // follow-on exclusions, so # will not be broken before if preceeded
          // by a punctuation character.  We could try to exclude # after
          // [|&;<>] but that doesn't seem to cause many major problems.
          // If that does turn out to be a problem, we should change the below
          // when hc is truthy to include # in the run of punctuation characters
          // only when not followint [|&;<>].
          '^.[^\\s\\w.$@\'"`/\\\\]*';
        if (options['regexLiterals']) {
          punctuation += '(?!\s*\/)';
        }
    
        fallthroughStylePatterns.push(
            // TODO(mikesamuel): recognize non-latin letters and numerals in idents
            [PR_LITERAL,     /^@[a-z_$][a-z_$@0-9]*/i, null],
            [PR_TYPE,        /^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/, null],
            [PR_PLAIN,       /^[a-z_$][a-z_$@0-9]*/i, null],
            [PR_LITERAL,
             new RegExp(
                 '^(?:'
                 // A hex number
                 + '0x[a-f0-9]+'
                 // or an octal or decimal number,
                 + '|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)'
                 // possibly in scientific notation
                 + '(?:e[+\\-]?\\d+)?'
                 + ')'
                 // with an optional modifier like UL for unsigned long
                 + '[a-z]*', 'i'),
             null, '0123456789'],
            // Don't treat escaped quotes in bash as starting strings.
            // See issue 144.
            [PR_PLAIN,       /^\\[\s\S]?/, null],
            [PR_PUNCTUATION, new RegExp(punctuation), null]);
    
        return createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns);
      }
    
      var decorateSource = sourceDecorator({
            'keywords': ALL_KEYWORDS,
            'hashComments': true,
            'cStyleComments': true,
            'multiLineStrings': true,
            'regexLiterals': true
          });
    
      /**
       * Given a DOM subtree, wraps it in a list, and puts each line into its own
       * list item.
       *
       * @param {Node} node modified in place.  Its content is pulled into an
       *     HTMLOListElement, and each line is moved into a separate list item.
       *     This requires cloning elements, so the input might not have unique
       *     IDs after numbering.
       * @param {boolean} isPreformatted true iff white-space in text nodes should
       *     be treated as significant.
       */
      function numberLines(node, opt_startLineNum, isPreformatted) {
        var nocode = /(?:^|\s)nocode(?:\s|$)/;
        var lineBreak = /\r\n?|\n/;
      
        var document = node.ownerDocument;
      
        var li = document.createElement('li');
        while (node.firstChild) {
          li.appendChild(node.firstChild);
        }
        // An array of lines.  We split below, so this is initialized to one
        // un-split line.
        var listItems = [li];
      
        function walk(node) {
          var type = node.nodeType;
          if (type == 1 && !nocode.test(node.className)) {  // Element
            if ('br' === node.nodeName) {
              breakAfter(node);
              // Discard the <BR> since it is now flush against a </LI>.
              if (node.parentNode) {
                node.parentNode.removeChild(node);
              }
            } else {
              for (var child = node.firstChild; child; child = child.nextSibling) {
                walk(child);
              }
            }
          } else if ((type == 3 || type == 4) && isPreformatted) {  // Text
            var text = node.nodeValue;
            var match = text.match(lineBreak);
            if (match) {
              var firstLine = text.substring(0, match.index);
              node.nodeValue = firstLine;
              var tail = text.substring(match.index + match[0].length);
              if (tail) {
                var parent = node.parentNode;
                parent.insertBefore(
                  document.createTextNode(tail), node.nextSibling);
              }
              breakAfter(node);
              if (!firstLine) {
                // Don't leave blank text nodes in the DOM.
                node.parentNode.removeChild(node);
              }
            }
          }
        }
      
        // Split a line after the given node.
        function breakAfter(lineEndNode) {
          // If there's nothing to the right, then we can skip ending the line
          // here, and move root-wards since splitting just before an end-tag
          // would require us to create a bunch of empty copies.
          while (!lineEndNode.nextSibling) {
            lineEndNode = lineEndNode.parentNode;
            if (!lineEndNode) { return; }
          }
      
          function breakLeftOf(limit, copy) {
            // Clone shallowly if this node needs to be on both sides of the break.
            var rightSide = copy ? limit.cloneNode(false) : limit;
            var parent = limit.parentNode;
            if (parent) {
              // We clone the parent chain.
              // This helps us resurrect important styling elements that cross lines.
              // E.g. in <i>Foo<br>Bar</i>
              // should be rewritten to <li><i>Foo</i></li><li><i>Bar</i></li>.
              var parentClone = breakLeftOf(parent, 1);
              // Move the clone and everything to the right of the original
              // onto the cloned parent.
              var next = limit.nextSibling;
              parentClone.appendChild(rightSide);
              for (var sibling = next; sibling; sibling = next) {
                next = sibling.nextSibling;
                parentClone.appendChild(sibling);
              }
            }
            return rightSide;
          }
      
          var copiedListItem = breakLeftOf(lineEndNode.nextSibling, 0);
      
          // Walk the parent chain until we reach an unattached LI.
          for (var parent;
               // Check nodeType since IE invents document fragments.
               (parent = copiedListItem.parentNode) && parent.nodeType === 1;) {
            copiedListItem = parent;
          }
          // Put it on the list of lines for later processing.
          listItems.push(copiedListItem);
        }
      
        // Split lines while there are lines left to split.
        for (var i = 0;  // Number of lines that have been split so far.
             i < listItems.length;  // length updated by breakAfter calls.
             ++i) {
          walk(listItems[i]);
        }
      
        // Make sure numeric indices show correctly.
        if (opt_startLineNum === (opt_startLineNum|0)) {
          listItems[0].setAttribute('value', opt_startLineNum);
        }
      
        var ol = document.createElement('ol');
        ol.className = 'linenums';
        var offset = Math.max(0, ((opt_startLineNum - 1 /* zero index */)) | 0) || 0;
        for (var i = 0, n = listItems.length; i < n; ++i) {
          li = listItems[i];
          // Stick a class on the LIs so that stylesheets can
          // color odd/even rows, or any other row pattern that
          // is co-prime with 10.
          li.className = 'L' + ((i + offset) % 10);
          if (!li.firstChild) {
            li.appendChild(document.createTextNode('\xA0'));
          }
          ol.appendChild(li);
        }
      
        node.appendChild(ol);
      }
      /**
       * Breaks {@code job.sourceCode} around style boundaries in
       * {@code job.decorations} and modifies {@code job.sourceNode} in place.
       * @param {Object} job like <pre>{
       *    sourceCode: {string} source as plain text,
       *    sourceNode: {HTMLElement} the element containing the source,
       *    spans: {Array.<number|Node>} alternating span start indices into source
       *       and the text node or element (e.g. {@code <BR>}) corresponding to that
       *       span.
       *    decorations: {Array.<number|string} an array of style classes preceded
       *       by the position at which they start in job.sourceCode in order
       * }</pre>
       * @private
       */
      function recombineTagsAndDecorations(job) {
        var isIE8OrEarlier = /\bMSIE\s(\d+)/.exec(navigator.userAgent);
        isIE8OrEarlier = isIE8OrEarlier && +isIE8OrEarlier[1] <= 8;
        var newlineRe = /\n/g;
      
        var source = job.sourceCode;
        var sourceLength = source.length;
        // Index into source after the last code-unit recombined.
        var sourceIndex = 0;
      
        var spans = job.spans;
        var nSpans = spans.length;
        // Index into spans after the last span which ends at or before sourceIndex.
        var spanIndex = 0;
      
        var decorations = job.decorations;
        var nDecorations = decorations.length;
        // Index into decorations after the last decoration which ends at or before
        // sourceIndex.
        var decorationIndex = 0;
      
        // Remove all zero-length decorations.
        decorations[nDecorations] = sourceLength;
        var decPos, i;
        for (i = decPos = 0; i < nDecorations;) {
          if (decorations[i] !== decorations[i + 2]) {
            decorations[decPos++] = decorations[i++];
            decorations[decPos++] = decorations[i++];
          } else {
            i += 2;
          }
        }
        nDecorations = decPos;
      
        // Simplify decorations.
        for (i = decPos = 0; i < nDecorations;) {
          var startPos = decorations[i];
          // Conflate all adjacent decorations that use the same style.
          var startDec = decorations[i + 1];
          var end = i + 2;
          while (end + 2 <= nDecorations && decorations[end + 1] === startDec) {
            end += 2;
          }
          decorations[decPos++] = startPos;
          decorations[decPos++] = startDec;
          i = end;
        }
      
        nDecorations = decorations.length = decPos;
      
        var sourceNode = job.sourceNode;
        var oldDisplay;
        if (sourceNode) {
          oldDisplay = sourceNode.style.display;
          sourceNode.style.display = 'none';
        }
        try {
          var decoration = null;
          while (spanIndex < nSpans) {
            var spanStart = spans[spanIndex];
            var spanEnd = spans[spanIndex + 2] || sourceLength;
      
            var decEnd = decorations[decorationIndex + 2] || sourceLength;
      
            var end = Math.min(spanEnd, decEnd);
      
            var textNode = spans[spanIndex + 1];
            var styledText;
            if (textNode.nodeType !== 1  // Don't muck with <BR>s or <LI>s
                // Don't introduce spans around empty text nodes.
                && (styledText = source.substring(sourceIndex, end))) {
              // This may seem bizarre, and it is.  Emitting LF on IE causes the
              // code to display with spaces instead of line breaks.
              // Emitting Windows standard issue linebreaks (CRLF) causes a blank
              // space to appear at the beginning of every line but the first.
              // Emitting an old Mac OS 9 line separator makes everything spiffy.
              if (isIE8OrEarlier) {
                styledText = styledText.replace(newlineRe, '\r');
              }
              textNode.nodeValue = styledText;
              var document = textNode.ownerDocument;
              var span = document.createElement('span');
              span.className = decorations[decorationIndex + 1];
              var parentNode = textNode.parentNode;
              parentNode.replaceChild(span, textNode);
              span.appendChild(textNode);
              if (sourceIndex < spanEnd) {  // Split off a text node.
                spans[spanIndex + 1] = textNode
                    // TODO: Possibly optimize by using '' if there's no flicker.
                    = document.createTextNode(source.substring(end, spanEnd));
                parentNode.insertBefore(textNode, span.nextSibling);
              }
            }
      
            sourceIndex = end;
      
            if (sourceIndex >= spanEnd) {
              spanIndex += 2;
            }
            if (sourceIndex >= decEnd) {
              decorationIndex += 2;
            }
          }
        } finally {
          if (sourceNode) {
            sourceNode.style.display = oldDisplay;
          }
        }
      }
    
      /** Maps language-specific file extensions to handlers. */
      var langHandlerRegistry = {};
      /** Register a language handler for the given file extensions.
        * @param {function (Object)} handler a function from source code to a list
        *      of decorations.  Takes a single argument job which describes the
        *      state of the computation.   The single parameter has the form
        *      {@code {
        *        sourceCode: {string} as plain text.
        *        decorations: {Array.<number|string>} an array of style classes
        *                     preceded by the position at which they start in
        *                     job.sourceCode in order.
        *                     The language handler should assigned this field.
        *        basePos: {int} the position of source in the larger source chunk.
        *                 All positions in the output decorations array are relative
        *                 to the larger source chunk.
        *      } }
        * @param {Array.<string>} fileExtensions
        */
      function registerLangHandler(handler, fileExtensions) {
        for (var i = fileExtensions.length; --i >= 0;) {
          var ext = fileExtensions[i];
          if (!langHandlerRegistry.hasOwnProperty(ext)) {
            langHandlerRegistry[ext] = handler;
          } else if (win['console']) {
            console['warn']('cannot override language handler %s', ext);
          }
        }
      }
      function langHandlerForExtension(extension, source) {
        if (!(extension && langHandlerRegistry.hasOwnProperty(extension))) {
          // Treat it as markup if the first non whitespace character is a < and
          // the last non-whitespace character is a >.
          extension = /^\s*</.test(source)
              ? 'default-markup'
              : 'default-code';
        }
        return langHandlerRegistry[extension];
      }
      registerLangHandler(decorateSource, ['default-code']);
      registerLangHandler(
          createSimpleLexer(
              [],
              [
               [PR_PLAIN,       /^[^<?]+/],
               [PR_DECLARATION, /^<!\w[^>]*(?:>|$)/],
               [PR_COMMENT,     /^<\!--[\s\S]*?(?:-\->|$)/],
               // Unescaped content in an unknown language
               ['lang-',        /^<\?([\s\S]+?)(?:\?>|$)/],
               ['lang-',        /^<%([\s\S]+?)(?:%>|$)/],
               [PR_PUNCTUATION, /^(?:<[%?]|[%?]>)/],
               ['lang-',        /^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],
               // Unescaped content in javascript.  (Or possibly vbscript).
               ['lang-js',      /^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],
               // Contains unescaped stylesheet content
               ['lang-css',     /^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],
               ['lang-in.tag',  /^(<\/?[a-z][^<>]*>)/i]
              ]),
          ['default-markup', 'htm', 'html', 'mxml', 'xhtml', 'xml', 'xsl']);
      registerLangHandler(
          createSimpleLexer(
              [
               [PR_PLAIN,        /^[\s]+/, null, ' \t\r\n'],
               [PR_ATTRIB_VALUE, /^(?:\"[^\"]*\"?|\'[^\']*\'?)/, null, '\"\'']
               ],
              [
               [PR_TAG,          /^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],
               [PR_ATTRIB_NAME,  /^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],
               ['lang-uq.val',   /^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],
               [PR_PUNCTUATION,  /^[=<>\/]+/],
               ['lang-js',       /^on\w+\s*=\s*\"([^\"]+)\"/i],
               ['lang-js',       /^on\w+\s*=\s*\'([^\']+)\'/i],
               ['lang-js',       /^on\w+\s*=\s*([^\"\'>\s]+)/i],
               ['lang-css',      /^style\s*=\s*\"([^\"]+)\"/i],
               ['lang-css',      /^style\s*=\s*\'([^\']+)\'/i],
               ['lang-css',      /^style\s*=\s*([^\"\'>\s]+)/i]
               ]),
          ['in.tag']);
      registerLangHandler(
          createSimpleLexer([], [[PR_ATTRIB_VALUE, /^[\s\S]+/]]), ['uq.val']);
      registerLangHandler(sourceDecorator({
              'keywords': CPP_KEYWORDS,
              'hashComments': true,
              'cStyleComments': true,
              'types': C_TYPES
            }), ['c', 'cc', 'cpp', 'cxx', 'cyc', 'm']);
      registerLangHandler(sourceDecorator({
              'keywords': 'null,true,false'
            }), ['json']);
      registerLangHandler(sourceDecorator({
              'keywords': CSHARP_KEYWORDS,
              'hashComments': true,
              'cStyleComments': true,
              'verbatimStrings': true,
              'types': C_TYPES
            }), ['cs']);
      registerLangHandler(sourceDecorator({
              'keywords': JAVA_KEYWORDS,
              'cStyleComments': true
            }), ['java']);
      registerLangHandler(sourceDecorator({
              'keywords': SH_KEYWORDS,
              'hashComments': true,
              'multiLineStrings': true
            }), ['bash', 'bsh', 'csh', 'sh']);
      registerLangHandler(sourceDecorator({
              'keywords': PYTHON_KEYWORDS,
              'hashComments': true,
              'multiLineStrings': true,
              'tripleQuotedStrings': true
            }), ['cv', 'py', 'python']);
      registerLangHandler(sourceDecorator({
              'keywords': PERL_KEYWORDS,
              'hashComments': true,
              'multiLineStrings': true,
              'regexLiterals': 2  // multiline regex literals
            }), ['perl', 'pl', 'pm']);
      registerLangHandler(sourceDecorator({
              'keywords': RUBY_KEYWORDS,
              'hashComments': true,
              'multiLineStrings': true,
              'regexLiterals': true
            }), ['rb', 'ruby']);
      registerLangHandler(sourceDecorator({
              'keywords': JSCRIPT_KEYWORDS,
              'cStyleComments': true,
              'regexLiterals': true
            }), ['javascript', 'js']);
      registerLangHandler(sourceDecorator({
              'keywords': COFFEE_KEYWORDS,
              'hashComments': 3,  // ### style block comments
              'cStyleComments': true,
              'multilineStrings': true,
              'tripleQuotedStrings': true,
              'regexLiterals': true
            }), ['coffee']);
      registerLangHandler(sourceDecorator({
              'keywords': RUST_KEYWORDS,
              'cStyleComments': true,
              'multilineStrings': true
            }), ['rc', 'rs', 'rust']);
      registerLangHandler(
          createSimpleLexer([], [[PR_STRING, /^[\s\S]+/]]), ['regex']);
    
      function applyDecorator(job) {
        var opt_langExtension = job.langExtension;
    
        try {
          // Extract tags, and convert the source code to plain text.
          var sourceAndSpans = extractSourceSpans(job.sourceNode, job.pre);
          /** Plain text. @type {string} */
          var source = sourceAndSpans.sourceCode;
          job.sourceCode = source;
          job.spans = sourceAndSpans.spans;
          job.basePos = 0;
    
          // Apply the appropriate language handler
          langHandlerForExtension(opt_langExtension, source)(job);
    
          // Integrate the decorations and tags back into the source code,
          // modifying the sourceNode in place.
          recombineTagsAndDecorations(job);
        } catch (e) {
          if (win['console']) {
            console['log'](e && e['stack'] || e);
          }
        }
      }
    
      /**
       * Pretty print a chunk of code.
       * @param sourceCodeHtml {string} The HTML to pretty print.
       * @param opt_langExtension {string} The language name to use.
       *     Typically, a filename extension like 'cpp' or 'java'.
       * @param opt_numberLines {number|boolean} True to number lines,
       *     or the 1-indexed number of the first line in sourceCodeHtml.
       */
      function $prettyPrintOne(sourceCodeHtml, opt_langExtension, opt_numberLines) {
        var container = document.createElement('div');
        // This could cause images to load and onload listeners to fire.
        // E.g. <img onerror="alert(1337)" src="nosuchimage.png">.
        // We assume that the inner HTML is from a trusted source.
        // The pre-tag is required for IE8 which strips newlines from innerHTML
        // when it is injected into a <pre> tag.
        // http://stackoverflow.com/questions/451486/pre-tag-loses-line-breaks-when-setting-innerhtml-in-ie
        // http://stackoverflow.com/questions/195363/inserting-a-newline-into-a-pre-tag-ie-javascript
        container.innerHTML = '<pre>' + sourceCodeHtml + '</pre>';
        container = container.firstChild;
        if (opt_numberLines) {
          numberLines(container, opt_numberLines, true);
        }
    
        var job = {
          langExtension: opt_langExtension,
          numberLines: opt_numberLines,
          sourceNode: container,
          pre: 1
        };
        applyDecorator(job);
        return container.innerHTML;
      }
    
       /**
        * Find all the {@code <pre>} and {@code <code>} tags in the DOM with
        * {@code class=prettyprint} and prettify them.
        *
        * @param {Function} opt_whenDone called when prettifying is done.
        * @param {HTMLElement|HTMLDocument} opt_root an element or document
        *   containing all the elements to pretty print.
        *   Defaults to {@code document.body}.
        */
      function $prettyPrint(opt_whenDone, opt_root) {
        var root = opt_root || document.body;
        var doc = root.ownerDocument || document;
        function byTagName(tn) { return root.getElementsByTagName(tn); }
        // fetch a list of nodes to rewrite
        var codeSegments = [byTagName('pre'), byTagName('code'), byTagName('xmp')];
        var elements = [];
        for (var i = 0; i < codeSegments.length; ++i) {
          for (var j = 0, n = codeSegments[i].length; j < n; ++j) {
            elements.push(codeSegments[i][j]);
          }
        }
        codeSegments = null;
    
        var clock = Date;
        if (!clock['now']) {
          clock = { 'now': function () { return +(new Date); } };
        }
    
        // The loop is broken into a series of continuations to make sure that we
        // don't make the browser unresponsive when rewriting a large page.
        var k = 0;
        var prettyPrintingJob;
    
        var langExtensionRe = /\blang(?:uage)?-([\w.]+)(?!\S)/;
        var prettyPrintRe = /\bprettyprint\b/;
        var prettyPrintedRe = /\bprettyprinted\b/;
        var preformattedTagNameRe = /pre|xmp/i;
        var codeRe = /^code$/i;
        var preCodeXmpRe = /^(?:pre|code|xmp)$/i;
        var EMPTY = {};
    
        function doWork() {
          var endTime = (win['PR_SHOULD_USE_CONTINUATION'] ?
                         clock['now']() + 250 /* ms */ :
                         Infinity);
          for (; k < elements.length && clock['now']() < endTime; k++) {
            var cs = elements[k];
    
            // Look for a preceding comment like
            // <?prettify lang="..." linenums="..."?>
            var attrs = EMPTY;
            {
              for (var preceder = cs; (preceder = preceder.previousSibling);) {
                var nt = preceder.nodeType;
                // <?foo?> is parsed by HTML 5 to a comment node (8)
                // like <!--?foo?-->, but in XML is a processing instruction
                var value = (nt === 7 || nt === 8) && preceder.nodeValue;
                if (value
                    ? !/^\??prettify\b/.test(value)
                    : (nt !== 3 || /\S/.test(preceder.nodeValue))) {
                  // Skip over white-space text nodes but not others.
                  break;
                }
                if (value) {
                  attrs = {};
                  value.replace(
                      /\b(\w+)=([\w:.%+-]+)/g,
                    function (_, name, value) { attrs[name] = value; });
                  break;
                }
              }
            }
    
            var className = cs.className;
            if ((attrs !== EMPTY || prettyPrintRe.test(className))
                // Don't redo this if we've already done it.
                // This allows recalling pretty print to just prettyprint elements
                // that have been added to the page since last call.
                && !prettyPrintedRe.test(className)) {
    
              // make sure this is not nested in an already prettified element
              var nested = false;
              for (var p = cs.parentNode; p; p = p.parentNode) {
                var tn = p.tagName;
                if (preCodeXmpRe.test(tn)
                    && p.className && prettyPrintRe.test(p.className)) {
                  nested = true;
                  break;
                }
              }
              if (!nested) {
                // Mark done.  If we fail to prettyprint for whatever reason,
                // we shouldn't try again.
                cs.className += ' prettyprinted';
    
                // If the classes includes a language extensions, use it.
                // Language extensions can be specified like
                //     <pre class="prettyprint lang-cpp">
                // the language extension "cpp" is used to find a language handler
                // as passed to PR.registerLangHandler.
                // HTML5 recommends that a language be specified using "language-"
                // as the prefix instead.  Google Code Prettify supports both.
                // http://dev.w3.org/html5/spec-author-view/the-code-element.html
                var langExtension = attrs['lang'];
                if (!langExtension) {
                  langExtension = className.match(langExtensionRe);
                  // Support <pre class="prettyprint"><code class="language-c">
                  var wrapper;
                  if (!langExtension && (wrapper = childContentWrapper(cs))
                      && codeRe.test(wrapper.tagName)) {
                    langExtension = wrapper.className.match(langExtensionRe);
                  }
    
                  if (langExtension) { langExtension = langExtension[1]; }
                }
    
                var preformatted;
                if (preformattedTagNameRe.test(cs.tagName)) {
                  preformatted = 1;
                } else {
                  var currentStyle = cs['currentStyle'];
                  var defaultView = doc.defaultView;
                  var whitespace = (
                      currentStyle
                      ? currentStyle['whiteSpace']
                      : (defaultView
                         && defaultView.getComputedStyle)
                      ? defaultView.getComputedStyle(cs, null)
                      .getPropertyValue('white-space')
                      : 0);
                  preformatted = whitespace
                      && 'pre' === whitespace.substring(0, 3);
                }
    
                // Look for a class like linenums or linenums:<n> where <n> is the
                // 1-indexed number of the first line.
                var lineNums = attrs['linenums'];
                if (!(lineNums = lineNums === 'true' || +lineNums)) {
                  lineNums = className.match(/\blinenums\b(?::(\d+))?/);
                  lineNums =
                    lineNums
                    ? lineNums[1] && lineNums[1].length
                      ? +lineNums[1] : true
                    : false;
                }
                if (lineNums) { numberLines(cs, lineNums, preformatted); }
    
                // do the pretty printing
                prettyPrintingJob = {
                  langExtension: langExtension,
                  sourceNode: cs,
                  numberLines: lineNums,
                  pre: preformatted
                };
                applyDecorator(prettyPrintingJob);
              }
            }
          }
          if (k < elements.length) {
            // finish up in a continuation
            setTimeout(doWork, 250);
          } else if ('function' === typeof opt_whenDone) {
            opt_whenDone();
          }
        }
    
        doWork();
      }
    
      /**
       * Contains functions for creating and registering new language handlers.
       * @type {Object}
       */
      var PR = win['PR'] = {
            'createSimpleLexer': createSimpleLexer,
            'registerLangHandler': registerLangHandler,
            'sourceDecorator': sourceDecorator,
            'PR_ATTRIB_NAME': PR_ATTRIB_NAME,
            'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE,
            'PR_COMMENT': PR_COMMENT,
            'PR_DECLARATION': PR_DECLARATION,
            'PR_KEYWORD': PR_KEYWORD,
            'PR_LITERAL': PR_LITERAL,
            'PR_NOCODE': PR_NOCODE,
            'PR_PLAIN': PR_PLAIN,
            'PR_PUNCTUATION': PR_PUNCTUATION,
            'PR_SOURCE': PR_SOURCE,
            'PR_STRING': PR_STRING,
            'PR_TAG': PR_TAG,
            'PR_TYPE': PR_TYPE,
            'prettyPrintOne':
               IN_GLOBAL_SCOPE
                 ? (win['prettyPrintOne'] = $prettyPrintOne)
                 : (prettyPrintOne = $prettyPrintOne),
            'prettyPrint': prettyPrint =
               IN_GLOBAL_SCOPE
                 ? (win['prettyPrint'] = $prettyPrint)
                 : (prettyPrint = $prettyPrint)
          };
    
      // Make PR available via the Asynchronous Module Definition (AMD) API.
      // Per https://github.com/amdjs/amdjs-api/wiki/AMD:
      // The Asynchronous Module Definition (AMD) API specifies a
      // mechanism for defining modules such that the module and its
      // dependencies can be asynchronously loaded.
      // ...
      // To allow a clear indicator that a global define function (as
      // needed for script src browser loading) conforms to the AMD API,
      // any global define function SHOULD have a property called "amd"
      // whose value is an object. This helps avoid conflict with any
      // other existing JavaScript code that could have defined a define()
      // function that does not conform to the AMD API.
      if (typeof define === "function" && define['amd']) {
        define("google-code-prettify", [], function () {
          return PR; 
        });
      }
    })();
    
    var Markdown;
    
    if (typeof exports === "object" && typeof require === "function") // we're in a CommonJS (e.g. Node.js) module
        Markdown = exports;
    else
        Markdown = {};
    
    // The following text is included for historical reasons, but should
    // be taken with a pinch of salt; it's not all true anymore.
    
    //
    // Wherever possible, Showdown is a straight, line-by-line port
    // of the Perl version of Markdown.
    //
    // This is not a normal parser design; it's basically just a
    // series of string substitutions.  It's hard to read and
    // maintain this way,  but keeping Showdown close to the original
    // design makes it easier to port new features.
    //
    // More importantly, Showdown behaves like markdown.pl in most
    // edge cases.  So web applications can do client-side preview
    // in Javascript, and then build identical HTML on the server.
    //
    // This port needs the new RegExp functionality of ECMA 262,
    // 3rd Edition (i.e. Javascript 1.5).  Most modern web browsers
    // should do fine.  Even with the new regular expression features,
    // We do a lot of work to emulate Perl's regex functionality.
    // The tricky changes in this file mostly have the "attacklab:"
    // label.  Major or self-explanatory changes don't.
    //
    // Smart diff tools like Araxis Merge will be able to match up
    // this file with markdown.pl in a useful way.  A little tweaking
    // helps: in a copy of markdown.pl, replace "#" with "//" and
    // replace "$text" with "text".  Be sure to ignore whitespace
    // and line endings.
    //
    
    
    //
    // Usage:
    //
    //   var text = "Markdown *rocks*.";
    //
    //   var converter = new Markdown.Converter();
    //   var html = converter.makeHtml(text);
    //
    //   alert(html);
    //
    // Note: move the sample code to the bottom of this
    // file before uncommenting it.
    //
    
    (function () {
    
        function identity(x) { return x; }
        function returnFalse(x) { return false; }
    
        function HookCollection() { }
    
        HookCollection.prototype = {
    
            chain: function (hookname, func) {
                var original = this[hookname];
                if (!original)
                    throw new Error("unknown hook " + hookname);
    
                if (original === identity)
                    this[hookname] = func;
                else
                    this[hookname] = function (text) {
                        var args = Array.prototype.slice.call(arguments, 0);
                        args[0] = original.apply(null, args);
                        return func.apply(null, args);
                    };
            },
            set: function (hookname, func) {
                if (!this[hookname])
                    throw new Error("unknown hook " + hookname);
                this[hookname] = func;
            },
            addNoop: function (hookname) {
                this[hookname] = identity;
            },
            addFalse: function (hookname) {
                this[hookname] = returnFalse;
            }
        };
    
        Markdown.HookCollection = HookCollection;
    
        // g_urls and g_titles allow arbitrary user-entered strings as keys. This
        // caused an exception (and hence stopped the rendering) when the user entered
        // e.g. [push] or [__proto__]. Adding a prefix to the actual key prevents this
        // (since no builtin property starts with "s_"). See
        // http://meta.stackoverflow.com/questions/64655/strange-wmd-bug
        // (granted, switching from Array() to Object() alone would have left only __proto__
        // to be a problem)
        function SaveHash() { }
        SaveHash.prototype = {
            set: function (key, value) {
                this["s_" + key] = value;
            },
            get: function (key) {
                return this["s_" + key];
            }
        };
    
        Markdown.Converter = function () {
            var options = {};
            this.setOptions = function(optionsParam) {
                options = optionsParam;
            };
    
            var pluginHooks = this.hooks = new HookCollection();
    
            // given a URL that was encountered by itself (without markup), should return the link text that's to be given to this link
            pluginHooks.addNoop("plainLinkText");
    
            // called with the orignal text as given to makeHtml. The result of this plugin hook is the actual markdown source that will be cooked
            pluginHooks.addNoop("preConversion");
    
            // called with the text once all normalizations have been completed (tabs to spaces, line endings, etc.), but before any conversions have
            pluginHooks.addNoop("postNormalization");
    
            // Called with the text before / after creating block elements like code blocks and lists. Note that this is called recursively
            // with inner content, e.g. it's called with the full text, and then only with the content of a blockquote. The inner
            // call will receive outdented text.
            pluginHooks.addNoop("preBlockGamut");
            pluginHooks.addNoop("postBlockGamut");
    
            // called with the text of a single block element before / after the span-level conversions (bold, code spans, etc.) have been made
            pluginHooks.addNoop("preSpanGamut");
            pluginHooks.addNoop("postSpanGamut");
    
            // called with the final cooked HTML code. The result of this plugin hook is the actual output of makeHtml
            pluginHooks.addNoop("postConversion");
    
            //
            // Private state of the converter instance:
            //
    
            // Global hashes, used by various utility routines
            var g_urls;
            var g_titles;
            var g_html_blocks;
    
            // Used to track when we're inside an ordered or unordered list
            // (see _ProcessListItems() for details):
            var g_list_level;
    
            this.makeHtml = function (text) {
    
                //
                // Main function. The order in which other subs are called here is
                // essential. Link and image substitutions need to happen before
                // _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>
                // and <img> tags get encoded.
                //
    
                // This will only happen if makeHtml on the same converter instance is called from a plugin hook.
                // Don't do that.
                if (g_urls)
                    throw new Error("Recursive call to converter.makeHtml");
    
                // Create the private state objects.
                g_urls = new SaveHash();
                g_titles = new SaveHash();
                g_html_blocks = [];
                g_list_level = 0;
    
                text = pluginHooks.preConversion(text);
    
                // attacklab: Replace ~ with ~T
                // This lets us use tilde as an escape char to avoid md5 hashes
                // The choice of character is arbitray; anything that isn't
                // magic in Markdown will work.
                text = text.replace(/~/g, "~T");
    
                // attacklab: Replace $ with ~D
                // RegExp interprets $ as a special character
                // when it's in a replacement string
                text = text.replace(/\$/g, "~D");
    
                // Standardize line endings
                text = text.replace(/\r\n/g, "\n"); // DOS to Unix
                text = text.replace(/\r/g, "\n"); // Mac to Unix
    
                // Make sure text begins and ends with a couple of newlines:
                text = "\n\n" + text + "\n\n";
    
                // Convert all tabs to spaces.
                text = _Detab(text);
    
                // Strip any lines consisting only of spaces and tabs.
                // This makes subsequent regexen easier to write, because we can
                // match consecutive blank lines with /\n+/ instead of something
                // contorted like /[ \t]*\n+/ .
                text = text.replace(/^[ \t]+$/mg, "");
    
                text = pluginHooks.postNormalization(text);
    
                // Turn block-level HTML blocks into hash entries
                text = _HashHTMLBlocks(text);
    
                // Strip link definitions, store in hashes.
                text = _StripLinkDefinitions(text);
    
                text = _RunBlockGamut(text);
    
                text = _UnescapeSpecialChars(text);
    
                // attacklab: Restore dollar signs
                text = text.replace(/~D/g, "$$");
    
                // attacklab: Restore tildes
                text = text.replace(/~T/g, "~");
    
                text = pluginHooks.postConversion(text);
    
                g_html_blocks = g_titles = g_urls = null;
    
                return text;
            };
    
            function _StripLinkDefinitions(text) {
                //
                // Strips link definitions from text, stores the URLs and titles in
                // hash references.
                //
    
                // Link defs are in the form: ^[id]: url "optional title"
    
                /*
                text = text.replace(/
                    ^[ ]{0,3}\[(.+)\]:  // id = $1  attacklab: g_tab_width - 1
                    [ \t]*
                    \n?                 // maybe *one* newline
                    [ \t]*
                    <?(\S+?)>?          // url = $2
                    (?=\s|$)            // lookahead for whitespace instead of the lookbehind removed below
                    [ \t]*
                    \n?                 // maybe one newline
                    [ \t]*
                    (                   // (potential) title = $3
                        (\n*)           // any lines skipped = $4 attacklab: lookbehind removed
                        [ \t]+
                        ["(]
                        (.+?)           // title = $5
                        [")]
                        [ \t]*
                    )?                  // title is optional
                    (?:\n+|$)
                /gm, function(){...});
                */
    
                text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm,
                    function (wholeMatch, m1, m2, m3, m4, m5) {
                        m1 = m1.toLowerCase();
                        g_urls.set(m1, _EncodeAmpsAndAngles(m2));  // Link IDs are case-insensitive
                        if (m4) {
                            // Oops, found blank lines, so it's not a title.
                            // Put back the parenthetical statement we stole.
                            return m3;
                        } else if (m5) {
                            g_titles.set(m1, m5.replace(/"/g, "&quot;"));
                        }
    
                        // Completely remove the definition from the text
                        return "";
                    }
                );
    
                return text;
            }
    
            function _HashHTMLBlocks(text) {
    
                // Hashify HTML blocks:
                // We only want to do this for block-level HTML tags, such as headers,
                // lists, and tables. That's because we still want to wrap <p>s around
                // "paragraphs" that are wrapped in non-block-level tags, such as anchors,
                // phrase emphasis, and spans. The list of tags we're looking for is
                // hard-coded:
                var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"
                var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"
    
                // First, look for nested blocks, e.g.:
                //   <div>
                //     <div>
                //     tags for inner block must be indented.
                //     </div>
                //   </div>
                //
                // The outermost tags must start at the left margin for this to match, and
                // the inner nested divs must be indented.
                // We need to do this before the next, more liberal match, because the next
                // match will start at the first `<div>` and stop at the first `</div>`.
    
                // attacklab: This regex can be expensive when it fails.
    
                /*
                text = text.replace(/
                    (                       // save in $1
                        ^                   // start of line  (with /m)
                        <($block_tags_a)    // start tag = $2
                        \b                  // word break
                                            // attacklab: hack around khtml/pcre bug...
                        [^\r]*?\n           // any number of lines, minimally matching
                        </\2>               // the matching end tag
                        [ \t]*              // trailing spaces/tabs
                        (?=\n+)             // followed by a newline
                    )                       // attacklab: there are sentinel newlines at end of document
                /gm,function(){...}};
                */
                text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm, hashElement);
    
                //
                // Now match more liberally, simply from `\n<tag>` to `</tag>\n`
                //
    
                /*
                text = text.replace(/
                    (                       // save in $1
                        ^                   // start of line  (with /m)
                        <($block_tags_b)    // start tag = $2
                        \b                  // word break
                                            // attacklab: hack around khtml/pcre bug...
                        [^\r]*?             // any number of lines, minimally matching
                        .*</\2>             // the matching end tag
                        [ \t]*              // trailing spaces/tabs
                        (?=\n+)             // followed by a newline
                    )                       // attacklab: there are sentinel newlines at end of document
                /gm,function(){...}};
                */
                //text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm, hashElement);
    
                // The .* is highly CPU consuming and I don't know what it's for (not even in the original showdown lib)
                text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?<\/\2>[ \t]*(?=\n+)\n)/gm, hashElement);
    
                // Special case just for <hr />. It was easier to make a special case than
                // to make the other regex more complicated.
    
                /*
                text = text.replace(/
                    \n                  // Starting after a blank line
                    [ ]{0,3}
                    (                   // save in $1
                        (<(hr)          // start tag = $2
                            \b          // word break
                            ([^<>])*?
                        \/?>)           // the matching end tag
                        [ \t]*
                        (?=\n{2,})      // followed by a blank line
                    )
                /g,hashElement);
                */
                text = text.replace(/\n[ ]{0,3}((<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g, hashElement);
    
                // Special case for standalone HTML comments:
    
                /*
                text = text.replace(/
                    \n\n                                            // Starting after a blank line
                    [ ]{0,3}                                        // attacklab: g_tab_width - 1
                    (                                               // save in $1
                        <!
                        (--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)   // see http://www.w3.org/TR/html-markup/syntax.html#comments and http://meta.stackoverflow.com/q/95256
                        >
                        [ \t]*
                        (?=\n{2,})                                  // followed by a blank line
                    )
                /g,hashElement);
                */
                text = text.replace(/\n\n[ ]{0,3}(<!(--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)>[ \t]*(?=\n{2,}))/g, hashElement);
    
                // PHP and ASP-style processor instructions (<?...?> and <%...%>)
    
                /*
                text = text.replace(/
                    (?:
                        \n\n            // Starting after a blank line
                    )
                    (                   // save in $1
                        [ ]{0,3}        // attacklab: g_tab_width - 1
                        (?:
                            <([?%])     // $2
                            [^\r]*?
                            \2>
                        )
                        [ \t]*
                        (?=\n{2,})      // followed by a blank line
                    )
                /g,hashElement);
                */
                text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g, hashElement);
    
                return text;
            }
    
            function hashElement(wholeMatch, m1) {
                var blockText = m1;
    
                // Undo double lines
                blockText = blockText.replace(/^\n+/, "");
    
                // strip trailing blank lines
                blockText = blockText.replace(/\n+$/g, "");
    
                // Replace the element text with a marker ("~KxK" where x is its key)
                blockText = "\n\n~K" + (g_html_blocks.push(blockText) - 1) + "K\n\n";
    
                return blockText;
            }
    
            var blockGamutHookCallback = function (t) { return _RunBlockGamut(t); }
    
            function _RunBlockGamut(text, doNotUnhash) {
                //
                // These are all the transformations that form block-level
                // tags like paragraphs, headers, and list items.
                //
    
                text = pluginHooks.preBlockGamut(text, blockGamutHookCallback);
    
                text = _DoHeaders(text);
    
                // Do Horizontal Rules:
                var replacement = "<hr />\n";
                text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, replacement);
                text = text.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm, replacement);
                text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, replacement);
    
                text = _DoLists(text);
                text = _DoCodeBlocks(text);
                text = _DoBlockQuotes(text);
    
                text = pluginHooks.postBlockGamut(text, blockGamutHookCallback);
    
                // We already ran _HashHTMLBlocks() before, in Markdown(), but that
                // was to escape raw HTML in the original Markdown source. This time,
                // we're escaping the markup we've just created, so that we don't wrap
                // <p> tags around block-level tags.
                text = _HashHTMLBlocks(text);
                text = _FormParagraphs(text, doNotUnhash);
    
                return text;
            }
    
            function _RunSpanGamut(text) {
                //
                // These are all the transformations that occur *within* block-level
                // tags like paragraphs, headers, and list items.
                //
    
                text = pluginHooks.preSpanGamut(text);
    
                text = _DoCodeSpans(text);
                text = _EscapeSpecialCharsWithinTagAttributes(text);
                text = _EncodeBackslashEscapes(text);
    
                // Process anchor and image tags. Images must come first,
                // because ![foo][f] looks like an anchor.
                text = _DoImages(text);
                text = _DoAnchors(text);
    
                // Make links out of things like `<http://example.com/>`
                // Must come after _DoAnchors(), because you can use < and >
                // delimiters in inline links like [this](<url>).
                text = _DoAutoLinks(text);
    
                text = text.replace(/~P/g, "://"); // put in place to prevent autolinking; reset now
    
                text = _EncodeAmpsAndAngles(text);
                text = options._DoItalicsAndBold ? options._DoItalicsAndBold(text) : _DoItalicsAndBold(text);
    
                // Do hard breaks:
                text = text.replace(/  +\n/g, " <br>\n");
    
                text = pluginHooks.postSpanGamut(text);
    
                return text;
            }
    
            function _EscapeSpecialCharsWithinTagAttributes(text) {
                //
                // Within tags -- meaning between < and > -- encode [\ ` * _] so they
                // don't conflict with their use in Markdown for code, italics and strong.
                //
    
                // Build a regex to find HTML tags and comments.  See Friedl's
                // "Mastering Regular Expressions", 2nd Ed., pp. 200-201.
    
                // SE: changed the comment part of the regex
    
                var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)>)/gi;
    
                text = text.replace(regex, function (wholeMatch) {
                    var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, "$1`");
                    tag = escapeCharacters(tag, wholeMatch.charAt(1) == "!" ? "\\`*_/" : "\\`*_"); // also escape slashes in comments to prevent autolinking there -- http://meta.stackoverflow.com/questions/95987
                    return tag;
                });
    
                return text;
            }
    
            function _DoAnchors(text) {
                //
                // Turn Markdown link shortcuts into XHTML <a> tags.
                //
                //
                // First, handle reference-style links: [link text] [id]
                //
    
                /*
                text = text.replace(/
                    (                           // wrap whole match in $1
                        \[
                        (
                            (?:
                                \[[^\]]*\]      // allow brackets nested one level
                                |
                                [^\[]           // or anything else
                            )*
                        )
                        \]
    
                        [ ]?                    // one optional space
                        (?:\n[ ]*)?             // one optional newline followed by spaces
    
                        \[
                        (.*?)                   // id = $3
                        \]
                    )
                    ()()()()                    // pad remaining backreferences
                /g, writeAnchorTag);
                */
                text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeAnchorTag);
    
                //
                // Next, inline-style links: [link text](url "optional title")
                //
    
                /*
                text = text.replace(/
                    (                           // wrap whole match in $1
                        \[
                        (
                            (?:
                                \[[^\]]*\]      // allow brackets nested one level
                                |
                                [^\[\]]         // or anything else
                            )*
                        )
                        \]
                        \(                      // literal paren
                        [ \t]*
                        ()                      // no id, so leave $3 empty
                        <?(                     // href = $4
                            (?:
                                \([^)]*\)       // allow one level of (correctly nested) parens (think MSDN)
                                |
                                [^()\s]
                            )*?
                        )>?
                        [ \t]*
                        (                       // $5
                            (['"])              // quote char = $6
                            (.*?)               // Title = $7
                            \6                  // matching quote
                            [ \t]*              // ignore any spaces/tabs between closing quote and )
                        )?                      // title is optional
                        \)
                    )
                /g, writeAnchorTag);
                */
    
                text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?((?:\([^)]*\)|[^()\s])*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeAnchorTag);
    
                //
                // Last, handle reference-style shortcuts: [link text]
                // These must come last in case you've also got [link test][1]
                // or [link test](/foo)
                //
    
                /*
                text = text.replace(/
                    (                   // wrap whole match in $1
                        \[
                        ([^\[\]]+)      // link text = $2; can't contain '[' or ']'
                        \]
                    )
                    ()()()()()          // pad rest of backreferences
                /g, writeAnchorTag);
                */
                text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);
    
                return text;
            }
    
            function writeAnchorTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) {
                if (m7 == undefined) m7 = "";
                var whole_match = m1;
                var link_text = m2.replace(/:\/\//g, "~P"); // to prevent auto-linking withing the link. will be converted back after the auto-linker runs
                var link_id = m3.toLowerCase();
                var url = m4;
                var title = m7;
    
                if (url == "") {
                    if (link_id == "") {
                        // lower-case and turn embedded newlines into spaces
                        link_id = link_text.toLowerCase().replace(/ ?\n/g, " ");
                    }
                    url = "#" + link_id;
    
                    if (g_urls.get(link_id) != undefined) {
                        url = g_urls.get(link_id);
                        if (g_titles.get(link_id) != undefined) {
                            title = g_titles.get(link_id);
                        }
                    }
                    else {
                        if (whole_match.search(/\(\s*\)$/m) > -1) {
                            // Special case for explicit empty url
                            url = "";
                        } else {
                            return whole_match;
                        }
                    }
                }
                url = encodeProblemUrlChars(url);
                url = escapeCharacters(url, "*_");
                var result = "<a href=\"" + url + "\"";
    
                if (title != "") {
                    title = attributeEncode(title);
                    title = escapeCharacters(title, "*_");
                    result += " title=\"" + title + "\"";
                }
    
                result += ">" + link_text + "</a>";
    
                return result;
            }
    
            function _DoImages(text) {
                //
                // Turn Markdown image shortcuts into <img> tags.
                //
    
                //
                // First, handle reference-style labeled images: ![alt text][id]
                //
    
                /*
                text = text.replace(/
                    (                   // wrap whole match in $1
                        !\[
                        (.*?)           // alt text = $2
                        \]
    
                        [ ]?            // one optional space
                        (?:\n[ ]*)?     // one optional newline followed by spaces
    
                        \[
                        (.*?)           // id = $3
                        \]
                    )
                    ()()()()            // pad rest of backreferences
                /g, writeImageTag);
                */
                text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeImageTag);
    
                //
                // Next, handle inline images:  ![alt text](url "optional title")
                // Don't forget: encode * and _
    
                /*
                text = text.replace(/
                    (                   // wrap whole match in $1
                        !\[
                        (.*?)           // alt text = $2
                        \]
                        \s?             // One optional whitespace character
                        \(              // literal paren
                        [ \t]*
                        ()              // no id, so leave $3 empty
                        <?(\S+?)>?      // src url = $4
                        [ \t]*
                        (               // $5
                            (['"])      // quote char = $6
                            (.*?)       // title = $7
                            \6          // matching quote
                            [ \t]*
                        )?              // title is optional
                        \)
                    )
                /g, writeImageTag);
                */
                text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeImageTag);
    
                return text;
            }
    
            function attributeEncode(text) {
                // unconditionally replace angle brackets here -- what ends up in an attribute (e.g. alt or title)
                // never makes sense to have verbatim HTML in it (and the sanitizer would totally break it)
                return text.replace(/>/g, "&gt;").replace(/</g, "&lt;").replace(/"/g, "&quot;");
            }
    
            function writeImageTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) {
                var whole_match = m1;
                var alt_text = m2;
                var link_id = m3.toLowerCase();
                var url = m4;
                var title = m7;
    
                if (!title) title = "";
    
                if (url == "") {
                    if (link_id == "") {
                        // lower-case and turn embedded newlines into spaces
                        link_id = alt_text.toLowerCase().replace(/ ?\n/g, " ");
                    }
                    url = "#" + link_id;
    
                    if (g_urls.get(link_id) != undefined) {
                        url = g_urls.get(link_id);
                        if (g_titles.get(link_id) != undefined) {
                            title = g_titles.get(link_id);
                        }
                    }
                    else {
                        return whole_match;
                    }
                }
    
                alt_text = escapeCharacters(attributeEncode(alt_text), "*_[]()");
                url = escapeCharacters(url, "*_");
                var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\"";
    
                // attacklab: Markdown.pl adds empty title attributes to images.
                // Replicate this bug.
    
                //if (title != "") {
                title = attributeEncode(title);
                title = escapeCharacters(title, "*_");
                result += " title=\"" + title + "\"";
                //}
    
                result += " />";
    
                return result;
            }
    
            function _DoHeaders(text) {
    
                // Setext-style headers:
                //  Header 1
                //  ========
                //
                //  Header 2
                //  --------
                //
                text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,
                    function (wholeMatch, m1) { return "<h1>" + _RunSpanGamut(m1) + "</h1>\n\n"; }
                );
    
                text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,
                    function (matchFound, m1) { return "<h2>" + _RunSpanGamut(m1) + "</h2>\n\n"; }
                );
    
                // atx-style headers:
                //  # Header 1
                //  ## Header 2
                //  ## Header 2 with closing hashes ##
                //  ...
                //  ###### Header 6
                //
    
                /*
                text = text.replace(/
                    ^(\#{1,6})      // $1 = string of #'s
                    [ \t]*
                    (.+?)           // $2 = Header text
                    [ \t]*
                    \#*             // optional closing #'s (not counted)
                    \n+
                /gm, function() {...});
                */
    
                text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,
                    function (wholeMatch, m1, m2) {
                        var h_level = m1.length;
                        return "<h" + h_level + ">" + _RunSpanGamut(m2) + "</h" + h_level + ">\n\n";
                    }
                );
    
                return text;
            }
    
            function _DoLists(text, isInsideParagraphlessListItem) {
                //
                // Form HTML ordered (numbered) and unordered (bulleted) lists.
                //
    
                // attacklab: add sentinel to hack around khtml/safari bug:
                // http://bugs.webkit.org/show_bug.cgi?id=11231
                text += "~0";
    
                // Re-usable pattern to match any entirel ul or ol list:
    
                /*
                var whole_list = /
                    (                                   // $1 = whole list
                        (                               // $2
                            [ ]{0,3}                    // attacklab: g_tab_width - 1
                            ([*+-]|\d+[.])              // $3 = first list item marker
                            [ \t]+
                        )
                        [^\r]+?
                        (                               // $4
                            ~0                          // sentinel for workaround; should be $
                            |
                            \n{2,}
                            (?=\S)
                            (?!                         // Negative lookahead for another list item marker
                                [ \t]*
                                (?:[*+-]|\d+[.])[ \t]+
                            )
                        )
                    )
                /g
                */
                var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
    
                if (g_list_level) {
                    text = text.replace(whole_list, function (wholeMatch, m1, m2) {
                        var list = m1;
                        var list_type = (m2.search(/[*+-]/g) > -1) ? "ul" : "ol";
    
                        var result = _ProcessListItems(list, list_type, isInsideParagraphlessListItem);
    
                        // Trim any trailing whitespace, to put the closing `</$list_type>`
                        // up on the preceding line, to get it past the current stupid
                        // HTML block parser. This is a hack to work around the terrible
                        // hack that is the HTML block parser.
                        result = result.replace(/\s+$/, "");
                        result = "<" + list_type + ">" + result + "</" + list_type + ">\n";
                        return result;
                    });
                } else {
                    whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
                    text = text.replace(whole_list, function (wholeMatch, m1, m2, m3) {
                        var runup = m1;
                        var list = m2;
    
                        var list_type = (m3.search(/[*+-]/g) > -1) ? "ul" : "ol";
                        var result = _ProcessListItems(list, list_type);
                        result = runup + "<" + list_type + ">\n" + result + "</" + list_type + ">\n";
                        return result;
                    });
                }
    
                // attacklab: strip sentinel
                text = text.replace(/~0/, "");
    
                return text;
            }
    
            var _listItemMarkers = { ol: "\\d+[.]", ul: "[*+-]" };
    
            function _ProcessListItems(list_str, list_type, isInsideParagraphlessListItem) {
                //
                //  Process the contents of a single ordered or unordered list, splitting it
                //  into individual list items.
                //
                //  list_type is either "ul" or "ol".
    
                // The $g_list_level global keeps track of when we're inside a list.
                // Each time we enter a list, we increment it; when we leave a list,
                // we decrement. If it's zero, we're not in a list anymore.
                //
                // We do this because when we're not inside a list, we want to treat
                // something like this:
                //
                //    I recommend upgrading to version
                //    8. Oops, now this line is treated
                //    as a sub-list.
                //
                // As a single paragraph, despite the fact that the second line starts
                // with a digit-period-space sequence.
                //
                // Whereas when we're inside a list (or sub-list), that line will be
                // treated as the start of a sub-list. What a kludge, huh? This is
                // an aspect of Markdown's syntax that's hard to parse perfectly
                // without resorting to mind-reading. Perhaps the solution is to
                // change the syntax rules such that sub-lists must start with a
                // starting cardinal number; e.g. "1." or "a.".
    
                g_list_level++;
    
                // trim trailing blank lines:
                list_str = list_str.replace(/\n{2,}$/, "\n");
    
                // attacklab: add sentinel to emulate \z
                list_str += "~0";
    
                // In the original attacklab showdown, list_type was not given to this function, and anything
                // that matched /[*+-]|\d+[.]/ would just create the next <li>, causing this mismatch:
                //
                //  Markdown          rendered by WMD        rendered by MarkdownSharp
                //  ------------------------------------------------------------------
                //  1. first          1. first               1. first
                //  2. second         2. second              2. second
                //  - third           3. third                   * third
                //
                // We changed this to behave identical to MarkdownSharp. This is the constructed RegEx,
                // with {MARKER} being one of \d+[.] or [*+-], depending on list_type:
    
                /*
                list_str = list_str.replace(/
                    (^[ \t]*)                       // leading whitespace = $1
                    ({MARKER}) [ \t]+               // list marker = $2
                    ([^\r]+?                        // list item text   = $3
                        (\n+)
                    )
                    (?=
                        (~0 | \2 ({MARKER}) [ \t]+)
                    )
                /gm, function(){...});
                */
    
                var marker = _listItemMarkers[list_type];
                var re = new RegExp("(^[ \\t]*)(" + marker + ")[ \\t]+([^\\r]+?(\\n+))(?=(~0|\\1(" + marker + ")[ \\t]+))", "gm");
                var last_item_had_a_double_newline = false;
                list_str = list_str.replace(re,
                    function (wholeMatch, m1, m2, m3) {
                        var item = m3;
                        var leading_space = m1;
                        var ends_with_double_newline = /\n\n$/.test(item);
                        var contains_double_newline = ends_with_double_newline || item.search(/\n{2,}/) > -1;
    
                        if (contains_double_newline || last_item_had_a_double_newline) {
                            item = _RunBlockGamut(_Outdent(item), /* doNotUnhash = */true);
                        }
                        else {
                            // Recursion for sub-lists:
                            item = _DoLists(_Outdent(item), /* isInsideParagraphlessListItem= */ true);
                            item = item.replace(/\n$/, ""); // chomp(item)
                            if (!isInsideParagraphlessListItem) // only the outer-most item should run this, otherwise it's run multiple times for the inner ones
                                item = _RunSpanGamut(item);
                        }
                        last_item_had_a_double_newline = ends_with_double_newline;
                        return "<li>" + item + "</li>\n";
                    }
                );
    
                // attacklab: strip sentinel
                list_str = list_str.replace(/~0/g, "");
    
                g_list_level--;
                return list_str;
            }
    
            function _DoCodeBlocks(text) {
                //
                //  Process Markdown `<pre><code>` blocks.
                //
    
                /*
                text = text.replace(/
                    (?:\n\n|^)
                    (                               // $1 = the code block -- one or more lines, starting with a space/tab
                        (?:
                            (?:[ ]{4}|\t)           // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
                            .*\n+
                        )+
                    )
                    (\n*[ ]{0,3}[^ \t\n]|(?=~0))    // attacklab: g_tab_width
                /g ,function(){...});
                */
    
                // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
                text += "~0";
    
                text = text.replace(/(?:\n\n|^\n?)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
                    function (wholeMatch, m1, m2) {
                        var codeblock = m1;
                        var nextChar = m2;
    
                        codeblock = _EncodeCode(_Outdent(codeblock));
                        codeblock = _Detab(codeblock);
                        codeblock = codeblock.replace(/^\n+/g, ""); // trim leading newlines
                        codeblock = codeblock.replace(/\n+$/g, ""); // trim trailing whitespace
    
                        codeblock = "<pre><code>" + codeblock + "\n</code></pre>";
    
                        return "\n\n" + codeblock + "\n\n" + nextChar;
                    }
                );
    
                // attacklab: strip sentinel
                text = text.replace(/~0/, "");
    
                return text;
            }
    
            function hashBlock(text) {
                text = text.replace(/(^\n+|\n+$)/g, "");
                return "\n\n~K" + (g_html_blocks.push(text) - 1) + "K\n\n";
            }
    
            function _DoCodeSpans(text) {
                //
                // * Backtick quotes are used for <code></code> spans.
                //
                // * You can use multiple backticks as the delimiters if you want to
                //   include literal backticks in the code span. So, this input:
                //
                //      Just type ``foo `bar` baz`` at the prompt.
                //
                //   Will translate to:
                //
                //      <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
                //
                //   There's no arbitrary limit to the number of backticks you
                //   can use as delimters. If you need three consecutive backticks
                //   in your code, use four for delimiters, etc.
                //
                // * You can use spaces to get literal backticks at the edges:
                //
                //      ... type `` `bar` `` ...
                //
                //   Turns to:
                //
                //      ... type <code>`bar`</code> ...
                //
    
                /*
                text = text.replace(/
                    (^|[^\\])       // Character before opening ` can't be a backslash
                    (`+)            // $2 = Opening run of `
                    (               // $3 = The code block
                        [^\r]*?
                        [^`]        // attacklab: work around lack of lookbehind
                    )
                    \2              // Matching closer
                    (?!`)
                /gm, function(){...});
                */
    
                text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
                    function (wholeMatch, m1, m2, m3, m4) {
                        var c = m3;
                        c = c.replace(/^([ \t]*)/g, ""); // leading whitespace
                        c = c.replace(/[ \t]*$/g, ""); // trailing whitespace
                        c = _EncodeCode(c);
                        c = c.replace(/:\/\//g, "~P"); // to prevent auto-linking. Not necessary in code *blocks*, but in code spans. Will be converted back after the auto-linker runs.
                        return m1 + "<code>" + c + "</code>";
                    }
                );
    
                return text;
            }
    
            function _EncodeCode(text) {
                //
                // Encode/escape certain characters inside Markdown code runs.
                // The point is that in code, these characters are literals,
                // and lose their special Markdown meanings.
                //
                // Encode all ampersands; HTML entities are not
                // entities within a Markdown code span.
                text = text.replace(/&/g, "&amp;");
    
                // Do the angle bracket song and dance:
                text = text.replace(/</g, "&lt;");
                text = text.replace(/>/g, "&gt;");
    
                // Now, escape characters that are magic in Markdown:
                text = escapeCharacters(text, "\*_{}[]\\", false);
    
                // jj the line above breaks this:
                //---
    
                //* Item
    
                //   1. Subitem
    
                //            special char: *
                //---
    
                return text;
            }
    
            function _DoItalicsAndBold(text) {
    
                // <strong> must go first:
                text = text.replace(/([\W_]|^)(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\2([\W_]|$)/g,
                "$1<strong>$3</strong>$4");
    
                text = text.replace(/([\W_]|^)(\*|_)(?=\S)([^\r\*_]*?\S)\2([\W_]|$)/g,
                "$1<em>$3</em>$4");
    
                return text;
            }
    
            function _DoBlockQuotes(text) {
    
                /*
                text = text.replace(/
                    (                           // Wrap whole match in $1
                        (
                            ^[ \t]*>[ \t]?      // '>' at the start of a line
                            .+\n                // rest of the first line
                            (.+\n)*             // subsequent consecutive lines
                            \n*                 // blanks
                        )+
                    )
                /gm, function(){...});
                */
    
                text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,
                    function (wholeMatch, m1) {
                        var bq = m1;
    
                        // attacklab: hack around Konqueror 3.5.4 bug:
                        // "----------bug".replace(/^-/g,"") == "bug"
    
                        bq = bq.replace(/^[ \t]*>[ \t]?/gm, "~0"); // trim one level of quoting
    
                        // attacklab: clean up hack
                        bq = bq.replace(/~0/g, "");
    
                        bq = bq.replace(/^[ \t]+$/gm, "");     // trim whitespace-only lines
                        bq = _RunBlockGamut(bq);             // recurse
    
                        bq = bq.replace(/(^|\n)/g, "$1  ");
                        // These leading spaces screw with <pre> content, so we need to fix that:
                        bq = bq.replace(
                                /(\s*<pre>[^\r]+?<\/pre>)/gm,
                            function (wholeMatch, m1) {
                                var pre = m1;
                                // attacklab: hack around Konqueror 3.5.4 bug:
                                pre = pre.replace(/^  /mg, "~0");
                                pre = pre.replace(/~0/g, "");
                                return pre;
                            });
    
                        return hashBlock("<blockquote>\n" + bq + "\n</blockquote>");
                    }
                );
                return text;
            }
    
            function _FormParagraphs(text, doNotUnhash) {
                //
                //  Params:
                //    $text - string to process with html <p> tags
                //
    
                // Strip leading and trailing lines:
                text = text.replace(/^\n+/g, "");
                text = text.replace(/\n+$/g, "");
    
                var grafs = text.split(/\n{2,}/g);
                var grafsOut = [];
    
                var markerRe = /~K(\d+)K/;
    
                //
                // Wrap <p> tags.
                //
                var end = grafs.length;
                for (var i = 0; i < end; i++) {
                    var str = grafs[i];
    
                    // if this is an HTML marker, copy it
                    if (markerRe.test(str)) {
                        grafsOut.push(str);
                    }
                    else if (/\S/.test(str)) {
                        str = _RunSpanGamut(str);
                        str = str.replace(/^([ \t]*)/g, "<p>");
                        str += "</p>"
                        grafsOut.push(str);
                    }
    
                }
                //
                // Unhashify HTML blocks
                //
                if (!doNotUnhash) {
                    end = grafsOut.length;
                    for (var i = 0; i < end; i++) {
                        var foundAny = true;
                        while (foundAny) { // we may need several runs, since the data may be nested
                            foundAny = false;
                            grafsOut[i] = grafsOut[i].replace(/~K(\d+)K/g, function (wholeMatch, id) {
                                foundAny = true;
                                return g_html_blocks[id];
                            });
                        }
                    }
                }
                return grafsOut.join("\n\n");
            }
    
            function _EncodeAmpsAndAngles(text) {
                // Smart processing for ampersands and angle brackets that need to be encoded.
    
                // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
                //   http://bumppo.net/projects/amputator/
                text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, "&amp;");
    
                // Encode naked <'s
                text = text.replace(/<(?![a-z\/?!]|~D)/gi, "&lt;");
    
                return text;
            }
    
            function _EncodeBackslashEscapes(text) {
                //
                //   Parameter:  String.
                //   Returns:    The string, with after processing the following backslash
                //               escape sequences.
                //
    
                // attacklab: The polite way to do this is with the new
                // escapeCharacters() function:
                //
                //     text = escapeCharacters(text,"\\",true);
                //     text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
                //
                // ...but we're sidestepping its use of the (slow) RegExp constructor
                // as an optimization for Firefox.  This function gets called a LOT.
    
                text = text.replace(/\\(\\)/g, escapeCharacters_callback);
                text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, escapeCharacters_callback);
                return text;
            }
    
            var charInsideUrl = "[-A-Z0-9+&@#/%?=~_|[\\]()!:,.;]",
                charEndingUrl = "[-A-Z0-9+&@#/%=~_|[\\])]",
                autoLinkRegex = new RegExp("(=\"|<)?\\b(https?|ftp)(://" + charInsideUrl + "*" + charEndingUrl + ")(?=$|\\W)", "gi"),
                endCharRegex = new RegExp(charEndingUrl, "i");
    
            function handleTrailingParens(wholeMatch, lookbehind, protocol, link) {
                if (lookbehind)
                    return wholeMatch;
                if (link.charAt(link.length - 1) !== ")")
                    return "<" + protocol + link + ">";
                var parens = link.match(/[()]/g);
                var level = 0;
                for (var i = 0; i < parens.length; i++) {
                    if (parens[i] === "(") {
                        if (level <= 0)
                            level = 1;
                        else
                            level++;
                    }
                    else {
                        level--;
                    }
                }
                var tail = "";
                if (level < 0) {
                    var re = new RegExp("\\){1," + (-level) + "}$");
                    link = link.replace(re, function (trailingParens) {
                        tail = trailingParens;
                        return "";
                    });
                }
                if (tail) {
                    var lastChar = link.charAt(link.length - 1);
                    if (!endCharRegex.test(lastChar)) {
                        tail = lastChar + tail;
                        link = link.substr(0, link.length - 1);
                    }
                }
                return "<" + protocol + link + ">" + tail;
            }
    
            function _DoAutoLinks(text) {
    
                // note that at this point, all other URL in the text are already hyperlinked as <a href=""></a>
                // *except* for the <http://www.foo.com> case
    
                // automatically add < and > around unadorned raw hyperlinks
                // must be preceded by a non-word character (and not by =" or <) and followed by non-word/EOF character
                // simulating the lookbehind in a consuming way is okay here, since a URL can neither and with a " nor
                // with a <, so there is no risk of overlapping matches.
                text = text.replace(autoLinkRegex, handleTrailingParens);
    
                //  autolink anything like <http://example.com>
    
                var replacer = function (wholematch, m1) { return "<a href=\"" + m1 + "\">" + pluginHooks.plainLinkText(m1) + "</a>"; }
                text = text.replace(/<((https?|ftp):[^'">\s]+)>/gi, replacer);
    
                // Email addresses: <address@domain.foo>
                /*
                text = text.replace(/
                    <
                    (?:mailto:)?
                    (
                        [-.\w]+
                        \@
                        [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
                    )
                    >
                /gi, _DoAutoLinks_callback());
                */
    
                /* disabling email autolinking, since we don't do that on the server, either
                text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,
                    function(wholeMatch,m1) {
                        return _EncodeEmailAddress( _UnescapeSpecialChars(m1) );
                    }
                );
                */
                return text;
            }
    
            function _UnescapeSpecialChars(text) {
                //
                // Swap back in all the special characters we've hidden.
                //
                text = text.replace(/~E(\d+)E/g,
                    function (wholeMatch, m1) {
                        var charCodeToReplace = parseInt(m1);
                        return String.fromCharCode(charCodeToReplace);
                    }
                );
                return text;
            }
    
            function _Outdent(text) {
                //
                // Remove one level of line-leading tabs or spaces
                //
    
                // attacklab: hack around Konqueror 3.5.4 bug:
                // "----------bug".replace(/^-/g,"") == "bug"
    
                text = text.replace(/^(\t|[ ]{1,4})/gm, "~0"); // attacklab: g_tab_width
    
                // attacklab: clean up hack
                text = text.replace(/~0/g, "")
    
                return text;
            }
    
            function _Detab(text) {
                if (!/\t/.test(text))
                    return text;
    
                var spaces = ["    ", "   ", "  ", " "],
                skew = 0,
                v;
    
                return text.replace(/[\n\t]/g, function (match, offset) {
                    if (match === "\n") {
                        skew = offset + 1;
                        return match;
                    }
                    v = (offset - skew) % 4;
                    skew = offset + 1;
                    return spaces[v];
                });
            }
    
            //
            //  attacklab: Utility functions
            //
    
            var _problemUrlChars = /(?:["'*()[\]:]|~D)/g;
    
            // hex-encodes some unusual "problem" chars in URLs to avoid URL detection problems
            function encodeProblemUrlChars(url) {
                if (!url)
                    return "";
    
                var len = url.length;
    
                return url.replace(_problemUrlChars, function (match, offset) {
                    if (match == "~D") // escape for dollar
                        return "%24";
                    if (match == ":") {
                        //if (offset == len - 1 || /[0-9\/]/.test(url.charAt(offset + 1)))
                            return ":"
                    }
                    return "%" + match.charCodeAt(0).toString(16);
                });
            }
    
    
            function escapeCharacters(text, charsToEscape, afterBackslash) {
                // First we have to escape the escape characters so that
                // we can build a character class out of them
                var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g, "\\$1") + "])";
    
                if (afterBackslash) {
                    regexString = "\\\\" + regexString;
                }
    
                var regex = new RegExp(regexString, "g");
                text = text.replace(regex, escapeCharacters_callback);
    
                return text;
            }
    
    
            function escapeCharacters_callback(wholeMatch, m1) {
                var charCodeToEscape = m1.charCodeAt(0);
                return "~E" + charCodeToEscape + "E";
            }
    
        }; // end of the Markdown.Converter constructor
    
    })();
    
    define("libs/Markdown.Converter", function(){});
    
    (function () {
      // A quick way to make sure we're only keeping span-level tags when we need to.
      // This isn't supposed to be foolproof. It's just a quick way to make sure we
      // keep all span-level tags returned by a pagedown converter. It should allow
      // all span-level tags through, with or without attributes.
      var inlineTags = new RegExp(['^(<\\/?(a|abbr|acronym|applet|area|b|basefont|',
                                   'bdo|big|button|cite|code|del|dfn|em|figcaption|',
                                   'font|i|iframe|img|input|ins|kbd|label|map|',
                                   'mark|meter|object|param|progress|q|ruby|rp|rt|s|',
                                   'samp|script|select|small|span|strike|strong|',
                                   'sub|sup|textarea|time|tt|u|var|wbr)[^>]*>|',
                                   '<(br)\\s?\\/?>)$'].join(''), 'i');
    
      /******************************************************************
       * Utility Functions                                              *
       *****************************************************************/
    
      // patch for ie7
      if (!Array.indexOf) {
        Array.prototype.indexOf = function(obj) {
          for (var i = 0; i < this.length; i++) {
            if (this[i] == obj) {
              return i;
            }
          }
          return -1;
        };
      }
    
      function trim(str) {
        return str.replace(/^\s+|\s+$/g, '');
      }
    
      function rtrim(str) {
        return str.replace(/\s+$/g, '');
      }
    
      // Remove one level of indentation from text. Indent is 4 spaces.
      function outdent(text) {
        return text.replace(new RegExp('^(\\t|[ ]{1,4})', 'gm'), '');
      }
    
      function contains(str, substr) {
        return str.indexOf(substr) != -1;
      }
    
      // Sanitize html, removing tags that aren't in the whitelist
      function sanitizeHtml(html, whitelist) {
        return html.replace(/<[^>]*>?/gi, function(tag) {
          return tag.match(whitelist) ? tag : '';
        });
      }
    
      // Merge two arrays, keeping only unique elements.
      function union(x, y) {
        var obj = {};
        for (var i = 0; i < x.length; i++)
           obj[x[i]] = x[i];
        for (i = 0; i < y.length; i++)
           obj[y[i]] = y[i];
        var res = [];
        for (var k in obj) {
          if (obj.hasOwnProperty(k))
            res.push(obj[k]);
        }
        return res;
      }
    
      // JS regexes don't support \A or \Z, so we add sentinels, as Pagedown
      // does. In this case, we add the ascii codes for start of text (STX) and
      // end of text (ETX), an idea borrowed from:
      // https://github.com/tanakahisateru/js-markdown-extra
      function addAnchors(text) {
        if(text.charAt(0) != '\x02')
          text = '\x02' + text;
        if(text.charAt(text.length - 1) != '\x03')
          text = text + '\x03';
        return text;
      }
    
      // Remove STX and ETX sentinels.
      function removeAnchors(text) {
        if(text.charAt(0) == '\x02')
          text = text.substr(1);
        if(text.charAt(text.length - 1) == '\x03')
          text = text.substr(0, text.length - 1);
        return text;
      }
    
      // Convert markdown within an element, retaining only span-level tags
      function convertSpans(text, extra) {
        return sanitizeHtml(convertAll(text, extra), inlineTags);
      }
    
      // Convert internal markdown using the stock pagedown converter
      function convertAll(text, extra) {
        var result = extra.blockGamutHookCallback(text);
        // We need to perform these operations since we skip the steps in the converter
        result = unescapeSpecialChars(result);
        result = result.replace(/~D/g, "$$").replace(/~T/g, "~");
        result = extra.previousPostConversion(result);
        return result;
      }
    
      // Convert escaped special characters
      function processEscapesStep1(text) {
        // Markdown extra adds two escapable characters, `:` and `|`
        return text.replace(/\\\|/g, '~I').replace(/\\:/g, '~i');
      }
      function processEscapesStep2(text) {
        return text.replace(/~I/g, '|').replace(/~i/g, ':');
      }
    
      // Duplicated from PageDown converter
      function unescapeSpecialChars(text) {
        // Swap back in all the special characters we've hidden.
        text = text.replace(/~E(\d+)E/g, function(wholeMatch, m1) {
          var charCodeToReplace = parseInt(m1);
          return String.fromCharCode(charCodeToReplace);
        });
        return text;
      }
      
      function slugify(text) {
        return text.toLowerCase()
        .replace(/\s+/g, '-') // Replace spaces with -
        .replace(/[^\w\-]+/g, '') // Remove all non-word chars
        .replace(/\-\-+/g, '-') // Replace multiple - with single -
        .replace(/^-+/, '') // Trim - from start of text
        .replace(/-+$/, ''); // Trim - from end of text
      }
    
      /*****************************************************************************
       * Markdown.Extra *
       ****************************************************************************/
    
      Markdown.Extra = function() {
        // For converting internal markdown (in tables for instance).
        // This is necessary since these methods are meant to be called as
        // preConversion hooks, and the Markdown converter passed to init()
        // won't convert any markdown contained in the html tags we return.
        this.converter = null;
    
        // Stores html blocks we generate in hooks so that
        // they're not destroyed if the user is using a sanitizing converter
        this.hashBlocks = [];
        
        // Stores footnotes
        this.footnotes = {};
        this.usedFootnotes = [];
    
        // Special attribute blocks for fenced code blocks and headers enabled.
        this.attributeBlocks = false;
    
        // Fenced code block options
        this.googleCodePrettify = false;
        this.highlightJs = false;
    
        // Table options
        this.tableClass = '';
    
        this.tabWidth = 4;
      };
    
      Markdown.Extra.init = function(converter, options) {
        // Each call to init creates a new instance of Markdown.Extra so it's
        // safe to have multiple converters, with different options, on a single page
        var extra = new Markdown.Extra();
        var postNormalizationTransformations = [];
        var preBlockGamutTransformations = [];
        var postSpanGamutTransformations = [];
        var postConversionTransformations = ["unHashExtraBlocks"];
    
        options = options || {};
        options.extensions = options.extensions || ["all"];
        if (contains(options.extensions, "all")) {
          options.extensions = ["tables", "fenced_code_gfm", "def_list", "attr_list", "footnotes", "smartypants", "strikethrough", "newlines"];
        }
        preBlockGamutTransformations.push("wrapHeaders");
        if (contains(options.extensions, "attr_list")) {
          postNormalizationTransformations.push("hashFcbAttributeBlocks");
          preBlockGamutTransformations.push("hashHeaderAttributeBlocks");
          postConversionTransformations.push("applyAttributeBlocks");
          extra.attributeBlocks = true;
        }
        if (contains(options.extensions, "fenced_code_gfm")) {
          // This step will convert fcb inside list items and blockquotes
          preBlockGamutTransformations.push("fencedCodeBlocks");
          // This extra step is to prevent html blocks hashing and link definition/footnotes stripping inside fcb
          postNormalizationTransformations.push("fencedCodeBlocks");
        }
        if (contains(options.extensions, "tables")) {
          preBlockGamutTransformations.push("tables");
        }
        if (contains(options.extensions, "def_list")) {
          preBlockGamutTransformations.push("definitionLists");
        }
        if (contains(options.extensions, "footnotes")) {
          postNormalizationTransformations.push("stripFootnoteDefinitions");
          preBlockGamutTransformations.push("doFootnotes");
          postConversionTransformations.push("printFootnotes");
        }
        if (contains(options.extensions, "smartypants")) {
          postConversionTransformations.push("runSmartyPants");
        }
        if (contains(options.extensions, "strikethrough")) {
          postSpanGamutTransformations.push("strikethrough");
        }
        if (contains(options.extensions, "newlines")) {
          postSpanGamutTransformations.push("newlines");
        }
        
        converter.hooks.chain("postNormalization", function(text) {
          return extra.doTransform(postNormalizationTransformations, text) + '\n';
        });
    
        converter.hooks.chain("preBlockGamut", function(text, blockGamutHookCallback) {
          // Keep a reference to the block gamut callback to run recursively
          extra.blockGamutHookCallback = blockGamutHookCallback;
          text = processEscapesStep1(text);
          text = extra.doTransform(preBlockGamutTransformations, text) + '\n';
          text = processEscapesStep2(text);
          return text;
        });
    
        converter.hooks.chain("postSpanGamut", function(text) {
          return extra.doTransform(postSpanGamutTransformations, text);
        });
    
        // Keep a reference to the hook chain running before doPostConversion to apply on hashed extra blocks
        extra.previousPostConversion = converter.hooks.postConversion;
        converter.hooks.chain("postConversion", function(text) {
          text = extra.doTransform(postConversionTransformations, text);
          // Clear state vars that may use unnecessary memory
          extra.hashBlocks = [];
          extra.footnotes = {};
          extra.usedFootnotes = [];
          return text;
        });
    
        if ("highlighter" in options) {
          extra.googleCodePrettify = options.highlighter === 'prettify';
          extra.highlightJs = options.highlighter === 'highlight';
        }
    
        if ("table_class" in options) {
          extra.tableClass = options.table_class;
        }
    
        extra.converter = converter;
    
        // Caller usually won't need this, but it's handy for testing.
        return extra;
      };
    
      // Do transformations
      Markdown.Extra.prototype.doTransform = function(transformations, text) {
        for(var i = 0; i < transformations.length; i++)
          text = this[transformations[i]](text);
        return text;
      };
    
      // Return a placeholder containing a key, which is the block's index in the
      // hashBlocks array. We wrap our output in a <p> tag here so Pagedown won't.
      Markdown.Extra.prototype.hashExtraBlock = function(block) {
        return '\n<p>~X' + (this.hashBlocks.push(block) - 1) + 'X</p>\n';
      };
      Markdown.Extra.prototype.hashExtraInline = function(block) {
        return '~X' + (this.hashBlocks.push(block) - 1) + 'X';
      };
      
      // Replace placeholder blocks in `text` with their corresponding
      // html blocks in the hashBlocks array.
      Markdown.Extra.prototype.unHashExtraBlocks = function(text) {
        var self = this;
        function recursiveUnHash() {
          var hasHash = false;
          text = text.replace(/(?:<p>)?~X(\d+)X(?:<\/p>)?/g, function(wholeMatch, m1) {
            hasHash = true;
            var key = parseInt(m1, 10);
            return self.hashBlocks[key];
          });
          if(hasHash === true) {
            recursiveUnHash();
          }
        }
        recursiveUnHash();
        return text;
      };
      
      // Wrap headers to make sure they won't be in def lists
      Markdown.Extra.prototype.wrapHeaders = function(text) {
        function wrap(text) {
          return '\n' + text + '\n';
        }
        text = text.replace(/^.+[ \t]*\n=+[ \t]*\n+/gm, wrap);
        text = text.replace(/^.+[ \t]*\n-+[ \t]*\n+/gm, wrap);
        text = text.replace(/^\#{1,6}[ \t]*.+?[ \t]*\#*\n+/gm, wrap);
        return text;
      };
    
    
      /******************************************************************
       * Attribute Blocks                                               *
       *****************************************************************/
    
      // TODO: use sentinels. Should we just add/remove them in doConversion?
      // TODO: better matches for id / class attributes
      var attrBlock = "\\{[ \\t]*((?:[#.][-_:a-zA-Z0-9]+[ \\t]*)+)\\}";
      var hdrAttributesA = new RegExp("^(#{1,6}.*#{0,6})[ \\t]+" + attrBlock + "[ \\t]*(?:\\n|0x03)", "gm");
      var hdrAttributesB = new RegExp("^(.*)[ \\t]+" + attrBlock + "[ \\t]*\\n" +
        "(?=[\\-|=]+\\s*(?:\\n|0x03))", "gm"); // underline lookahead
      var fcbAttributes =  new RegExp("^(```[^`\\n]*)[ \\t]+" + attrBlock + "[ \\t]*\\n" +
        "(?=([\\s\\S]*?)\\n```[ \\t]*(\\n|0x03))", "gm");
          
      // Extract headers attribute blocks, move them above the element they will be
      // applied to, and hash them for later.
      Markdown.Extra.prototype.hashHeaderAttributeBlocks = function(text) {
        
        var self = this;
        function attributeCallback(wholeMatch, pre, attr) {
          return '<p>~XX' + (self.hashBlocks.push(attr) - 1) + 'XX</p>\n' + pre + "\n";
        }
    
        text = text.replace(hdrAttributesA, attributeCallback);  // ## headers
        text = text.replace(hdrAttributesB, attributeCallback);  // underline headers
        return text;
      };
      
      // Extract FCB attribute blocks, move them above the element they will be
      // applied to, and hash them for later.
      Markdown.Extra.prototype.hashFcbAttributeBlocks = function(text) {
        // TODO: use sentinels. Should we just add/remove them in doConversion?
        // TODO: better matches for id / class attributes
    
        var self = this;
        function attributeCallback(wholeMatch, pre, attr) {
          return '<p>~XX' + (self.hashBlocks.push(attr) - 1) + 'XX</p>\n' + pre + "\n";
        }
    
        return text.replace(fcbAttributes, attributeCallback);
      };
    
      Markdown.Extra.prototype.applyAttributeBlocks = function(text) {
        var self = this;
        var blockRe = new RegExp('<p>~XX(\\d+)XX</p>[\\s]*' +
                                 '(?:<(h[1-6]|pre)(?: +class="(\\S+)")?(>[\\s\\S]*?</\\2>))', "gm");
        text = text.replace(blockRe, function(wholeMatch, k, tag, cls, rest) {
          if (!tag) // no following header or fenced code block.
            return '';
    
          // get attributes list from hash
          var key = parseInt(k, 10);
          var attributes = self.hashBlocks[key];
    
          // get id
          var id = attributes.match(/#[^\s#.]+/g) || [];
          var idStr = id[0] ? ' id="' + id[0].substr(1, id[0].length - 1) + '"' : '';
    
          // get classes and merge with existing classes
          var classes = attributes.match(/\.[^\s#.]+/g) || [];
          for (var i = 0; i < classes.length; i++) // Remove leading dot
            classes[i] = classes[i].substr(1, classes[i].length - 1);
    
          var classStr = '';
          if (cls)
            classes = union(classes, [cls]);
    
          if (classes.length > 0)
            classStr = ' class="' + classes.join(' ') + '"';
    
          return "<" + tag + idStr + classStr + rest;
        });
    
        return text;
      };
    
      /******************************************************************
       * Tables                                                         *
       *****************************************************************/
    
      // Find and convert Markdown Extra tables into html.
      Markdown.Extra.prototype.tables = function(text) {
        var self = this;
    
        var leadingPipe = new RegExp(
          ['^'                         ,
           '[ ]{0,3}'                  , // Allowed whitespace
           '[|]'                       , // Initial pipe
           '(.+)\\n'                   , // $1: Header Row
    
           '[ ]{0,3}'                  , // Allowed whitespace
           '[|]([ ]*[-:]+[-| :]*)\\n'  , // $2: Separator
    
           '('                         , // $3: Table Body
             '(?:[ ]*[|].*\\n?)*'      , // Table rows
           ')',
           '(?:\\n|$)'                   // Stop at final newline
          ].join(''),
          'gm'
        );
    
        var noLeadingPipe = new RegExp(
          ['^'                         ,
           '[ ]{0,3}'                  , // Allowed whitespace
           '(\\S.*[|].*)\\n'           , // $1: Header Row
    
           '[ ]{0,3}'                  , // Allowed whitespace
           '([-:]+[ ]*[|][-| :]*)\\n'  , // $2: Separator
    
           '('                         , // $3: Table Body
             '(?:.*[|].*\\n?)*'        , // Table rows
           ')'                         ,
           '(?:\\n|$)'                   // Stop at final newline
          ].join(''),
          'gm'
        );
    
        text = text.replace(leadingPipe, doTable);
        text = text.replace(noLeadingPipe, doTable);
    
        // $1 = header, $2 = separator, $3 = body
        function doTable(match, header, separator, body, offset, string) {
          // remove any leading pipes and whitespace
          header = header.replace(/^ *[|]/m, '');
          separator = separator.replace(/^ *[|]/m, '');
          body = body.replace(/^ *[|]/gm, '');
    
          // remove trailing pipes and whitespace
          header = header.replace(/[|] *$/m, '');
          separator = separator.replace(/[|] *$/m, '');
          body = body.replace(/[|] *$/gm, '');
    
          // determine column alignments
          alignspecs = separator.split(/ *[|] */);
          align = [];
          for (var i = 0; i < alignspecs.length; i++) {
            var spec = alignspecs[i];
            if (spec.match(/^ *-+: *$/m))
              align[i] = ' align="right"';
            else if (spec.match(/^ *:-+: *$/m))
              align[i] = ' align="center"';
            else if (spec.match(/^ *:-+ *$/m))
              align[i] = ' align="left"';
            else align[i] = '';
          }
    
          // TODO: parse spans in header and rows before splitting, so that pipes
          // inside of tags are not interpreted as separators
          var headers = header.split(/ *[|] */);
          var colCount = headers.length;
    
          // build html
          var cls = self.tableClass ? ' class="' + self.tableClass + '"' : '';
          var html = ['<table', cls, '>\n', '<thead>\n', '<tr>\n'].join('');
    
          // build column headers.
          for (i = 0; i < colCount; i++) {
            var headerHtml = convertSpans(trim(headers[i]), self);
            html += ["  <th", align[i], ">", headerHtml, "</th>\n"].join('');
          }
          html += "</tr>\n</thead>\n";
    
          // build rows
          var rows = body.split('\n');
          for (i = 0; i < rows.length; i++) {
            if (rows[i].match(/^\s*$/)) // can apply to final row
              continue;
    
            // ensure number of rowCells matches colCount
            var rowCells = rows[i].split(/ *[|] */);
            var lenDiff = colCount - rowCells.length;
            for (var j = 0; j < lenDiff; j++)
              rowCells.push('');
    
            html += "<tr>\n";
            for (j = 0; j < colCount; j++) {
              var colHtml = convertSpans(trim(rowCells[j]), self);
              html += ["  <td", align[j], ">", colHtml, "</td>\n"].join('');
            }
            html += "</tr>\n";
          }
    
          html += "</table>\n";
    
          // replace html with placeholder until postConversion step
          return self.hashExtraBlock(html);
        }
    
        return text;
      };
    
    
      /******************************************************************
       * Footnotes                                                      *
       *****************************************************************/
      
      // Strip footnote, store in hashes.
      Markdown.Extra.prototype.stripFootnoteDefinitions = function(text) {
        var self = this;
    
        text = text.replace(
          /\n[ ]{0,3}\[\^(.+?)\]\:[ \t]*\n?([\s\S]*?)\n{1,2}((?=\n[ ]{0,3}\S)|$)/g,
          function(wholeMatch, m1, m2) {
            m1 = slugify(m1);
            m2 += "\n";
            m2 = m2.replace(/^[ ]{0,3}/g, "");
            self.footnotes[m1] = m2;
            return "\n";
          });
    
        return text;
      };
      
    
      // Find and convert footnotes references.
      Markdown.Extra.prototype.doFootnotes = function(text) {
        var self = this;
        if(self.isConvertingFootnote === true) {
          return text;
        }
    
        var footnoteCounter = 0;
        text = text.replace(/\[\^(.+?)\]/g, function(wholeMatch, m1) {
          var id = slugify(m1);
          var footnote = self.footnotes[id];
          if (footnote === undefined) {
            return wholeMatch;
          }
          footnoteCounter++;
          self.usedFootnotes.push(id);
          var html = '<a href="#fn:' + id + '" id="fnref:' + id
          + '" title="See footnote" class="footnote">' + footnoteCounter
          + '</a>';
          return self.hashExtraInline(html);
        });
    
        return text;
      };
    
      // Print footnotes at the end of the document
      Markdown.Extra.prototype.printFootnotes = function(text) {
        var self = this;
    
        if (self.usedFootnotes.length === 0) {
          return text;
        }
    
        text += '\n\n<div class="footnotes">\n<hr>\n<ol>\n\n';
        for(var i=0; i<self.usedFootnotes.length; i++) {
          var id = self.usedFootnotes[i];
          var footnote = self.footnotes[id];
          self.isConvertingFootnote = true;
          var formattedfootnote = convertSpans(footnote, self);
          delete self.isConvertingFootnote;
          text += '<li id="fn:'
            + id
            + '">'
            + formattedfootnote
            + ' <a href="#fnref:'
            + id
            + '" title="Return to article" class="reversefootnote">&#8617;</a></li>\n\n';
        }
        text += '</ol>\n</div>';
        return text;
      };
      
      
      /******************************************************************
      * Fenced Code Blocks  (gfm)                                       *
      ******************************************************************/
    
      // Find and convert gfm-inspired fenced code blocks into html.
      Markdown.Extra.prototype.fencedCodeBlocks = function(text) {
        function encodeCode(code) {
          code = code.replace(/&/g, "&amp;");
          code = code.replace(/</g, "&lt;");
          code = code.replace(/>/g, "&gt;");
          // These were escaped by PageDown before postNormalization 
          code = code.replace(/~D/g, "$$");
          code = code.replace(/~T/g, "~");
          return code;
        }
    
        var self = this;
        text = text.replace(/(?:^|\n)```([^`\n]*)\n([\s\S]*?)\n```[ \t]*(?=\n)/g, function(match, m1, m2) {
          var language = trim(m1), codeblock = m2;
    
          // adhere to specified options
          var preclass = self.googleCodePrettify ? ' class="prettyprint"' : '';
          var codeclass = '';
          if (language) {
            if (self.googleCodePrettify || self.highlightJs) {
              // use html5 language- class names. supported by both prettify and highlight.js
              codeclass = ' class="language-' + language + '"';
            } else {
              codeclass = ' class="' + language + '"';
            }
          }
    
          var html = ['<pre', preclass, '><code', codeclass, '>',
                      encodeCode(codeblock), '</code></pre>'].join('');
    
          // replace codeblock with placeholder until postConversion step
          return self.hashExtraBlock(html);
        });
    
        return text;
      };
    
    
      /******************************************************************
      * SmartyPants                                                     *
      ******************************************************************/
      
      Markdown.Extra.prototype.educatePants = function(text) {
        var self = this;
        var result = '';
        var blockOffset = 0;
        // Here we parse HTML in a very bad manner
        text.replace(/(?:<!--[\s\S]*?-->)|(<)([a-zA-Z1-6]+)([^\n]*?>)([\s\S]*?)(<\/\2>)/g, function(wholeMatch, m1, m2, m3, m4, m5, offset) {
          var token = text.substring(blockOffset, offset);
          result += self.applyPants(token);
          self.smartyPantsLastChar = result.substring(result.length - 1);
          blockOffset = offset + wholeMatch.length;
          if(!m1) {
            // Skip commentary
            result += wholeMatch;
            return;
          }
          // Skip special tags
          if(!/code|kbd|pre|script|noscript|iframe|math|ins|del|pre/i.test(m2)) {
            m4 = self.educatePants(m4);
          }
          else {
            self.smartyPantsLastChar = m4.substring(m4.length - 1);
          }
          result += m1 + m2 + m3 + m4 + m5;
        });
        var lastToken = text.substring(blockOffset);
        result += self.applyPants(lastToken);
        self.smartyPantsLastChar = result.substring(result.length - 1);
        return result;
      };
        
      function revertPants(wholeMatch, m1) {
        var blockText = m1;
        blockText = blockText.replace(/&\#8220;/g, "\"");
        blockText = blockText.replace(/&\#8221;/g, "\"");
        blockText = blockText.replace(/&\#8216;/g, "'");
        blockText = blockText.replace(/&\#8217;/g, "'");
        blockText = blockText.replace(/&\#8212;/g, "---");
        blockText = blockText.replace(/&\#8211;/g, "--");
        blockText = blockText.replace(/&\#8230;/g, "...");
        return blockText;
      }
      
      Markdown.Extra.prototype.applyPants = function(text) {
        // Dashes
        text = text.replace(/---/g, "&#8212;").replace(/--/g, "&#8211;");
        // Ellipses
        text = text.replace(/\.\.\./g, "&#8230;").replace(/\.\s\.\s\./g, "&#8230;");
        // Backticks
        text = text.replace(/``/g, "&#8220;").replace (/''/g, "&#8221;");
        
        if(/^'$/.test(text)) {
          // Special case: single-character ' token
          if(/\S/.test(this.smartyPantsLastChar)) {
            return "&#8217;";
          }
          return "&#8216;";
        }
        if(/^"$/.test(text)) {
          // Special case: single-character " token
          if(/\S/.test(this.smartyPantsLastChar)) {
            return "&#8221;";
          }
          return "&#8220;";
        }
    
        // Special case if the very first character is a quote
        // followed by punctuation at a non-word-break. Close the quotes by brute force:
        text = text.replace (/^'(?=[!"#\$\%'()*+,\-.\/:;<=>?\@\[\\]\^_`{|}~]\B)/, "&#8217;");
        text = text.replace (/^"(?=[!"#\$\%'()*+,\-.\/:;<=>?\@\[\\]\^_`{|}~]\B)/, "&#8221;");
    
        // Special case for double sets of quotes, e.g.:
        //   <p>He said, "'Quoted' words in a larger quote."</p>
        text = text.replace(/"'(?=\w)/g, "&#8220;&#8216;");
        text = text.replace(/'"(?=\w)/g, "&#8216;&#8220;");
    
        // Special case for decade abbreviations (the '80s):
        text = text.replace(/'(?=\d{2}s)/g, "&#8217;");
        
        // Get most opening single quotes:
        text = text.replace(/(\s|&nbsp;|--|&[mn]dash;|&\#8211;|&\#8212;|&\#x201[34];)'(?=\w)/g, "$1&#8216;");
        
        // Single closing quotes:
        text = text.replace(/([^\s\[\{\(\-])'/g, "$1&#8217;");
        text = text.replace(/'(?=\s|s\b)/g, "&#8217;");
    
        // Any remaining single quotes should be opening ones:
        text = text.replace(/'/g, "&#8216;");
        
        // Get most opening double quotes:
        text = text.replace(/(\s|&nbsp;|--|&[mn]dash;|&\#8211;|&\#8212;|&\#x201[34];)"(?=\w)/g, "$1&#8220;");
        
        // Double closing quotes:
        text = text.replace(/([^\s\[\{\(\-])"/g, "$1&#8221;");
        text = text.replace(/"(?=\s)/g, "&#8221;");
        
        // Any remaining quotes should be opening ones.
        text = text.replace(/"/ig, "&#8220;");
        return text;
      };
    
      // Find and convert markdown extra definition lists into html.
      Markdown.Extra.prototype.runSmartyPants = function(text) {
        this.smartyPantsLastChar = '';
        text = this.educatePants(text);
        // Clean everything inside html tags (some of them may have been converted due to our rough html parsing)
        text = text.replace(/(<([a-zA-Z1-6]+)\b([^\n>]*?)(\/)?>)/g, revertPants);
        return text;
      };
      
      /******************************************************************
      * Definition Lists                                                *
      ******************************************************************/
    
      // Find and convert markdown extra definition lists into html.
      Markdown.Extra.prototype.definitionLists = function(text) {
        var wholeList = new RegExp(
          ['(\\x02\\n?|\\n\\n)'          ,
           '(?:'                         ,
             '('                         , // $1 = whole list
               '('                       , // $2
                 '[ ]{0,3}'              ,
                 '((?:[ \\t]*\\S.*\\n)+)', // $3 = defined term
                 '\\n?'                  ,
                 '[ ]{0,3}:[ ]+'         , // colon starting definition
               ')'                       ,
               '([\\s\\S]+?)'            ,
               '('                       , // $4
                   '(?=\\0x03)'          , // \z
                 '|'                     ,
                   '(?='                 ,
                     '\\n{2,}'           ,
                     '(?=\\S)'           ,
                     '(?!'               , // Negative lookahead for another term
                       '[ ]{0,3}'        ,
                       '(?:\\S.*\\n)+?'  , // defined term
                       '\\n?'            ,
                       '[ ]{0,3}:[ ]+'   , // colon starting definition
                     ')'                 ,
                     '(?!'               , // Negative lookahead for another definition
                       '[ ]{0,3}:[ ]+'   , // colon starting definition
                     ')'                 ,
                   ')'                   ,
               ')'                       ,
             ')'                         ,
           ')'
          ].join(''),
          'gm'
        );
    
        var self = this;
        text = addAnchors(text);
    
        text = text.replace(wholeList, function(match, pre, list) {
          var result = trim(self.processDefListItems(list));
          result = "<dl>\n" + result + "\n</dl>";
          return pre + self.hashExtraBlock(result) + "\n\n";
        });
    
        return removeAnchors(text);
      };
    
      // Process the contents of a single definition list, splitting it
      // into individual term and definition list items.
      Markdown.Extra.prototype.processDefListItems = function(listStr) {
        var self = this;
    
        var dt = new RegExp(
          ['(\\x02\\n?|\\n\\n+)'    , // leading line
           '('                      , // definition terms = $1
             '[ ]{0,3}'             , // leading whitespace
             '(?![:][ ]|[ ])'       , // negative lookahead for a definition
                                      //   mark (colon) or more whitespace
             '(?:\\S.*\\n)+?'       , // actual term (not whitespace)
           ')'                      ,
           '(?=\\n?[ ]{0,3}:[ ])'     // lookahead for following line feed
          ].join(''),                 //   with a definition mark
          'gm'
        );
    
        var dd = new RegExp(
          ['\\n(\\n+)?'              , // leading line = $1
           '('                       , // marker space = $2
             '[ ]{0,3}'              , // whitespace before colon
             '[:][ ]+'               , // definition mark (colon)
           ')'                       ,
           '([\\s\\S]+?)'            , // definition text = $3
           '(?=\\n*'                 , // stop at next definition mark,
             '(?:'                   , // next term or end of text
               '\\n[ ]{0,3}[:][ ]|'  ,
               '<dt>|\\x03'          , // \z
             ')'                     ,
           ')'
          ].join(''),
          'gm'
        );
    
        listStr = addAnchors(listStr);
        // trim trailing blank lines:
        listStr = listStr.replace(/\n{2,}(?=\\x03)/, "\n");
    
        // Process definition terms.
        listStr = listStr.replace(dt, function(match, pre, termsStr) {
          var terms = trim(termsStr).split("\n");
          var text = '';
          for (var i = 0; i < terms.length; i++) {
            var term = terms[i];
            // process spans inside dt
            term = convertSpans(trim(term), self);
            text += "\n<dt>" + term + "</dt>";
          }
          return text + "\n";
        });
    
        // Process actual definitions.
        listStr = listStr.replace(dd, function(match, leadingLine, markerSpace, def) {
          if (leadingLine || def.match(/\n{2,}/)) {
            // replace marker with the appropriate whitespace indentation
            def = Array(markerSpace.length + 1).join(' ') + def;
            // process markdown inside definition
            // TODO?: currently doesn't apply extensions
            def = outdent(def) + "\n\n";
            def = "\n" + convertAll(def, self) + "\n";
          } else {
            // convert span-level markdown inside definition
            def = rtrim(def);
            def = convertSpans(outdent(def), self);
          }
    
          return "\n<dd>" + def + "</dd>\n";
        });
    
        return removeAnchors(listStr);
      };
    
    
      /***********************************************************
      * Strikethrough                                            *
      ************************************************************/
    
      Markdown.Extra.prototype.strikethrough = function(text) {
        // Pretty much duplicated from _DoItalicsAndBold
        return text.replace(/([\W_]|^)~T~T(?=\S)([^\r]*?\S[\*_]*)~T~T([\W_]|$)/g,
          "$1<del>$2</del>$3");
      };
    
    
      /***********************************************************
      * New lines                                                *
      ************************************************************/
    
      Markdown.Extra.prototype.newlines = function(text) {
        // We have to ignore already converted newlines and line breaks in sub-list items
        return text.replace(/(<(?:br|\/li)>)?\n/g, function(wholeMatch, previousTag) {
          return previousTag ? wholeMatch : " <br>\n";
        });
      };
      
    })();
    
    
    define("pagedownExtra", function(){});
    
    /*globals Markdown */
    define('extensions/markdownExtra',[
    	// "jquery",
    	"underscore",
    	"utils",
    	"logger",
    	"classes/Extension",
    	// "ext!html/markdownExtraSettingsBlock.html",
    	'google-code-prettify',
    	// 'highlightjs',
    	'crel',
    	'pagedownExtra'
    ], function( _, utils, logger, Extension, prettify) {
    
    	var markdownExtra = new Extension("markdownExtra", "Markdown Extra", true);
    	// markdownExtra.settingsBlock = markdownExtraSettingsBlockHTML;
    	markdownExtra.defaultConfig = {
    		extensions: [
    			"fenced_code_gfm",
    			"tables",
    			"def_list",
    			"attr_list",
    			"footnotes",
    			"smartypants",
    			"strikethrough",
    			"newlines"
    		],
    		intraword: true,
    		comments: true,
    		highlighter: "highlight"
    	};
    
    	var eventMgr;
    	markdownExtra.onEventMgrCreated = function(eventMgrParameter) {
    		eventMgr = eventMgrParameter;
    	};
    
    	var previewContentsElt;
    	markdownExtra.onReady = function() {
    		previewContentsElt = document.getElementById('preview-contents');
    	};
    
    	markdownExtra.onPagedownConfigure = function(editor) {
    		var converter = editor.getConverter();
    		var extraOptions = {
    			extensions: markdownExtra.config.extensions,
    			highlighter: "prettify"
    		};
    
    		if(markdownExtra.config.intraword === true) {
    			var converterOptions = {
    				_DoItalicsAndBold: function(text) {
    					text = text.replace(/([^\w*]|^)(\*\*|__)(?=\S)(.+?[*_]*)(?=\S)\2(?=[^\w*]|$)/g, "$1<strong>$3</strong>");
    					text = text.replace(/([^\w*]|^)(\*|_)(?=\S)(.+?)(?=\S)\2(?=[^\w*]|$)/g, "$1<em>$3</em>");
    					// Redo bold to handle _**word**_
    					text = text.replace(/([^\w*]|^)(\*\*|__)(?=\S)(.+?[*_]*)(?=\S)\2(?=[^\w*]|$)/g, "$1<strong>$3</strong>");
    					return text;
    				}
    			};
    			converter.setOptions(converterOptions);
    		}
    		if(markdownExtra.config.comments === true) {
    			converter.hooks.chain("postConversion", function(text) {
    				return text.replace(/<!--.*?-->/g, function(wholeMatch) {
    					return wholeMatch.replace(/^<!---(.+?)-?-->$/, ' <span class="comment label label-danger">$1</span> ');
    				});
    			});
    		}
    		/*
    		if(markdownExtra.config.highlighter == "highlight") {
    			var previewContentsElt = document.getElementById('preview-contents');
    			editor.hooks.chain("onPreviewRefresh", function() {
    				_.each(previewContentsElt.querySelectorAll('.prettyprint > code'), function(elt) {
    					!elt.highlighted && hljs.highlightBlock(elt);
    					elt.highlighted = true;
    				});
    			});
    		}
    		else if(markdownExtra.config.highlighter == "prettify") {
    			editor.hooks.chain("onPreviewRefresh", prettify.prettyPrint);
    		}
    		*/
    		editor.hooks.chain("onPreviewRefresh", function() {
    			$('#preview-contents pre').addClass('prettyprint linenums');
    			prettify.prettyPrint();
    		});
    		Markdown.Extra.init(converter, extraOptions);
    	};
    
    	return markdownExtra;
    });
    
    define('libs/mathjax_init',[
        "settings",
        // "ext!libs/mathjax_config.js"
    ], function(settings/*, mathjaxConfigJS*/) {
        var script = document.createElement('script');
        script.type = 'text/x-mathjax-config';
        var mathjaxConfigJS = 'MathJax.Hub.Config({\n\tskipStartupTypeset: true,\n    "HTML-CSS": {\n        preferredFont: "TeX",\n        availableFonts: [\n            "STIX",\n            "TeX"\n        ],\n        linebreaks: {\n            automatic: true\n        },\n        EqnChunk: 10,\n        imageFont: null\n    },\n    tex2jax: <%= tex2jax || \'{ inlineMath: [["$","$"],["\\\\\\\\\\\\\\\\(","\\\\\\\\\\\\\\\\)"]], displayMath: [["$$","$$"],["\\\\\\\\[","\\\\\\\\]"]], processEscapes: true }\' %>,\n    TeX: $.extend({\n        noUndefined: {\n            attributes: {\n                mathcolor: "red",\n                mathbackground: "#FFEEEE",\n                mathsize: "90%"\n            }\n        },\n        Safe: {\n            allow: {\n                URLs: "safe",\n                classes: "safe",\n                cssIDs: "safe",\n                styles: "safe",\n                fontsize: "all"\n            }\n        }\n    }, <%= tex %>),\n    messageStyle: "none"\n});\n';
        script.innerHTML = _.template(mathjaxConfigJS, {
            tex: settings.extensionSettings.mathJax ? settings.extensionSettings.mathJax.tex : 'undefined',
            tex2jax: settings.extensionSettings.mathJax ? settings.extensionSettings.mathJax.tex2jax : undefined
        });
        document.getElementsByTagName('head')[0].appendChild(script);
    });
    /*defines MathJax */
    define('extensions/mathJax',[
    	"utils",
    	"classes/Extension",
    	// "ext!html/mathJaxSettingsBlock.html",
    	"mathjax"
    ], function(utils, Extension) {
    
    	var mathJax = new Extension("mathJax", "MathJax", true);
    	// mathJax.settingsBlock = mathJaxSettingsBlockHTML;
    	mathJax.defaultConfig = {
    		tex    : "{}",
    		tex2jax: '{ inlineMath: [["$","$"],["\\\\\\\\(","\\\\\\\\)"]], displayMath: [["$$","$$"],["\\\\[","\\\\]"]], processEscapes: true }'
    	};
    
    	// mathJax.onLoadSettings = function() {
    	// 	utils.setInputValue("#input-mathjax-config-tex", mathJax.config.tex);
    	// 	utils.setInputValue("#input-mathjax-config-tex2jax", mathJax.config.tex2jax);
    	// };
    
    	// mathJax.onSaveSettings = function(newConfig, event) {
    	// 	newConfig.tex = utils.getInputJsValue("#input-mathjax-config-tex", event);
    	// 	newConfig.tex2jax = utils.getInputJsValue("#input-mathjax-config-tex2jax", event);
    	// };
    
    	/*jshint ignore:start */
    	mathJax.onPagedownConfigure = function(editorObject) {
    		preview = document.getElementById("preview-contents");
    
    		var converter = editorObject.getConverter();
    		converter.hooks.chain("preConversion", removeMath);
    		converter.hooks.chain("postConversion", replaceMath);
    	};
    
    	var afterRefreshCallback;
    	mathJax.onAsyncPreview = function(callback) {
    		afterRefreshCallback = callback;
    		UpdateMJ();
    	};
    
    	// From math.stackexchange.com...
    
    	//
    	//  The math is in blocks i through j, so
    	//    collect it into one block and clear the others.
    	//  Replace &, <, and > by named entities.
    	//  For IE, put <br> at the ends of comments since IE removes \n.
    	//  Clear the current math positions and store the index of the
    	//    math, then push the math string onto the storage array.
    	//
    	function processMath(i, j, unescape) {
    		var block = blocks.slice(i, j + 1).join("")
    			.replace(/&/g, "&amp;")
    			.replace(/</g, "&lt;")
    			.replace(/>/g, "&gt;");
    		for(HUB.Browser.isMSIE && (block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n")); j > i;)
    			blocks[j] = "", j--;
    		blocks[i] = "@@" + math.length + "@@";
    		unescape && (block = unescape(block));
    		math.push(block);
    		start = end = last = null;
    	}
    
    	function removeMath(text) {
    		start = end = last = null;
    		math = [];
    		var unescape;
    		if(/`/.test(text)) {
    			text = text.replace(/~/g, "~T").replace(/(^|[^\\])(`+)([^\n]*?[^`\n])\2(?!`)/gm, function(text) {
    				return text.replace(/\$/g, "~D")
    			});
    			unescape = function(text) {
    				return text.replace(/~([TD])/g,
    					function(match, n) {
    						return {T: "~", D: "$"}[n]
    					})
    			};
    		} else {
    			unescape = function(text) {
    				return text
    			};
    		}
    		blocks = split(text.replace(/\r\n?/g, "\n"), splitDelimiter);
    		for(var i = 1, m = blocks.length; i < m; i += 2) {
    			var block = blocks[i];
    			if("@" === block.charAt(0)) {
    				//
    				//  Things that look like our math markers will get
    				//  stored and then retrieved along with the math.
    				//
    				blocks[i] = "@@" + math.length + "@@";
    				math.push(block)
    			} else if(start) {
    				// Ignore inline maths that are actually multiline (fixes #136)
    				if(end == inline && block.charAt(0) == '\n') {
    					if(last) {
    						i = last;
    						processMath(start, i, unescape);
    					}
    					start = end = last = null;
    					braces = 0;
    				}
    				//
    				//  If we are in math, look for the end delimiter,
    				//    but don't go past double line breaks, and
    				//    and balance braces within the math.
    				//
    				else if(block === end) {
    					if(braces) {
    						last = i
    					} else {
    						processMath(start, i, unescape)
    					}
    				} else {
    					if(block.match(/\n.*\n/)) {
    						if(last) {
    							i = last;
    							processMath(start, i, unescape);
    						}
    						start = end = last = null;
    						braces = 0;
    					} else {
    						if("{" === block) {
    							braces++
    						} else {
    							"}" === block && braces && braces--
    						}
    					}
    				}
    			} else {
    				if(block === inline || "$$" === block) {
    					start = i;
    					end = block;
    					braces = 0;
    				} else {
    					if("begin" === block.substr(1, 5)) {
    						start = i;
    						end = "\\end" + block.substr(6);
    						braces = 0;
    					}
    				}
    			}
    
    		}
    		last && processMath(start, last, unescape);
    		return unescape(blocks.join(""))
    	}
    
    	//
    	//  Put back the math strings that were saved,
    	//    and clear the math array (no need to keep it around).
    	//
    	function replaceMath(text) {
    		text = text.replace(/@@(\d+)@@/g, function(match, n) {
    			return math[n]
    		});
    		math = null;
    		return text
    	}
    
    	//
    	//  This is run to restart MathJax after it has finished
    	//    the previous run (that may have been canceled)
    	//
    	function RestartMJ() {
    		pending = false;
    		HUB.cancelTypeset = false;
    		HUB.Queue([
    			"Typeset",
    			HUB,
    			preview
    		]);
    		HUB.Queue(afterRefreshCallback); //benweet
    	}
    
    	//
    	//  When the preview changes, cancel MathJax and restart,
    	//    if we haven't done that already.
    	//
    	function UpdateMJ() {
    		if(!pending /*benweet (we need to call our afterRefreshCallback) && ready */) {
    			pending = true;
    			HUB.Cancel();
    			HUB.Queue(RestartMJ);
    		}
    	}
    
    	var ready = false, pending = false, preview = null, inline = "$", blocks, start, end, last, braces, math, HUB = MathJax.Hub;
    
    	//
    	//  Runs after initial typeset
    	//
    	HUB.Queue(function() {
    		ready = true;
    		HUB.processUpdateTime = 50;
    		HUB.Config({"HTML-CSS": {EqnChunk: 10, EqnChunkFactor: 1}, SVG: {EqnChunk: 10, EqnChunkFactor: 1}})
    	});
    
    
    	/*benweet
    	 Don't hash inline math $...$ (see https://github.com/benweet/stackedit/issues/136)
    	 var u = /(\$\$?|\\(?:begin|end)\{[a-z]*\*?\}|\\[\\{}$]|[{}]|(?:\n\s*)+|@@\d+@@)/i, r;
    	 */
    
    
    	//
    	//  The pattern for math delimiters and special symbols
    	//    needed for searching for math in the page.
    	//
    	var splitDelimiter = /(\$\$?|\\(?:begin|end)\{[a-z]*\*?\}|\\[\\{}$]|[{}]|(?:\n\s*)+|@@\d+@@)/i;
    	var split;
    
    	if(3 === "aba".split(/(b)/).length) {
    		split = function(text, delimiter) {
    			return text.split(delimiter)
    		};
    	} else {
    		split = function(text, delimiter) {
    			var b = [], c;
    			if(!delimiter.global) {
    				c = delimiter.toString();
    				var d = "";
    				c = c.replace(/^\/(.*)\/([im]*)$/, function(a, c, b) {
    					d = b;
    					return c
    				});
    				delimiter = RegExp(c, d + "g")
    			}
    			for(var e = delimiter.lastIndex = 0; c = delimiter.exec(text);) {
    				b.push(text.substring(e, c.index));
    				b.push.apply(b, c.slice(1));
    				e = c.index + c[0].length;
    			}
    			b.push(text.substring(e));
    			return b
    		};
    	}
    
    	(function() {
    		var HUB = MathJax.Hub;
    		if(!HUB.Cancel) {
    			HUB.cancelTypeset = !1;
    			HUB.Register.StartupHook("HTML-CSS Jax Config", function() {
    				var HTMLCSS = MathJax.OutputJax["HTML-CSS"], TRANSLATE = HTMLCSS.Translate;
    				HTMLCSS.Augment({Translate: function(script, state) {
    					if(HUB.cancelTypeset || state.cancelled)
    						throw Error("MathJax Canceled");
    					return TRANSLATE.call(HTMLCSS, script, state)
    				}})
    			});
    			HUB.Register.StartupHook("SVG Jax Config", function() {
    				var SVG = MathJax.OutputJax.SVG, TRANSLATE = SVG.Translate;
    				SVG.Augment({Translate: function(script, state) {
    					if(HUB.cancelTypeset || state.cancelled)
    						throw Error("MathJax Canceled");
    					return TRANSLATE.call(SVG,
    						script, state)
    				}})
    			});
    			HUB.Register.StartupHook("TeX Jax Config", function() {
    				var TEX = MathJax.InputJax.TeX, TRANSLATE = TEX.Translate;
    				TEX.Augment({Translate: function(script, state) {
    					if(HUB.cancelTypeset || state.cancelled)
    						throw Error("MathJax Canceled");
    					return TRANSLATE.call(TEX, script, state)
    				}})
    			});
    			var PROCESSERROR = HUB.processError;
    			HUB.processError = function(error, state, type) {
    				if("MathJax Canceled" !== error.message)
    					return PROCESSERROR.call(HUB, error, state, type);
    				MathJax.Message.Clear(0, 0);
    				state.jaxIDs = [];
    				state.jax = {};
    				state.scripts = [];
    				state.i = state.j = 0;
    				state.cancelled = true;
    				return null
    			};
    			HUB.Cancel = function() {
    				this.cancelTypeset = true
    			}
    		}
    	})();
    	/*jshint ignore:end */
    
    	return mathJax;
    });
    define('extensions/partialRendering',[
    	"underscore",
    	"crel",
    	"extensions/markdownExtra",
    	"classes/Extension",
    	// "ext!html/partialRenderingSettingsBlock.html",
    ], function(_, crel, markdownExtra, Extension) {
    
    	var partialRendering = new Extension("partialRendering", "Partial Rendering", true);
    	// partialRendering.settingsBlock = partialRenderingSettingsBlockHTML;
    
    	var converter;
    	var doFootnotes = false;
    	var hasFootnotes = false;
    	var currentSectionList = [];
    
    	var sectionList = [];
    	var linkDefinition;
    	var sectionsToRemove = [];
    	var modifiedSections = [];
    	var insertBeforeSection;
    	var fileChanged = false;
    
    	function updateSectionList() {
    		var newSectionList = [];
    		var newLinkDefinition = '\n';
    		hasFootnotes = false;
    		_.each(currentSectionList, function(section) {
    			var text = '\n<div class="se-preview-section-delimiter"></div>\n\n' + section.text + '\n\n';
    
    			// Strip footnotes
    			if(doFootnotes) {
    				text = text.replace(/^```.*\n[\s\S]*?\n```|\n[ ]{0,3}\[\^(.+?)\]\:[ \t]*\n?([\s\S]*?)\n{1,2}((?=\n[ ]{0,3}\S)|$)/gm, function(wholeMatch, footnote) {
    					if(footnote) {
    						hasFootnotes = true;
    						newLinkDefinition += wholeMatch.replace(/^\s*\n/gm, '') + '\n';
    						return "";
    					}
    					return wholeMatch;
    				});
    			}
    
    			// Strip link definitions
    			text = text.replace(/^```.*\n[\s\S]*?\n```|^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm, function(wholeMatch, link) {
    				if(link) {
    					newLinkDefinition += wholeMatch.replace(/^\s*\n/gm, '') + '\n';
    					return "";
    				}
    				return wholeMatch;
    			});
    
    			// Add section to the newSectionList
    			newSectionList.push({
    				id: section.id,
    				text: text + '\n'
    			});
    		});
    
    		modifiedSections = [];
    		sectionsToRemove = [];
    		insertBeforeSection = undefined;
    
    		// Render everything if file or linkDefinition changed
    		if(fileChanged === true || linkDefinition != newLinkDefinition) {
    			fileChanged = false;
    			linkDefinition = newLinkDefinition;
    			sectionsToRemove = sectionList;
    			sectionList = newSectionList;
    			modifiedSections = newSectionList;
    			return;
    		}
    
    		// Find modified section starting from top
    		var leftIndex = sectionList.length;
    		_.some(sectionList, function(section, index) {
    			if(index >= newSectionList.length || section.text != newSectionList[index].text) {
    				leftIndex = index;
    				return true;
    			}
    		});
    
    		// Find modified section starting from bottom
    		var rightIndex = -sectionList.length;
    		_.some(sectionList.slice().reverse(), function(section, index) {
    			if(index >= newSectionList.length || section.text != newSectionList[newSectionList.length - index - 1].text) {
    				rightIndex = -index;
    				return true;
    			}
    		});
    
    		if(leftIndex - rightIndex > sectionList.length) {
    			// Prevent overlap
    			rightIndex = leftIndex - sectionList.length;
    		}
    
    		// Create an array composed of left unmodified, modified, right
    		// unmodified sections
    		var leftSections = sectionList.slice(0, leftIndex);
    		modifiedSections = newSectionList.slice(leftIndex, newSectionList.length + rightIndex);
    		var rightSections = sectionList.slice(sectionList.length + rightIndex, sectionList.length);
    		insertBeforeSection = _.first(rightSections);
    		sectionsToRemove = sectionList.slice(leftIndex, sectionList.length + rightIndex);
    		sectionList = leftSections.concat(modifiedSections).concat(rightSections);
    	}
    
    	var footnoteMap = {};
    	var footnoteFragment = document.createDocumentFragment();
    	// Store one footnote elt in the footnote map
    	function storeFootnote(footnoteElt) {
    		var id = footnoteElt.id.substring(3);
    		var oldFootnote = footnoteMap[id];
    		oldFootnote && footnoteFragment.removeChild(oldFootnote);
    		footnoteMap[id] = footnoteElt;
    		footnoteFragment.appendChild(footnoteElt);
    	}
    
    	var footnoteContainerElt;
    	var previewContentsElt;
    
    	function refreshSections() {
    
    		// Remove outdated sections
    		_.each(sectionsToRemove, function(section) {
    			var sectionElt = document.getElementById("wmd-preview-section-" + section.id);
    			previewContentsElt.removeChild(sectionElt);
    		});
    
    		var wmdPreviewElt = document.getElementById("wmd-preview");
    		var childNode = wmdPreviewElt.firstChild;
    
    		function createSectionElt(section) {
    			var sectionElt = crel('div', {
    				id: 'wmd-preview-section-' + section.id,
    				class: 'wmd-preview-section preview-content'
    			});
    			var isNextDelimiter = false;
    			while(childNode) {
    				var nextNode = childNode.nextSibling;
    				var isDelimiter = childNode.className == 'se-preview-section-delimiter';
    				if(isNextDelimiter === true && childNode.tagName == 'DIV' && isDelimiter) {
    					// Stop when encountered the next delimiter
    					break;
    				}
    				isNextDelimiter = true;
    				if(childNode.tagName == 'DIV' && childNode.className == 'footnotes') {
    					_.each(childNode.querySelectorAll("ol > li"), storeFootnote);
    				}
    				else {
    					isDelimiter || sectionElt.appendChild(childNode);
    				}
    				childNode = nextNode;
    			}
    			return sectionElt;
    		}
    
    		var newSectionEltList = document.createDocumentFragment();
    		_.each(modifiedSections, function(section) {
    			newSectionEltList.appendChild(createSectionElt(section));
    		});
    		wmdPreviewElt.innerHTML = '';
    		var insertBeforeElt = footnoteContainerElt;
    		if(insertBeforeSection !== undefined) {
    			insertBeforeElt = document.getElementById("wmd-preview-section-" + insertBeforeSection.id);
    		}
    		previewContentsElt.insertBefore(newSectionEltList, insertBeforeElt);
    
    		// Rewrite footnotes in the footer and update footnote numbers
    		footnoteContainerElt.innerHTML = '';
    		var usedFootnoteIds = [];
    		if(hasFootnotes === true) {
    			var footnoteElts = crel('ol');
    			_.each(previewContentsElt.querySelectorAll('a.footnote'), function(elt, index) {
    				elt.textContent = index + 1;
    				var id = elt.id.substring(6);
    				usedFootnoteIds.push(id);
    				var footnoteElt = footnoteMap[id];
    				footnoteElt && footnoteElts.appendChild(footnoteElt.cloneNode(true));
    			});
    			if(usedFootnoteIds.length > 0) {
    				// Append the whole footnotes at the end of the document
    				footnoteContainerElt.appendChild(crel('div', {
    					class: 'footnotes'
    				}, crel('hr'), footnoteElts));
    			}
    			// Keep used footnotes only in our map
    			Object.keys(footnoteMap).forEach(function(key) {
    				if(usedFootnoteIds.indexOf(key) === -1) {
    					footnoteFragment.removeChild(footnoteMap[key]);
    					delete footnoteMap[key];
    				}
    			});
    		}
    	}
    
    	partialRendering.onSectionsCreated = function(sectionListParam) {
    		currentSectionList = sectionListParam;
    	};
    
    	partialRendering.onPagedownConfigure = function(editor) {
    		converter = editor.getConverter();
    		converter.hooks.chain("preConversion", function() {
    			updateSectionList();
    			var result = _.map(modifiedSections, function(section) {
    				return section.text;
    			});
    			result.push(linkDefinition + "\n\n");
    			return result.join("");
    		});
    		editor.hooks.chain("onPreviewRefresh", function() {
    			refreshSections();
    		});
    	};
    
    	partialRendering.onInit = function() {
    		if(markdownExtra.enabled) {
    			if(_.some(markdownExtra.config.extensions, function(extension) {
    				return extension == "footnotes";
    			})) {
    				doFootnotes = true;
    			}
    		}
    	};
    
    	partialRendering.onReady = function() {
    		footnoteContainerElt = crel('div', {
    			id: 'wmd-preview-section-footnotes',
    			class: 'preview-content'
    		});
    		previewContentsElt = document.getElementById("preview-contents");
    		previewContentsElt.appendChild(footnoteContainerElt);
    	};
    
    	partialRendering.onFileSelected = function() {
    		fileChanged = true;
    	};
    
    	return partialRendering;
    });
    
    define('extensions/markdownSectionParser',[
        "underscore",
        "extensions/markdownExtra",
        "extensions/mathJax",
        "extensions/partialRendering",
        "classes/Extension",
        "crel",
    ], function(_, markdownExtra, mathJax, partialRendering, Extension, crel) {
    
        var markdownSectionParser = new Extension("markdownSectionParser", "Markdown section parser");
    
        var eventMgr;
        markdownSectionParser.onEventMgrCreated = function(eventMgrParameter) {
            eventMgr = eventMgrParameter;
        };
    
        var sectionList = [];
        var previewContentsElt;
    
        // Regexp to look for section delimiters
        var regexp = '^.+[ \\t]*\\n=+[ \\t]*\\n+|^.+[ \\t]*\\n-+[ \\t]*\\n+|^\\#{1,6}[ \\t]*.+?[ \\t]*\\#*\\n+'; // Title delimiters
        markdownSectionParser.onPagedownConfigure = function(editor) {
            if(markdownExtra.enabled) {
                if(_.some(markdownExtra.config.extensions, function(extension) {
                    return extension == "fenced_code_gfm";
                })) {
                    regexp = '^```[^`\\n]*\\n[\\s\\S]*?\\n```|' + regexp; // Fenced block delimiters
                }
            }
            if(mathJax.enabled) {
                // Math delimiter has to follow 1 empty line to be considered as a section delimiter
                regexp = '^[ \\t]*\\n\\$\\$[\\s\\S]*?\\$\\$|' + regexp; // $$ math block delimiters
                regexp = '^[ \\t]*\\n\\\\\\\\[[\\s\\S]*?\\\\\\\\]|' + regexp; // \\[ \\] math block delimiters
                regexp = '^[ \\t]*\\n\\\\?\\\\begin\\{[a-z]*\\*?\\}[\\s\\S]*?\\\\end\\{[a-z]*\\*?\\}|' + regexp; // \\begin{...} \\end{...} math block delimiters
            }
            regexp = new RegExp(regexp, 'gm');
    
            var converter = editor.getConverter();
            if(!partialRendering.enabled) {
                converter.hooks.chain("preConversion", function() {
                    return _.reduce(sectionList, function(result, section) {
                        return result + '\n<div class="se-preview-section-delimiter"></div>\n\n' + section.text + '\n\n';
                    }, '');
                });
    
                editor.hooks.chain("onPreviewRefresh", function() {
                    var wmdPreviewElt = document.getElementById("wmd-preview");
                    var childNode = wmdPreviewElt.firstChild;
                    function createSectionElt() {
                        var sectionElt = crel('div', {
                            class: 'wmd-preview-section preview-content'
                        });
                        var isNextDelimiter = false;
                        while (childNode) {
                            var nextNode = childNode.nextSibling;
                            var isDelimiter = childNode.className == 'se-preview-section-delimiter';
                            if(isNextDelimiter === true && childNode.tagName == 'DIV' && isDelimiter) {
                                // Stop when encountered the next delimiter
                                break;
                            }
                            isNextDelimiter = true;
                            isDelimiter || sectionElt.appendChild(childNode);
                            childNode = nextNode;
                        }
                        return sectionElt;
                    }
    
                    var newSectionEltList = document.createDocumentFragment();
                    sectionList.forEach(function(section) {
                        newSectionEltList.appendChild(createSectionElt(section));
                    });
                    previewContentsElt.innerHTML = '';
                    previewContentsElt.appendChild(wmdPreviewElt);
                    previewContentsElt.appendChild(newSectionEltList);
                });
            }
        };
    
        markdownSectionParser.onReady = function() {
            previewContentsElt = document.getElementById("preview-contents");
        };
    
        var fileDesc;
        markdownSectionParser.onFileSelected = function(fileDescParam) {
            fileDesc = fileDescParam;
        };
    
        var sectionCounter = 0;
        // 当内容改变时, 触发之
        // content全部内容
        function parseFileContent(fileDescParam, content) {
            if(fileDescParam !== fileDesc) {
                return;
            }
            var frontMatter = (fileDesc.frontMatter || {})._frontMatter || '';
            var text = content.substring(frontMatter.length);
            var tmpText = text + "\n\n";
            function addSection(startOffset, endOffset) {
                var sectionText = tmpText.substring(offset, endOffset);
                sectionList.push({
                    id: ++sectionCounter,
                    text: sectionText,
                    textWithFrontMatter: frontMatter + sectionText
                });
                frontMatter = '';
            }
            sectionList = [];
            var offset = 0;
            // Look for delimiters
            tmpText.replace(regexp, function(match, matchOffset) {
                // Create a new section with the text preceding the delimiter
                addSection(offset, matchOffset);
                offset = matchOffset;
            });
            // Last section
            addSection(offset, text.length);
            // 触发事件
            eventMgr.onSectionsCreated(sectionList);
        }
    
        markdownSectionParser.onFileOpen = parseFileContent;
        markdownSectionParser.onContentChanged = parseFileContent;
    
        return markdownSectionParser;
    });
    
    // ┌────────────────────────────────────────────────────────────────────┐ \\
    // │ Raphaël 2.1.1 - JavaScript Vector Library                          │ \\
    // ├────────────────────────────────────────────────────────────────────┤ \\
    // │ Copyright © 2008-2012 Dmitry Baranovskiy (http://raphaeljs.com)    │ \\
    // │ Copyright © 2008-2012 Sencha Labs (http://sencha.com)              │ \\
    // ├────────────────────────────────────────────────────────────────────┤ \\
    // │ Licensed under the MIT (http://raphaeljs.com/license.html) license.│ \\
    // └────────────────────────────────────────────────────────────────────┘ \\
    // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
    // 
    // Licensed under the Apache License, Version 2.0 (the "License");
    // you may not use this file except in compliance with the License.
    // You may obtain a copy of the License at
    // 
    // http://www.apache.org/licenses/LICENSE-2.0
    // 
    // Unless required by applicable law or agreed to in writing, software
    // distributed under the License is distributed on an "AS IS" BASIS,
    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    // See the License for the specific language governing permissions and
    // limitations under the License.
    // ┌────────────────────────────────────────────────────────────┐ \\
    // │ Eve 0.4.2 - JavaScript Events Library                      │ \\
    // ├────────────────────────────────────────────────────────────┤ \\
    // │ Author Dmitry Baranovskiy (http://dmitry.baranovskiy.com/) │ \\
    // └────────────────────────────────────────────────────────────┘ \\
    
    (function (glob) {
        var version = "0.4.2",
            has = "hasOwnProperty",
            separator = /[\.\/]/,
            wildcard = "*",
            fun = function () {},
            numsort = function (a, b) {
                return a - b;
            },
            current_event,
            stop,
            events = {n: {}},
        /*\
         * eve
         [ method ]
    
         * Fires event with given `name`, given scope and other parameters.
    
         > Arguments
    
         - name (string) name of the *event*, dot (`.`) or slash (`/`) separated
         - scope (object) context for the event handlers
         - varargs (...) the rest of arguments will be sent to event handlers
    
         = (object) array of returned values from the listeners
        \*/
            eve = function (name, scope) {
    			name = String(name);
                var e = events,
                    oldstop = stop,
                    args = Array.prototype.slice.call(arguments, 2),
                    listeners = eve.listeners(name),
                    z = 0,
                    f = false,
                    l,
                    indexed = [],
                    queue = {},
                    out = [],
                    ce = current_event,
                    errors = [];
                current_event = name;
                stop = 0;
                for (var i = 0, ii = listeners.length; i < ii; i++) if ("zIndex" in listeners[i]) {
                    indexed.push(listeners[i].zIndex);
                    if (listeners[i].zIndex < 0) {
                        queue[listeners[i].zIndex] = listeners[i];
                    }
                }
                indexed.sort(numsort);
                while (indexed[z] < 0) {
                    l = queue[indexed[z++]];
                    out.push(l.apply(scope, args));
                    if (stop) {
                        stop = oldstop;
                        return out;
                    }
                }
                for (i = 0; i < ii; i++) {
                    l = listeners[i];
                    if ("zIndex" in l) {
                        if (l.zIndex == indexed[z]) {
                            out.push(l.apply(scope, args));
                            if (stop) {
                                break;
                            }
                            do {
                                z++;
                                l = queue[indexed[z]];
                                l && out.push(l.apply(scope, args));
                                if (stop) {
                                    break;
                                }
                            } while (l)
                        } else {
                            queue[l.zIndex] = l;
                        }
                    } else {
                        out.push(l.apply(scope, args));
                        if (stop) {
                            break;
                        }
                    }
                }
                stop = oldstop;
                current_event = ce;
                return out.length ? out : null;
            };
    		// Undocumented. Debug only.
    		eve._events = events;
        /*\
         * eve.listeners
         [ method ]
    
         * Internal method which gives you array of all event handlers that will be triggered by the given `name`.
    
         > Arguments
    
         - name (string) name of the event, dot (`.`) or slash (`/`) separated
    
         = (array) array of event handlers
        \*/
        eve.listeners = function (name) {
            var names = name.split(separator),
                e = events,
                item,
                items,
                k,
                i,
                ii,
                j,
                jj,
                nes,
                es = [e],
                out = [];
            for (i = 0, ii = names.length; i < ii; i++) {
                nes = [];
                for (j = 0, jj = es.length; j < jj; j++) {
                    e = es[j].n;
                    items = [e[names[i]], e[wildcard]];
                    k = 2;
                    while (k--) {
                        item = items[k];
                        if (item) {
                            nes.push(item);
                            out = out.concat(item.f || []);
                        }
                    }
                }
                es = nes;
            }
            return out;
        };
        
        /*\
         * eve.on
         [ method ]
         **
         * Binds given event handler with a given name. You can use wildcards “`*`” for the names:
         | eve.on("*.under.*", f);
         | eve("mouse.under.floor"); // triggers f
         * Use @eve to trigger the listener.
         **
         > Arguments
         **
         - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
         - f (function) event handler function
         **
         = (function) returned function accepts a single numeric parameter that represents z-index of the handler. It is an optional feature and only used when you need to ensure that some subset of handlers will be invoked in a given order, despite of the order of assignment. 
         > Example:
         | eve.on("mouse", eatIt)(2);
         | eve.on("mouse", scream);
         | eve.on("mouse", catchIt)(1);
         * This will ensure that `catchIt()` function will be called before `eatIt()`.
    	 *
         * If you want to put your handler before non-indexed handlers, specify a negative value.
         * Note: I assume most of the time you don’t need to worry about z-index, but it’s nice to have this feature “just in case”.
        \*/
        eve.on = function (name, f) {
    		name = String(name);
    		if (typeof f != "function") {
    			return function () {};
    		}
            var names = name.split(separator),
                e = events;
            for (var i = 0, ii = names.length; i < ii; i++) {
                e = e.n;
                e = e.hasOwnProperty(names[i]) && e[names[i]] || (e[names[i]] = {n: {}});
            }
            e.f = e.f || [];
            for (i = 0, ii = e.f.length; i < ii; i++) if (e.f[i] == f) {
                return fun;
            }
            e.f.push(f);
            return function (zIndex) {
                if (+zIndex == +zIndex) {
                    f.zIndex = +zIndex;
                }
            };
        };
        /*\
         * eve.f
         [ method ]
         **
         * Returns function that will fire given event with optional arguments.
    	 * Arguments that will be passed to the result function will be also
    	 * concated to the list of final arguments.
     	 | el.onclick = eve.f("click", 1, 2);
     	 | eve.on("click", function (a, b, c) {
     	 |     console.log(a, b, c); // 1, 2, [event object]
     	 | });
         > Arguments
    	 - event (string) event name
    	 - varargs (…) and any other arguments
    	 = (function) possible event handler function
        \*/
    	eve.f = function (event) {
    		var attrs = [].slice.call(arguments, 1);
    		return function () {
    			eve.apply(null, [event, null].concat(attrs).concat([].slice.call(arguments, 0)));
    		};
    	};
        /*\
         * eve.stop
         [ method ]
         **
         * Is used inside an event handler to stop the event, preventing any subsequent listeners from firing.
        \*/
        eve.stop = function () {
            stop = 1;
        };
        /*\
         * eve.nt
         [ method ]
         **
         * Could be used inside event handler to figure out actual name of the event.
         **
         > Arguments
         **
         - subname (string) #optional subname of the event
         **
         = (string) name of the event, if `subname` is not specified
         * or
         = (boolean) `true`, if current event’s name contains `subname`
        \*/
        eve.nt = function (subname) {
            if (subname) {
                return new RegExp("(?:\\.|\\/|^)" + subname + "(?:\\.|\\/|$)").test(current_event);
            }
            return current_event;
        };
        /*\
         * eve.nts
         [ method ]
         **
         * Could be used inside event handler to figure out actual name of the event.
         **
         **
         = (array) names of the event
        \*/
        eve.nts = function () {
            return current_event.split(separator);
        };
        /*\
         * eve.off
         [ method ]
         **
         * Removes given function from the list of event listeners assigned to given name.
    	 * If no arguments specified all the events will be cleared.
         **
         > Arguments
         **
         - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
         - f (function) event handler function
        \*/
        /*\
         * eve.unbind
         [ method ]
         **
         * See @eve.off
        \*/
        eve.off = eve.unbind = function (name, f) {
    		if (!name) {
    		    eve._events = events = {n: {}};
    			return;
    		}
            var names = name.split(separator),
                e,
                key,
                splice,
                i, ii, j, jj,
                cur = [events];
            for (i = 0, ii = names.length; i < ii; i++) {
                for (j = 0; j < cur.length; j += splice.length - 2) {
                    splice = [j, 1];
                    e = cur[j].n;
                    if (names[i] != wildcard) {
                        if (e[names[i]]) {
                            splice.push(e[names[i]]);
                        }
                    } else {
                        for (key in e) if (e[has](key)) {
                            splice.push(e[key]);
                        }
                    }
                    cur.splice.apply(cur, splice);
                }
            }
            for (i = 0, ii = cur.length; i < ii; i++) {
                e = cur[i];
                while (e.n) {
                    if (f) {
                        if (e.f) {
                            for (j = 0, jj = e.f.length; j < jj; j++) if (e.f[j] == f) {
                                e.f.splice(j, 1);
                                break;
                            }
                            !e.f.length && delete e.f;
                        }
                        for (key in e.n) if (e.n[has](key) && e.n[key].f) {
                            var funcs = e.n[key].f;
                            for (j = 0, jj = funcs.length; j < jj; j++) if (funcs[j] == f) {
                                funcs.splice(j, 1);
                                break;
                            }
                            !funcs.length && delete e.n[key].f;
                        }
                    } else {
                        delete e.f;
                        for (key in e.n) if (e.n[has](key) && e.n[key].f) {
                            delete e.n[key].f;
                        }
                    }
                    e = e.n;
                }
            }
        };
        /*\
         * eve.once
         [ method ]
         **
         * Binds given event handler with a given name to only run once then unbind itself.
         | eve.once("login", f);
         | eve("login"); // triggers f
         | eve("login"); // no listeners
         * Use @eve to trigger the listener.
         **
         > Arguments
         **
         - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
         - f (function) event handler function
         **
         = (function) same return function as @eve.on
        \*/
        eve.once = function (name, f) {
            var f2 = function () {
                eve.unbind(name, f2);
                return f.apply(this, arguments);
            };
            return eve.on(name, f2);
        };
        /*\
         * eve.version
         [ property (string) ]
         **
         * Current version of the library.
        \*/
        eve.version = version;
        eve.toString = function () {
            return "You are running Eve " + version;
        };
        (typeof module != "undefined" && module.exports) ? (module.exports = eve) : (typeof define != "undefined" ? (define("eve", [], function() { return eve; })) : (glob.eve = eve));
    })(this);
    // ┌─────────────────────────────────────────────────────────────────────┐ \\
    // │ "Raphaël 2.1.0" - JavaScript Vector Library                         │ \\
    // ├─────────────────────────────────────────────────────────────────────┤ \\
    // │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com)   │ \\
    // │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com)             │ \\
    // │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\
    // └─────────────────────────────────────────────────────────────────────┘ \\
    
    (function (glob, factory) {
        // AMD support
        if (typeof define === "function" && define.amd) {
            // Define as an anonymous module
            define('raphael',["eve"], function( eve ) {
                return factory(glob, eve);
            });
        } else {
            // Browser globals (glob is window)
            // Raphael adds itself to window
            factory(glob, glob.eve);
        }
    }(this, function (window, eve) {
        /*\
         * Raphael
         [ method ]
         **
         * Creates a canvas object on which to draw.
         * You must do this first, as all future calls to drawing methods
         * from this instance will be bound to this canvas.
         > Parameters
         **
         - container (HTMLElement|string) DOM element or its ID which is going to be a parent for drawing surface
         - width (number)
         - height (number)
         - callback (function) #optional callback function which is going to be executed in the context of newly created paper
         * or
         - x (number)
         - y (number)
         - width (number)
         - height (number)
         - callback (function) #optional callback function which is going to be executed in the context of newly created paper
         * or
         - all (array) (first 3 or 4 elements in the array are equal to [containerID, width, height] or [x, y, width, height]. The rest are element descriptions in format {type: type, <attributes>}). See @Paper.add.
         - callback (function) #optional callback function which is going to be executed in the context of newly created paper
         * or
         - onReadyCallback (function) function that is going to be called on DOM ready event. You can also subscribe to this event via Eve’s “DOMLoad” event. In this case method returns `undefined`.
         = (object) @Paper
         > Usage
         | // Each of the following examples create a canvas
         | // that is 320px wide by 200px high.
         | // Canvas is created at the viewport’s 10,50 coordinate.
         | var paper = Raphael(10, 50, 320, 200);
         | // Canvas is created at the top left corner of the #notepad element
         | // (or its top right corner in dir="rtl" elements)
         | var paper = Raphael(document.getElementById("notepad"), 320, 200);
         | // Same as above
         | var paper = Raphael("notepad", 320, 200);
         | // Image dump
         | var set = Raphael(["notepad", 320, 200, {
         |     type: "rect",
         |     x: 10,
         |     y: 10,
         |     width: 25,
         |     height: 25,
         |     stroke: "#f00"
         | }, {
         |     type: "text",
         |     x: 30,
         |     y: 40,
         |     text: "Dump"
         | }]);
        \*/
        function R(first) {
            if (R.is(first, "function")) {
                return loaded ? first() : eve.on("raphael.DOMload", first);
            } else if (R.is(first, array)) {
                return R._engine.create[apply](R, first.splice(0, 3 + R.is(first[0], nu))).add(first);
            } else {
                var args = Array.prototype.slice.call(arguments, 0);
                if (R.is(args[args.length - 1], "function")) {
                    var f = args.pop();
                    return loaded ? f.call(R._engine.create[apply](R, args)) : eve.on("raphael.DOMload", function () {
                        f.call(R._engine.create[apply](R, args));
                    });
                } else {
                    return R._engine.create[apply](R, arguments);
                }
            }
        }
        R.version = "2.1.0";
        R.eve = eve;
        var loaded,
            separator = /[, ]+/,
            elements = {circle: 1, rect: 1, path: 1, ellipse: 1, text: 1, image: 1},
            formatrg = /\{(\d+)\}/g,
            proto = "prototype",
            has = "hasOwnProperty",
            g = {
                doc: document,
                win: window
            },
            oldRaphael = {
                was: Object.prototype[has].call(g.win, "Raphael"),
                is: g.win.Raphael
            },
            Paper = function () {
                /*\
                 * Paper.ca
                 [ property (object) ]
                 **
                 * Shortcut for @Paper.customAttributes
                \*/
                /*\
                 * Paper.customAttributes
                 [ property (object) ]
                 **
                 * If you have a set of attributes that you would like to represent
                 * as a function of some number you can do it easily with custom attributes:
                 > Usage
                 | paper.customAttributes.hue = function (num) {
                 |     num = num % 1;
                 |     return {fill: "hsb(" + num + ", 0.75, 1)"};
                 | };
                 | // Custom attribute “hue” will change fill
                 | // to be given hue with fixed saturation and brightness.
                 | // Now you can use it like this:
                 | var c = paper.circle(10, 10, 10).attr({hue: .45});
                 | // or even like this:
                 | c.animate({hue: 1}, 1e3);
                 | 
                 | // You could also create custom attribute
                 | // with multiple parameters:
                 | paper.customAttributes.hsb = function (h, s, b) {
                 |     return {fill: "hsb(" + [h, s, b].join(",") + ")"};
                 | };
                 | c.attr({hsb: "0.5 .8 1"});
                 | c.animate({hsb: [1, 0, 0.5]}, 1e3);
                \*/
                this.ca = this.customAttributes = {};
            },
            paperproto,
            appendChild = "appendChild",
            apply = "apply",
            concat = "concat",
            supportsTouch = ('ontouchstart' in g.win) || g.win.DocumentTouch && g.doc instanceof DocumentTouch, //taken from Modernizr touch test
            E = "",
            S = " ",
            Str = String,
            split = "split",
            events = "click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend touchcancel"[split](S),
            touchMap = {
                mousedown: "touchstart",
                mousemove: "touchmove",
                mouseup: "touchend"
            },
            lowerCase = Str.prototype.toLowerCase,
            math = Math,
            mmax = math.max,
            mmin = math.min,
            abs = math.abs,
            pow = math.pow,
            PI = math.PI,
            nu = "number",
            string = "string",
            array = "array",
            toString = "toString",
            fillString = "fill",
            objectToString = Object.prototype.toString,
            paper = {},
            push = "push",
            ISURL = R._ISURL = /^url\(['"]?([^\)]+?)['"]?\)$/i,
            colourRegExp = /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\))\s*$/i,
            isnan = {"NaN": 1, "Infinity": 1, "-Infinity": 1},
            bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
            round = math.round,
            setAttribute = "setAttribute",
            toFloat = parseFloat,
            toInt = parseInt,
            upperCase = Str.prototype.toUpperCase,
            availableAttrs = R._availableAttrs = {
                "arrow-end": "none",
                "arrow-start": "none",
                blur: 0,
                "clip-rect": "0 0 1e9 1e9",
                cursor: "default",
                cx: 0,
                cy: 0,
                fill: "#fff",
                "fill-opacity": 1,
                font: '10px "Arial"',
                "font-family": '"Arial"',
                "font-size": "10",
                "font-style": "normal",
                "font-weight": 400,
                gradient: 0,
                height: 0,
                href: "http://raphaeljs.com/",
                "letter-spacing": 0,
                opacity: 1,
                path: "M0,0",
                r: 0,
                rx: 0,
                ry: 0,
                src: "",
                stroke: "#000",
                "stroke-dasharray": "",
                "stroke-linecap": "butt",
                "stroke-linejoin": "butt",
                "stroke-miterlimit": 0,
                "stroke-opacity": 1,
                "stroke-width": 1,
                target: "_blank",
                "text-anchor": "middle",
                title: "Raphael",
                transform: "",
                width: 0,
                x: 0,
                y: 0
            },
            availableAnimAttrs = R._availableAnimAttrs = {
                blur: nu,
                "clip-rect": "csv",
                cx: nu,
                cy: nu,
                fill: "colour",
                "fill-opacity": nu,
                "font-size": nu,
                height: nu,
                opacity: nu,
                path: "path",
                r: nu,
                rx: nu,
                ry: nu,
                stroke: "colour",
                "stroke-opacity": nu,
                "stroke-width": nu,
                transform: "transform",
                width: nu,
                x: nu,
                y: nu
            },
            whitespace = /[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]/g,
            commaSpaces = /[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/,
            hsrg = {hs: 1, rg: 1},
            p2s = /,?([achlmqrstvxz]),?/gi,
            pathCommand = /([achlmrqstvz])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig,
            tCommand = /([rstm])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig,
            pathValues = /(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/ig,
            radial_gradient = R._radial_gradient = /^r(?:\(([^,]+?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*([^\)]+?)\))?/,
            eldata = {},
            sortByKey = function (a, b) {
                return a.key - b.key;
            },
            sortByNumber = function (a, b) {
                return toFloat(a) - toFloat(b);
            },
            fun = function () {},
            pipe = function (x) {
                return x;
            },
            rectPath = R._rectPath = function (x, y, w, h, r) {
                if (r) {
                    return [["M", x + r, y], ["l", w - r * 2, 0], ["a", r, r, 0, 0, 1, r, r], ["l", 0, h - r * 2], ["a", r, r, 0, 0, 1, -r, r], ["l", r * 2 - w, 0], ["a", r, r, 0, 0, 1, -r, -r], ["l", 0, r * 2 - h], ["a", r, r, 0, 0, 1, r, -r], ["z"]];
                }
                return [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
            },
            ellipsePath = function (x, y, rx, ry) {
                if (ry == null) {
                    ry = rx;
                }
                return [["M", x, y], ["m", 0, -ry], ["a", rx, ry, 0, 1, 1, 0, 2 * ry], ["a", rx, ry, 0, 1, 1, 0, -2 * ry], ["z"]];
            },
            getPath = R._getPath = {
                path: function (el) {
                    return el.attr("path");
                },
                circle: function (el) {
                    var a = el.attrs;
                    return ellipsePath(a.cx, a.cy, a.r);
                },
                ellipse: function (el) {
                    var a = el.attrs;
                    return ellipsePath(a.cx, a.cy, a.rx, a.ry);
                },
                rect: function (el) {
                    var a = el.attrs;
                    return rectPath(a.x, a.y, a.width, a.height, a.r);
                },
                image: function (el) {
                    var a = el.attrs;
                    return rectPath(a.x, a.y, a.width, a.height);
                },
                text: function (el) {
                    var bbox = el._getBBox();
                    return rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
                },
                set : function(el) {
                    var bbox = el._getBBox();
                    return rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
                }
            },
            /*\
             * Raphael.mapPath
             [ method ]
             **
             * Transform the path string with given matrix.
             > Parameters
             - path (string) path string
             - matrix (object) see @Matrix
             = (string) transformed path string
            \*/
            mapPath = R.mapPath = function (path, matrix) {
                if (!matrix) {
                    return path;
                }
                var x, y, i, j, ii, jj, pathi;
                path = path2curve(path);
                for (i = 0, ii = path.length; i < ii; i++) {
                    pathi = path[i];
                    for (j = 1, jj = pathi.length; j < jj; j += 2) {
                        x = matrix.x(pathi[j], pathi[j + 1]);
                        y = matrix.y(pathi[j], pathi[j + 1]);
                        pathi[j] = x;
                        pathi[j + 1] = y;
                    }
                }
                return path;
            };
    
        R._g = g;
        /*\
         * Raphael.type
         [ property (string) ]
         **
         * Can be “SVG”, “VML” or empty, depending on browser support.
        \*/
        R.type = (g.win.SVGAngle || g.doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1") ? "SVG" : "VML");
        if (R.type == "VML") {
            var d = g.doc.createElement("div"),
                b;
            d.innerHTML = '<v:shape adj="1"/>';
            b = d.firstChild;
            b.style.behavior = "url(#default#VML)";
            if (!(b && typeof b.adj == "object")) {
                return (R.type = E);
            }
            d = null;
        }
        /*\
         * Raphael.svg
         [ property (boolean) ]
         **
         * `true` if browser supports SVG.
        \*/
        /*\
         * Raphael.vml
         [ property (boolean) ]
         **
         * `true` if browser supports VML.
        \*/
        R.svg = !(R.vml = R.type == "VML");
        R._Paper = Paper;
        /*\
         * Raphael.fn
         [ property (object) ]
         **
         * You can add your own method to the canvas. For example if you want to draw a pie chart,
         * you can create your own pie chart function and ship it as a Raphaël plugin. To do this
         * you need to extend the `Raphael.fn` object. You should modify the `fn` object before a
         * Raphaël instance is created, otherwise it will take no effect. Please note that the
         * ability for namespaced plugins was removed in Raphael 2.0. It is up to the plugin to
         * ensure any namespacing ensures proper context.
         > Usage
         | Raphael.fn.arrow = function (x1, y1, x2, y2, size) {
         |     return this.path( ... );
         | };
         | // or create namespace
         | Raphael.fn.mystuff = {
         |     arrow: function () {…},
         |     star: function () {…},
         |     // etc…
         | };
         | var paper = Raphael(10, 10, 630, 480);
         | // then use it
         | paper.arrow(10, 10, 30, 30, 5).attr({fill: "#f00"});
         | paper.mystuff.arrow();
         | paper.mystuff.star();
        \*/
        R.fn = paperproto = Paper.prototype = R.prototype;
        R._id = 0;
        R._oid = 0;
        /*\
         * Raphael.is
         [ method ]
         **
         * Handfull replacement for `typeof` operator.
         > Parameters
         - o (…) any object or primitive
         - type (string) name of the type, i.e. “string”, “function”, “number”, etc.
         = (boolean) is given value is of given type
        \*/
        R.is = function (o, type) {
            type = lowerCase.call(type);
            if (type == "finite") {
                return !isnan[has](+o);
            }
            if (type == "array") {
                return o instanceof Array;
            }
            return  (type == "null" && o === null) ||
                    (type == typeof o && o !== null) ||
                    (type == "object" && o === Object(o)) ||
                    (type == "array" && Array.isArray && Array.isArray(o)) ||
                    objectToString.call(o).slice(8, -1).toLowerCase() == type;
        };
    
        function clone(obj) {
            if (typeof obj == "function" || Object(obj) !== obj) {
                return obj;
            }
            var res = new obj.constructor;
            for (var key in obj) if (obj[has](key)) {
                res[key] = clone(obj[key]);
            }
            return res;
        }
    
        /*\
         * Raphael.angle
         [ method ]
         **
         * Returns angle between two or three points
         > Parameters
         - x1 (number) x coord of first point
         - y1 (number) y coord of first point
         - x2 (number) x coord of second point
         - y2 (number) y coord of second point
         - x3 (number) #optional x coord of third point
         - y3 (number) #optional y coord of third point
         = (number) angle in degrees.
        \*/
        R.angle = function (x1, y1, x2, y2, x3, y3) {
            if (x3 == null) {
                var x = x1 - x2,
                    y = y1 - y2;
                if (!x && !y) {
                    return 0;
                }
                return (180 + math.atan2(-y, -x) * 180 / PI + 360) % 360;
            } else {
                return R.angle(x1, y1, x3, y3) - R.angle(x2, y2, x3, y3);
            }
        };
        /*\
         * Raphael.rad
         [ method ]
         **
         * Transform angle to radians
         > Parameters
         - deg (number) angle in degrees
         = (number) angle in radians.
        \*/
        R.rad = function (deg) {
            return deg % 360 * PI / 180;
        };
        /*\
         * Raphael.deg
         [ method ]
         **
         * Transform angle to degrees
         > Parameters
         - deg (number) angle in radians
         = (number) angle in degrees.
        \*/
        R.deg = function (rad) {
            return rad * 180 / PI % 360;
        };
        /*\
         * Raphael.snapTo
         [ method ]
         **
         * Snaps given value to given grid.
         > Parameters
         - values (array|number) given array of values or step of the grid
         - value (number) value to adjust
         - tolerance (number) #optional tolerance for snapping. Default is `10`.
         = (number) adjusted value.
        \*/
        R.snapTo = function (values, value, tolerance) {
            tolerance = R.is(tolerance, "finite") ? tolerance : 10;
            if (R.is(values, array)) {
                var i = values.length;
                while (i--) if (abs(values[i] - value) <= tolerance) {
                    return values[i];
                }
            } else {
                values = +values;
                var rem = value % values;
                if (rem < tolerance) {
                    return value - rem;
                }
                if (rem > values - tolerance) {
                    return value - rem + values;
                }
            }
            return value;
        };
    
        /*\
         * Raphael.createUUID
         [ method ]
         **
         * Returns RFC4122, version 4 ID
        \*/
        var createUUID = R.createUUID = (function (uuidRegEx, uuidReplacer) {
            return function () {
                return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(uuidRegEx, uuidReplacer).toUpperCase();
            };
        })(/[xy]/g, function (c) {
            var r = math.random() * 16 | 0,
                v = c == "x" ? r : (r & 3 | 8);
            return v.toString(16);
        });
    
        /*\
         * Raphael.setWindow
         [ method ]
         **
         * Used when you need to draw in `&lt;iframe>`. Switched window to the iframe one.
         > Parameters
         - newwin (window) new window object
        \*/
        R.setWindow = function (newwin) {
            eve("raphael.setWindow", R, g.win, newwin);
            g.win = newwin;
            g.doc = g.win.document;
            if (R._engine.initWin) {
                R._engine.initWin(g.win);
            }
        };
        var toHex = function (color) {
            if (R.vml) {
                // http://dean.edwards.name/weblog/2009/10/convert-any-colour-value-to-hex-in-msie/
                var trim = /^\s+|\s+$/g;
                var bod;
                try {
                    var docum = new ActiveXObject("htmlfile");
                    docum.write("<body>");
                    docum.close();
                    bod = docum.body;
                } catch(e) {
                    bod = createPopup().document.body;
                }
                var range = bod.createTextRange();
                toHex = cacher(function (color) {
                    try {
                        bod.style.color = Str(color).replace(trim, E);
                        var value = range.queryCommandValue("ForeColor");
                        value = ((value & 255) << 16) | (value & 65280) | ((value & 16711680) >>> 16);
                        return "#" + ("000000" + value.toString(16)).slice(-6);
                    } catch(e) {
                        return "none";
                    }
                });
            } else {
                var i = g.doc.createElement("i");
                i.title = "Rapha\xebl Colour Picker";
                i.style.display = "none";
                g.doc.body.appendChild(i);
                toHex = cacher(function (color) {
                    i.style.color = color;
                    return g.doc.defaultView.getComputedStyle(i, E).getPropertyValue("color");
                });
            }
            return toHex(color);
        },
        hsbtoString = function () {
            return "hsb(" + [this.h, this.s, this.b] + ")";
        },
        hsltoString = function () {
            return "hsl(" + [this.h, this.s, this.l] + ")";
        },
        rgbtoString = function () {
            return this.hex;
        },
        prepareRGB = function (r, g, b) {
            if (g == null && R.is(r, "object") && "r" in r && "g" in r && "b" in r) {
                b = r.b;
                g = r.g;
                r = r.r;
            }
            if (g == null && R.is(r, string)) {
                var clr = R.getRGB(r);
                r = clr.r;
                g = clr.g;
                b = clr.b;
            }
            if (r > 1 || g > 1 || b > 1) {
                r /= 255;
                g /= 255;
                b /= 255;
            }
    
            return [r, g, b];
        },
        packageRGB = function (r, g, b, o) {
            r *= 255;
            g *= 255;
            b *= 255;
            var rgb = {
                r: r,
                g: g,
                b: b,
                hex: R.rgb(r, g, b),
                toString: rgbtoString
            };
            R.is(o, "finite") && (rgb.opacity = o);
            return rgb;
        };
    
        /*\
         * Raphael.color
         [ method ]
         **
         * Parses the color string and returns object with all values for the given color.
         > Parameters
         - clr (string) color string in one of the supported formats (see @Raphael.getRGB)
         = (object) Combined RGB & HSB object in format:
         o {
         o     r (number) red,
         o     g (number) green,
         o     b (number) blue,
         o     hex (string) color in HTML/CSS format: #••••••,
         o     error (boolean) `true` if string can’t be parsed,
         o     h (number) hue,
         o     s (number) saturation,
         o     v (number) value (brightness),
         o     l (number) lightness
         o }
        \*/
        R.color = function (clr) {
            var rgb;
            if (R.is(clr, "object") && "h" in clr && "s" in clr && "b" in clr) {
                rgb = R.hsb2rgb(clr);
                clr.r = rgb.r;
                clr.g = rgb.g;
                clr.b = rgb.b;
                clr.hex = rgb.hex;
            } else if (R.is(clr, "object") && "h" in clr && "s" in clr && "l" in clr) {
                rgb = R.hsl2rgb(clr);
                clr.r = rgb.r;
                clr.g = rgb.g;
                clr.b = rgb.b;
                clr.hex = rgb.hex;
            } else {
                if (R.is(clr, "string")) {
                    clr = R.getRGB(clr);
                }
                if (R.is(clr, "object") && "r" in clr && "g" in clr && "b" in clr) {
                    rgb = R.rgb2hsl(clr);
                    clr.h = rgb.h;
                    clr.s = rgb.s;
                    clr.l = rgb.l;
                    rgb = R.rgb2hsb(clr);
                    clr.v = rgb.b;
                } else {
                    clr = {hex: "none"};
                    clr.r = clr.g = clr.b = clr.h = clr.s = clr.v = clr.l = -1;
                }
            }
            clr.toString = rgbtoString;
            return clr;
        };
        /*\
         * Raphael.hsb2rgb
         [ method ]
         **
         * Converts HSB values to RGB object.
         > Parameters
         - h (number) hue
         - s (number) saturation
         - v (number) value or brightness
         = (object) RGB object in format:
         o {
         o     r (number) red,
         o     g (number) green,
         o     b (number) blue,
         o     hex (string) color in HTML/CSS format: #••••••
         o }
        \*/
        R.hsb2rgb = function (h, s, v, o) {
            if (this.is(h, "object") && "h" in h && "s" in h && "b" in h) {
                v = h.b;
                s = h.s;
                h = h.h;
                o = h.o;
            }
            h *= 360;
            var R, G, B, X, C;
            h = (h % 360) / 60;
            C = v * s;
            X = C * (1 - abs(h % 2 - 1));
            R = G = B = v - C;
    
            h = ~~h;
            R += [C, X, 0, 0, X, C][h];
            G += [X, C, C, X, 0, 0][h];
            B += [0, 0, X, C, C, X][h];
            return packageRGB(R, G, B, o);
        };
        /*\
         * Raphael.hsl2rgb
         [ method ]
         **
         * Converts HSL values to RGB object.
         > Parameters
         - h (number) hue
         - s (number) saturation
         - l (number) luminosity
         = (object) RGB object in format:
         o {
         o     r (number) red,
         o     g (number) green,
         o     b (number) blue,
         o     hex (string) color in HTML/CSS format: #••••••
         o }
        \*/
        R.hsl2rgb = function (h, s, l, o) {
            if (this.is(h, "object") && "h" in h && "s" in h && "l" in h) {
                l = h.l;
                s = h.s;
                h = h.h;
            }
            if (h > 1 || s > 1 || l > 1) {
                h /= 360;
                s /= 100;
                l /= 100;
            }
            h *= 360;
            var R, G, B, X, C;
            h = (h % 360) / 60;
            C = 2 * s * (l < .5 ? l : 1 - l);
            X = C * (1 - abs(h % 2 - 1));
            R = G = B = l - C / 2;
    
            h = ~~h;
            R += [C, X, 0, 0, X, C][h];
            G += [X, C, C, X, 0, 0][h];
            B += [0, 0, X, C, C, X][h];
            return packageRGB(R, G, B, o);
        };
        /*\
         * Raphael.rgb2hsb
         [ method ]
         **
         * Converts RGB values to HSB object.
         > Parameters
         - r (number) red
         - g (number) green
         - b (number) blue
         = (object) HSB object in format:
         o {
         o     h (number) hue
         o     s (number) saturation
         o     b (number) brightness
         o }
        \*/
        R.rgb2hsb = function (r, g, b) {
            b = prepareRGB(r, g, b);
            r = b[0];
            g = b[1];
            b = b[2];
    
            var H, S, V, C;
            V = mmax(r, g, b);
            C = V - mmin(r, g, b);
            H = (C == 0 ? null :
                 V == r ? (g - b) / C :
                 V == g ? (b - r) / C + 2 :
                          (r - g) / C + 4
                );
            H = ((H + 360) % 6) * 60 / 360;
            S = C == 0 ? 0 : C / V;
            return {h: H, s: S, b: V, toString: hsbtoString};
        };
        /*\
         * Raphael.rgb2hsl
         [ method ]
         **
         * Converts RGB values to HSL object.
         > Parameters
         - r (number) red
         - g (number) green
         - b (number) blue
         = (object) HSL object in format:
         o {
         o     h (number) hue
         o     s (number) saturation
         o     l (number) luminosity
         o }
        \*/
        R.rgb2hsl = function (r, g, b) {
            b = prepareRGB(r, g, b);
            r = b[0];
            g = b[1];
            b = b[2];
    
            var H, S, L, M, m, C;
            M = mmax(r, g, b);
            m = mmin(r, g, b);
            C = M - m;
            H = (C == 0 ? null :
                 M == r ? (g - b) / C :
                 M == g ? (b - r) / C + 2 :
                          (r - g) / C + 4);
            H = ((H + 360) % 6) * 60 / 360;
            L = (M + m) / 2;
            S = (C == 0 ? 0 :
                 L < .5 ? C / (2 * L) :
                          C / (2 - 2 * L));
            return {h: H, s: S, l: L, toString: hsltoString};
        };
        R._path2string = function () {
            return this.join(",").replace(p2s, "$1");
        };
        function repush(array, item) {
            for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) {
                return array.push(array.splice(i, 1)[0]);
            }
        }
        function cacher(f, scope, postprocessor) {
            function newf() {
                var arg = Array.prototype.slice.call(arguments, 0),
                    args = arg.join("\u2400"),
                    cache = newf.cache = newf.cache || {},
                    count = newf.count = newf.count || [];
                if (cache[has](args)) {
                    repush(count, args);
                    return postprocessor ? postprocessor(cache[args]) : cache[args];
                }
                count.length >= 1e3 && delete cache[count.shift()];
                count.push(args);
                cache[args] = f[apply](scope, arg);
                return postprocessor ? postprocessor(cache[args]) : cache[args];
            }
            return newf;
        }
    
        var preload = R._preload = function (src, f) {
            var img = g.doc.createElement("img");
            img.style.cssText = "position:absolute;left:-9999em;top:-9999em";
            img.onload = function () {
                f.call(this);
                this.onload = null;
                g.doc.body.removeChild(this);
            };
            img.onerror = function () {
                g.doc.body.removeChild(this);
            };
            g.doc.body.appendChild(img);
            img.src = src;
        };
    
        function clrToString() {
            return this.hex;
        }
    
        /*\
         * Raphael.getRGB
         [ method ]
         **
         * Parses colour string as RGB object
         > Parameters
         - colour (string) colour string in one of formats:
         # <ul>
         #     <li>Colour name (“<code>red</code>”, “<code>green</code>”, “<code>cornflowerblue</code>”, etc)</li>
         #     <li>#••• — shortened HTML colour: (“<code>#000</code>”, “<code>#fc0</code>”, etc)</li>
         #     <li>#•••••• — full length HTML colour: (“<code>#000000</code>”, “<code>#bd2300</code>”)</li>
         #     <li>rgb(•••, •••, •••) — red, green and blue channels’ values: (“<code>rgb(200,&nbsp;100,&nbsp;0)</code>”)</li>
         #     <li>rgb(•••%, •••%, •••%) — same as above, but in %: (“<code>rgb(100%,&nbsp;175%,&nbsp;0%)</code>”)</li>
         #     <li>hsb(•••, •••, •••) — hue, saturation and brightness values: (“<code>hsb(0.5,&nbsp;0.25,&nbsp;1)</code>”)</li>
         #     <li>hsb(•••%, •••%, •••%) — same as above, but in %</li>
         #     <li>hsl(•••, •••, •••) — same as hsb</li>
         #     <li>hsl(•••%, •••%, •••%) — same as hsb</li>
         # </ul>
         = (object) RGB object in format:
         o {
         o     r (number) red,
         o     g (number) green,
         o     b (number) blue
         o     hex (string) color in HTML/CSS format: #••••••,
         o     error (boolean) true if string can’t be parsed
         o }
        \*/
        R.getRGB = cacher(function (colour) {
            if (!colour || !!((colour = Str(colour)).indexOf("-") + 1)) {
                return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: clrToString};
            }
            if (colour == "none") {
                return {r: -1, g: -1, b: -1, hex: "none", toString: clrToString};
            }
            !(hsrg[has](colour.toLowerCase().substring(0, 2)) || colour.charAt() == "#") && (colour = toHex(colour));
            var res,
                red,
                green,
                blue,
                opacity,
                t,
                values,
                rgb = colour.match(colourRegExp);
            if (rgb) {
                if (rgb[2]) {
                    blue = toInt(rgb[2].substring(5), 16);
                    green = toInt(rgb[2].substring(3, 5), 16);
                    red = toInt(rgb[2].substring(1, 3), 16);
                }
                if (rgb[3]) {
                    blue = toInt((t = rgb[3].charAt(3)) + t, 16);
                    green = toInt((t = rgb[3].charAt(2)) + t, 16);
                    red = toInt((t = rgb[3].charAt(1)) + t, 16);
                }
                if (rgb[4]) {
                    values = rgb[4][split](commaSpaces);
                    red = toFloat(values[0]);
                    values[0].slice(-1) == "%" && (red *= 2.55);
                    green = toFloat(values[1]);
                    values[1].slice(-1) == "%" && (green *= 2.55);
                    blue = toFloat(values[2]);
                    values[2].slice(-1) == "%" && (blue *= 2.55);
                    rgb[1].toLowerCase().slice(0, 4) == "rgba" && (opacity = toFloat(values[3]));
                    values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
                }
                if (rgb[5]) {
                    values = rgb[5][split](commaSpaces);
                    red = toFloat(values[0]);
                    values[0].slice(-1) == "%" && (red *= 2.55);
                    green = toFloat(values[1]);
                    values[1].slice(-1) == "%" && (green *= 2.55);
                    blue = toFloat(values[2]);
                    values[2].slice(-1) == "%" && (blue *= 2.55);
                    (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
                    rgb[1].toLowerCase().slice(0, 4) == "hsba" && (opacity = toFloat(values[3]));
                    values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
                    return R.hsb2rgb(red, green, blue, opacity);
                }
                if (rgb[6]) {
                    values = rgb[6][split](commaSpaces);
                    red = toFloat(values[0]);
                    values[0].slice(-1) == "%" && (red *= 2.55);
                    green = toFloat(values[1]);
                    values[1].slice(-1) == "%" && (green *= 2.55);
                    blue = toFloat(values[2]);
                    values[2].slice(-1) == "%" && (blue *= 2.55);
                    (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
                    rgb[1].toLowerCase().slice(0, 4) == "hsla" && (opacity = toFloat(values[3]));
                    values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
                    return R.hsl2rgb(red, green, blue, opacity);
                }
                rgb = {r: red, g: green, b: blue, toString: clrToString};
                rgb.hex = "#" + (16777216 | blue | (green << 8) | (red << 16)).toString(16).slice(1);
                R.is(opacity, "finite") && (rgb.opacity = opacity);
                return rgb;
            }
            return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: clrToString};
        }, R);
        /*\
         * Raphael.hsb
         [ method ]
         **
         * Converts HSB values to hex representation of the colour.
         > Parameters
         - h (number) hue
         - s (number) saturation
         - b (number) value or brightness
         = (string) hex representation of the colour.
        \*/
        R.hsb = cacher(function (h, s, b) {
            return R.hsb2rgb(h, s, b).hex;
        });
        /*\
         * Raphael.hsl
         [ method ]
         **
         * Converts HSL values to hex representation of the colour.
         > Parameters
         - h (number) hue
         - s (number) saturation
         - l (number) luminosity
         = (string) hex representation of the colour.
        \*/
        R.hsl = cacher(function (h, s, l) {
            return R.hsl2rgb(h, s, l).hex;
        });
        /*\
         * Raphael.rgb
         [ method ]
         **
         * Converts RGB values to hex representation of the colour.
         > Parameters
         - r (number) red
         - g (number) green
         - b (number) blue
         = (string) hex representation of the colour.
        \*/
        R.rgb = cacher(function (r, g, b) {
            return "#" + (16777216 | b | (g << 8) | (r << 16)).toString(16).slice(1);
        });
        /*\
         * Raphael.getColor
         [ method ]
         **
         * On each call returns next colour in the spectrum. To reset it back to red call @Raphael.getColor.reset
         > Parameters
         - value (number) #optional brightness, default is `0.75`
         = (string) hex representation of the colour.
        \*/
        R.getColor = function (value) {
            var start = this.getColor.start = this.getColor.start || {h: 0, s: 1, b: value || .75},
                rgb = this.hsb2rgb(start.h, start.s, start.b);
            start.h += .075;
            if (start.h > 1) {
                start.h = 0;
                start.s -= .2;
                start.s <= 0 && (this.getColor.start = {h: 0, s: 1, b: start.b});
            }
            return rgb.hex;
        };
        /*\
         * Raphael.getColor.reset
         [ method ]
         **
         * Resets spectrum position for @Raphael.getColor back to red.
        \*/
        R.getColor.reset = function () {
            delete this.start;
        };
    
        // http://schepers.cc/getting-to-the-point
        function catmullRom2bezier(crp, z) {
            var d = [];
            for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) {
                var p = [
                            {x: +crp[i - 2], y: +crp[i - 1]},
                            {x: +crp[i],     y: +crp[i + 1]},
                            {x: +crp[i + 2], y: +crp[i + 3]},
                            {x: +crp[i + 4], y: +crp[i + 5]}
                        ];
                if (z) {
                    if (!i) {
                        p[0] = {x: +crp[iLen - 2], y: +crp[iLen - 1]};
                    } else if (iLen - 4 == i) {
                        p[3] = {x: +crp[0], y: +crp[1]};
                    } else if (iLen - 2 == i) {
                        p[2] = {x: +crp[0], y: +crp[1]};
                        p[3] = {x: +crp[2], y: +crp[3]};
                    }
                } else {
                    if (iLen - 4 == i) {
                        p[3] = p[2];
                    } else if (!i) {
                        p[0] = {x: +crp[i], y: +crp[i + 1]};
                    }
                }
                d.push(["C",
                      (-p[0].x + 6 * p[1].x + p[2].x) / 6,
                      (-p[0].y + 6 * p[1].y + p[2].y) / 6,
                      (p[1].x + 6 * p[2].x - p[3].x) / 6,
                      (p[1].y + 6*p[2].y - p[3].y) / 6,
                      p[2].x,
                      p[2].y
                ]);
            }
    
            return d;
        }
        /*\
         * Raphael.parsePathString
         [ method ]
         **
         * Utility method
         **
         * Parses given path string into an array of arrays of path segments.
         > Parameters
         - pathString (string|array) path string or array of segments (in the last case it will be returned straight away)
         = (array) array of segments.
        \*/
        R.parsePathString = function (pathString) {
            if (!pathString) {
                return null;
            }
            var pth = paths(pathString);
            if (pth.arr) {
                return pathClone(pth.arr);
            }
    
            var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0},
                data = [];
            if (R.is(pathString, array) && R.is(pathString[0], array)) { // rough assumption
                data = pathClone(pathString);
            }
            if (!data.length) {
                Str(pathString).replace(pathCommand, function (a, b, c) {
                    var params = [],
                        name = b.toLowerCase();
                    c.replace(pathValues, function (a, b) {
                        b && params.push(+b);
                    });
                    if (name == "m" && params.length > 2) {
                        data.push([b][concat](params.splice(0, 2)));
                        name = "l";
                        b = b == "m" ? "l" : "L";
                    }
                    if (name == "r") {
                        data.push([b][concat](params));
                    } else while (params.length >= paramCounts[name]) {
                        data.push([b][concat](params.splice(0, paramCounts[name])));
                        if (!paramCounts[name]) {
                            break;
                        }
                    }
                });
            }
            data.toString = R._path2string;
            pth.arr = pathClone(data);
            return data;
        };
        /*\
         * Raphael.parseTransformString
         [ method ]
         **
         * Utility method
         **
         * Parses given path string into an array of transformations.
         > Parameters
         - TString (string|array) transform string or array of transformations (in the last case it will be returned straight away)
         = (array) array of transformations.
        \*/
        R.parseTransformString = cacher(function (TString) {
            if (!TString) {
                return null;
            }
            var paramCounts = {r: 3, s: 4, t: 2, m: 6},
                data = [];
            if (R.is(TString, array) && R.is(TString[0], array)) { // rough assumption
                data = pathClone(TString);
            }
            if (!data.length) {
                Str(TString).replace(tCommand, function (a, b, c) {
                    var params = [],
                        name = lowerCase.call(b);
                    c.replace(pathValues, function (a, b) {
                        b && params.push(+b);
                    });
                    data.push([b][concat](params));
                });
            }
            data.toString = R._path2string;
            return data;
        });
        // PATHS
        var paths = function (ps) {
            var p = paths.ps = paths.ps || {};
            if (p[ps]) {
                p[ps].sleep = 100;
            } else {
                p[ps] = {
                    sleep: 100
                };
            }
            setTimeout(function () {
                for (var key in p) if (p[has](key) && key != ps) {
                    p[key].sleep--;
                    !p[key].sleep && delete p[key];
                }
            });
            return p[ps];
        };
        /*\
         * Raphael.findDotsAtSegment
         [ method ]
         **
         * Utility method
         **
         * Find dot coordinates on the given cubic bezier curve at the given t.
         > Parameters
         - p1x (number) x of the first point of the curve
         - p1y (number) y of the first point of the curve
         - c1x (number) x of the first anchor of the curve
         - c1y (number) y of the first anchor of the curve
         - c2x (number) x of the second anchor of the curve
         - c2y (number) y of the second anchor of the curve
         - p2x (number) x of the second point of the curve
         - p2y (number) y of the second point of the curve
         - t (number) position on the curve (0..1)
         = (object) point information in format:
         o {
         o     x: (number) x coordinate of the point
         o     y: (number) y coordinate of the point
         o     m: {
         o         x: (number) x coordinate of the left anchor
         o         y: (number) y coordinate of the left anchor
         o     }
         o     n: {
         o         x: (number) x coordinate of the right anchor
         o         y: (number) y coordinate of the right anchor
         o     }
         o     start: {
         o         x: (number) x coordinate of the start of the curve
         o         y: (number) y coordinate of the start of the curve
         o     }
         o     end: {
         o         x: (number) x coordinate of the end of the curve
         o         y: (number) y coordinate of the end of the curve
         o     }
         o     alpha: (number) angle of the curve derivative at the point
         o }
        \*/
        R.findDotsAtSegment = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
            var t1 = 1 - t,
                t13 = pow(t1, 3),
                t12 = pow(t1, 2),
                t2 = t * t,
                t3 = t2 * t,
                x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x,
                y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y,
                mx = p1x + 2 * t * (c1x - p1x) + t2 * (c2x - 2 * c1x + p1x),
                my = p1y + 2 * t * (c1y - p1y) + t2 * (c2y - 2 * c1y + p1y),
                nx = c1x + 2 * t * (c2x - c1x) + t2 * (p2x - 2 * c2x + c1x),
                ny = c1y + 2 * t * (c2y - c1y) + t2 * (p2y - 2 * c2y + c1y),
                ax = t1 * p1x + t * c1x,
                ay = t1 * p1y + t * c1y,
                cx = t1 * c2x + t * p2x,
                cy = t1 * c2y + t * p2y,
                alpha = (90 - math.atan2(mx - nx, my - ny) * 180 / PI);
            (mx > nx || my < ny) && (alpha += 180);
            return {
                x: x,
                y: y,
                m: {x: mx, y: my},
                n: {x: nx, y: ny},
                start: {x: ax, y: ay},
                end: {x: cx, y: cy},
                alpha: alpha
            };
        };
        /*\
         * Raphael.bezierBBox
         [ method ]
         **
         * Utility method
         **
         * Return bounding box of a given cubic bezier curve
         > Parameters
         - p1x (number) x of the first point of the curve
         - p1y (number) y of the first point of the curve
         - c1x (number) x of the first anchor of the curve
         - c1y (number) y of the first anchor of the curve
         - c2x (number) x of the second anchor of the curve
         - c2y (number) y of the second anchor of the curve
         - p2x (number) x of the second point of the curve
         - p2y (number) y of the second point of the curve
         * or
         - bez (array) array of six points for bezier curve
         = (object) point information in format:
         o {
         o     min: {
         o         x: (number) x coordinate of the left point
         o         y: (number) y coordinate of the top point
         o     }
         o     max: {
         o         x: (number) x coordinate of the right point
         o         y: (number) y coordinate of the bottom point
         o     }
         o }
        \*/
        R.bezierBBox = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
            if (!R.is(p1x, "array")) {
                p1x = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y];
            }
            var bbox = curveDim.apply(null, p1x);
            return {
                x: bbox.min.x,
                y: bbox.min.y,
                x2: bbox.max.x,
                y2: bbox.max.y,
                width: bbox.max.x - bbox.min.x,
                height: bbox.max.y - bbox.min.y
            };
        };
        /*\
         * Raphael.isPointInsideBBox
         [ method ]
         **
         * Utility method
         **
         * Returns `true` if given point is inside bounding boxes.
         > Parameters
         - bbox (string) bounding box
         - x (string) x coordinate of the point
         - y (string) y coordinate of the point
         = (boolean) `true` if point inside
        \*/
        R.isPointInsideBBox = function (bbox, x, y) {
            return x >= bbox.x && x <= bbox.x2 && y >= bbox.y && y <= bbox.y2;
        };
        /*\
         * Raphael.isBBoxIntersect
         [ method ]
         **
         * Utility method
         **
         * Returns `true` if two bounding boxes intersect
         > Parameters
         - bbox1 (string) first bounding box
         - bbox2 (string) second bounding box
         = (boolean) `true` if they intersect
        \*/
        R.isBBoxIntersect = function (bbox1, bbox2) {
            var i = R.isPointInsideBBox;
            return i(bbox2, bbox1.x, bbox1.y)
                || i(bbox2, bbox1.x2, bbox1.y)
                || i(bbox2, bbox1.x, bbox1.y2)
                || i(bbox2, bbox1.x2, bbox1.y2)
                || i(bbox1, bbox2.x, bbox2.y)
                || i(bbox1, bbox2.x2, bbox2.y)
                || i(bbox1, bbox2.x, bbox2.y2)
                || i(bbox1, bbox2.x2, bbox2.y2)
                || (bbox1.x < bbox2.x2 && bbox1.x > bbox2.x || bbox2.x < bbox1.x2 && bbox2.x > bbox1.x)
                && (bbox1.y < bbox2.y2 && bbox1.y > bbox2.y || bbox2.y < bbox1.y2 && bbox2.y > bbox1.y);
        };
        function base3(t, p1, p2, p3, p4) {
            var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
                t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
            return t * t2 - 3 * p1 + 3 * p2;
        }
        function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) {
            if (z == null) {
                z = 1;
            }
            z = z > 1 ? 1 : z < 0 ? 0 : z;
            var z2 = z / 2,
                n = 12,
                Tvalues = [-0.1252,0.1252,-0.3678,0.3678,-0.5873,0.5873,-0.7699,0.7699,-0.9041,0.9041,-0.9816,0.9816],
                Cvalues = [0.2491,0.2491,0.2335,0.2335,0.2032,0.2032,0.1601,0.1601,0.1069,0.1069,0.0472,0.0472],
                sum = 0;
            for (var i = 0; i < n; i++) {
                var ct = z2 * Tvalues[i] + z2,
                    xbase = base3(ct, x1, x2, x3, x4),
                    ybase = base3(ct, y1, y2, y3, y4),
                    comb = xbase * xbase + ybase * ybase;
                sum += Cvalues[i] * math.sqrt(comb);
            }
            return z2 * sum;
        }
        function getTatLen(x1, y1, x2, y2, x3, y3, x4, y4, ll) {
            if (ll < 0 || bezlen(x1, y1, x2, y2, x3, y3, x4, y4) < ll) {
                return;
            }
            var t = 1,
                step = t / 2,
                t2 = t - step,
                l,
                e = .01;
            l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
            while (abs(l - ll) > e) {
                step /= 2;
                t2 += (l < ll ? 1 : -1) * step;
                l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
            }
            return t2;
        }
        function intersect(x1, y1, x2, y2, x3, y3, x4, y4) {
            if (
                mmax(x1, x2) < mmin(x3, x4) ||
                mmin(x1, x2) > mmax(x3, x4) ||
                mmax(y1, y2) < mmin(y3, y4) ||
                mmin(y1, y2) > mmax(y3, y4)
            ) {
                return;
            }
            var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4),
                ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4),
                denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
    
            if (!denominator) {
                return;
            }
            var px = nx / denominator,
                py = ny / denominator,
                px2 = +px.toFixed(2),
                py2 = +py.toFixed(2);
            if (
                px2 < +mmin(x1, x2).toFixed(2) ||
                px2 > +mmax(x1, x2).toFixed(2) ||
                px2 < +mmin(x3, x4).toFixed(2) ||
                px2 > +mmax(x3, x4).toFixed(2) ||
                py2 < +mmin(y1, y2).toFixed(2) ||
                py2 > +mmax(y1, y2).toFixed(2) ||
                py2 < +mmin(y3, y4).toFixed(2) ||
                py2 > +mmax(y3, y4).toFixed(2)
            ) {
                return;
            }
            return {x: px, y: py};
        }
        function inter(bez1, bez2) {
            return interHelper(bez1, bez2);
        }
        function interCount(bez1, bez2) {
            return interHelper(bez1, bez2, 1);
        }
        function interHelper(bez1, bez2, justCount) {
            var bbox1 = R.bezierBBox(bez1),
                bbox2 = R.bezierBBox(bez2);
            if (!R.isBBoxIntersect(bbox1, bbox2)) {
                return justCount ? 0 : [];
            }
            var l1 = bezlen.apply(0, bez1),
                l2 = bezlen.apply(0, bez2),
                n1 = mmax(~~(l1 / 5), 1),
                n2 = mmax(~~(l2 / 5), 1),
                dots1 = [],
                dots2 = [],
                xy = {},
                res = justCount ? 0 : [];
            for (var i = 0; i < n1 + 1; i++) {
                var p = R.findDotsAtSegment.apply(R, bez1.concat(i / n1));
                dots1.push({x: p.x, y: p.y, t: i / n1});
            }
            for (i = 0; i < n2 + 1; i++) {
                p = R.findDotsAtSegment.apply(R, bez2.concat(i / n2));
                dots2.push({x: p.x, y: p.y, t: i / n2});
            }
            for (i = 0; i < n1; i++) {
                for (var j = 0; j < n2; j++) {
                    var di = dots1[i],
                        di1 = dots1[i + 1],
                        dj = dots2[j],
                        dj1 = dots2[j + 1],
                        ci = abs(di1.x - di.x) < .001 ? "y" : "x",
                        cj = abs(dj1.x - dj.x) < .001 ? "y" : "x",
                        is = intersect(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y);
                    if (is) {
                        if (xy[is.x.toFixed(4)] == is.y.toFixed(4)) {
                            continue;
                        }
                        xy[is.x.toFixed(4)] = is.y.toFixed(4);
                        var t1 = di.t + abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t),
                            t2 = dj.t + abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t);
                        if (t1 >= 0 && t1 <= 1.001 && t2 >= 0 && t2 <= 1.001) {
                            if (justCount) {
                                res++;
                            } else {
                                res.push({
                                    x: is.x,
                                    y: is.y,
                                    t1: mmin(t1, 1),
                                    t2: mmin(t2, 1)
                                });
                            }
                        }
                    }
                }
            }
            return res;
        }
        /*\
         * Raphael.pathIntersection
         [ method ]
         **
         * Utility method
         **
         * Finds intersections of two paths
         > Parameters
         - path1 (string) path string
         - path2 (string) path string
         = (array) dots of intersection
         o [
         o     {
         o         x: (number) x coordinate of the point
         o         y: (number) y coordinate of the point
         o         t1: (number) t value for segment of path1
         o         t2: (number) t value for segment of path2
         o         segment1: (number) order number for segment of path1
         o         segment2: (number) order number for segment of path2
         o         bez1: (array) eight coordinates representing beziér curve for the segment of path1
         o         bez2: (array) eight coordinates representing beziér curve for the segment of path2
         o     }
         o ]
        \*/
        R.pathIntersection = function (path1, path2) {
            return interPathHelper(path1, path2);
        };
        R.pathIntersectionNumber = function (path1, path2) {
            return interPathHelper(path1, path2, 1);
        };
        function interPathHelper(path1, path2, justCount) {
            path1 = R._path2curve(path1);
            path2 = R._path2curve(path2);
            var x1, y1, x2, y2, x1m, y1m, x2m, y2m, bez1, bez2,
                res = justCount ? 0 : [];
            for (var i = 0, ii = path1.length; i < ii; i++) {
                var pi = path1[i];
                if (pi[0] == "M") {
                    x1 = x1m = pi[1];
                    y1 = y1m = pi[2];
                } else {
                    if (pi[0] == "C") {
                        bez1 = [x1, y1].concat(pi.slice(1));
                        x1 = bez1[6];
                        y1 = bez1[7];
                    } else {
                        bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m];
                        x1 = x1m;
                        y1 = y1m;
                    }
                    for (var j = 0, jj = path2.length; j < jj; j++) {
                        var pj = path2[j];
                        if (pj[0] == "M") {
                            x2 = x2m = pj[1];
                            y2 = y2m = pj[2];
                        } else {
                            if (pj[0] == "C") {
                                bez2 = [x2, y2].concat(pj.slice(1));
                                x2 = bez2[6];
                                y2 = bez2[7];
                            } else {
                                bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m];
                                x2 = x2m;
                                y2 = y2m;
                            }
                            var intr = interHelper(bez1, bez2, justCount);
                            if (justCount) {
                                res += intr;
                            } else {
                                for (var k = 0, kk = intr.length; k < kk; k++) {
                                    intr[k].segment1 = i;
                                    intr[k].segment2 = j;
                                    intr[k].bez1 = bez1;
                                    intr[k].bez2 = bez2;
                                }
                                res = res.concat(intr);
                            }
                        }
                    }
                }
            }
            return res;
        }
        /*\
         * Raphael.isPointInsidePath
         [ method ]
         **
         * Utility method
         **
         * Returns `true` if given point is inside a given closed path.
         > Parameters
         - path (string) path string
         - x (number) x of the point
         - y (number) y of the point
         = (boolean) true, if point is inside the path
        \*/
        R.isPointInsidePath = function (path, x, y) {
            var bbox = R.pathBBox(path);
            return R.isPointInsideBBox(bbox, x, y) &&
                   interPathHelper(path, [["M", x, y], ["H", bbox.x2 + 10]], 1) % 2 == 1;
        };
        R._removedFactory = function (methodname) {
            return function () {
                eve("raphael.log", null, "Rapha\xebl: you are calling to method \u201c" + methodname + "\u201d of removed object", methodname);
            };
        };
        /*\
         * Raphael.pathBBox
         [ method ]
         **
         * Utility method
         **
         * Return bounding box of a given path
         > Parameters
         - path (string) path string
         = (object) bounding box
         o {
         o     x: (number) x coordinate of the left top point of the box
         o     y: (number) y coordinate of the left top point of the box
         o     x2: (number) x coordinate of the right bottom point of the box
         o     y2: (number) y coordinate of the right bottom point of the box
         o     width: (number) width of the box
         o     height: (number) height of the box
         o     cx: (number) x coordinate of the center of the box
         o     cy: (number) y coordinate of the center of the box
         o }
        \*/
        var pathDimensions = R.pathBBox = function (path) {
            var pth = paths(path);
            if (pth.bbox) {
                return clone(pth.bbox);
            }
            if (!path) {
                return {x: 0, y: 0, width: 0, height: 0, x2: 0, y2: 0};
            }
            path = path2curve(path);
            var x = 0,
                y = 0,
                X = [],
                Y = [],
                p;
            for (var i = 0, ii = path.length; i < ii; i++) {
                p = path[i];
                if (p[0] == "M") {
                    x = p[1];
                    y = p[2];
                    X.push(x);
                    Y.push(y);
                } else {
                    var dim = curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
                    X = X[concat](dim.min.x, dim.max.x);
                    Y = Y[concat](dim.min.y, dim.max.y);
                    x = p[5];
                    y = p[6];
                }
            }
            var xmin = mmin[apply](0, X),
                ymin = mmin[apply](0, Y),
                xmax = mmax[apply](0, X),
                ymax = mmax[apply](0, Y),
                width = xmax - xmin,
                height = ymax - ymin,
                    bb = {
                    x: xmin,
                    y: ymin,
                    x2: xmax,
                    y2: ymax,
                    width: width,
                    height: height,
                    cx: xmin + width / 2,
                    cy: ymin + height / 2
                };
            pth.bbox = clone(bb);
            return bb;
        },
            pathClone = function (pathArray) {
                var res = clone(pathArray);
                res.toString = R._path2string;
                return res;
            },
            pathToRelative = R._pathToRelative = function (pathArray) {
                var pth = paths(pathArray);
                if (pth.rel) {
                    return pathClone(pth.rel);
                }
                if (!R.is(pathArray, array) || !R.is(pathArray && pathArray[0], array)) { // rough assumption
                    pathArray = R.parsePathString(pathArray);
                }
                var res = [],
                    x = 0,
                    y = 0,
                    mx = 0,
                    my = 0,
                    start = 0;
                if (pathArray[0][0] == "M") {
                    x = pathArray[0][1];
                    y = pathArray[0][2];
                    mx = x;
                    my = y;
                    start++;
                    res.push(["M", x, y]);
                }
                for (var i = start, ii = pathArray.length; i < ii; i++) {
                    var r = res[i] = [],
                        pa = pathArray[i];
                    if (pa[0] != lowerCase.call(pa[0])) {
                        r[0] = lowerCase.call(pa[0]);
                        switch (r[0]) {
                            case "a":
                                r[1] = pa[1];
                                r[2] = pa[2];
                                r[3] = pa[3];
                                r[4] = pa[4];
                                r[5] = pa[5];
                                r[6] = +(pa[6] - x).toFixed(3);
                                r[7] = +(pa[7] - y).toFixed(3);
                                break;
                            case "v":
                                r[1] = +(pa[1] - y).toFixed(3);
                                break;
                            case "m":
                                mx = pa[1];
                                my = pa[2];
                            default:
                                for (var j = 1, jj = pa.length; j < jj; j++) {
                                    r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
                                }
                        }
                    } else {
                        r = res[i] = [];
                        if (pa[0] == "m") {
                            mx = pa[1] + x;
                            my = pa[2] + y;
                        }
                        for (var k = 0, kk = pa.length; k < kk; k++) {
                            res[i][k] = pa[k];
                        }
                    }
                    var len = res[i].length;
                    switch (res[i][0]) {
                        case "z":
                            x = mx;
                            y = my;
                            break;
                        case "h":
                            x += +res[i][len - 1];
                            break;
                        case "v":
                            y += +res[i][len - 1];
                            break;
                        default:
                            x += +res[i][len - 2];
                            y += +res[i][len - 1];
                    }
                }
                res.toString = R._path2string;
                pth.rel = pathClone(res);
                return res;
            },
            pathToAbsolute = R._pathToAbsolute = function (pathArray) {
                var pth = paths(pathArray);
                if (pth.abs) {
                    return pathClone(pth.abs);
                }
                if (!R.is(pathArray, array) || !R.is(pathArray && pathArray[0], array)) { // rough assumption
                    pathArray = R.parsePathString(pathArray);
                }
                if (!pathArray || !pathArray.length) {
                    return [["M", 0, 0]];
                }
                var res = [],
                    x = 0,
                    y = 0,
                    mx = 0,
                    my = 0,
                    start = 0;
                if (pathArray[0][0] == "M") {
                    x = +pathArray[0][1];
                    y = +pathArray[0][2];
                    mx = x;
                    my = y;
                    start++;
                    res[0] = ["M", x, y];
                }
                var crz = pathArray.length == 3 && pathArray[0][0] == "M" && pathArray[1][0].toUpperCase() == "R" && pathArray[2][0].toUpperCase() == "Z";
                for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) {
                    res.push(r = []);
                    pa = pathArray[i];
                    if (pa[0] != upperCase.call(pa[0])) {
                        r[0] = upperCase.call(pa[0]);
                        switch (r[0]) {
                            case "A":
                                r[1] = pa[1];
                                r[2] = pa[2];
                                r[3] = pa[3];
                                r[4] = pa[4];
                                r[5] = pa[5];
                                r[6] = +(pa[6] + x);
                                r[7] = +(pa[7] + y);
                                break;
                            case "V":
                                r[1] = +pa[1] + y;
                                break;
                            case "H":
                                r[1] = +pa[1] + x;
                                break;
                            case "R":
                                var dots = [x, y][concat](pa.slice(1));
                                for (var j = 2, jj = dots.length; j < jj; j++) {
                                    dots[j] = +dots[j] + x;
                                    dots[++j] = +dots[j] + y;
                                }
                                res.pop();
                                res = res[concat](catmullRom2bezier(dots, crz));
                                break;
                            case "M":
                                mx = +pa[1] + x;
                                my = +pa[2] + y;
                            default:
                                for (j = 1, jj = pa.length; j < jj; j++) {
                                    r[j] = +pa[j] + ((j % 2) ? x : y);
                                }
                        }
                    } else if (pa[0] == "R") {
                        dots = [x, y][concat](pa.slice(1));
                        res.pop();
                        res = res[concat](catmullRom2bezier(dots, crz));
                        r = ["R"][concat](pa.slice(-2));
                    } else {
                        for (var k = 0, kk = pa.length; k < kk; k++) {
                            r[k] = pa[k];
                        }
                    }
                    switch (r[0]) {
                        case "Z":
                            x = mx;
                            y = my;
                            break;
                        case "H":
                            x = r[1];
                            break;
                        case "V":
                            y = r[1];
                            break;
                        case "M":
                            mx = r[r.length - 2];
                            my = r[r.length - 1];
                        default:
                            x = r[r.length - 2];
                            y = r[r.length - 1];
                    }
                }
                res.toString = R._path2string;
                pth.abs = pathClone(res);
                return res;
            },
            l2c = function (x1, y1, x2, y2) {
                return [x1, y1, x2, y2, x2, y2];
            },
            q2c = function (x1, y1, ax, ay, x2, y2) {
                var _13 = 1 / 3,
                    _23 = 2 / 3;
                return [
                        _13 * x1 + _23 * ax,
                        _13 * y1 + _23 * ay,
                        _13 * x2 + _23 * ax,
                        _13 * y2 + _23 * ay,
                        x2,
                        y2
                    ];
            },
            a2c = function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
                // for more information of where this math came from visit:
                // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
                var _120 = PI * 120 / 180,
                    rad = PI / 180 * (+angle || 0),
                    res = [],
                    xy,
                    rotate = cacher(function (x, y, rad) {
                        var X = x * math.cos(rad) - y * math.sin(rad),
                            Y = x * math.sin(rad) + y * math.cos(rad);
                        return {x: X, y: Y};
                    });
                if (!recursive) {
                    xy = rotate(x1, y1, -rad);
                    x1 = xy.x;
                    y1 = xy.y;
                    xy = rotate(x2, y2, -rad);
                    x2 = xy.x;
                    y2 = xy.y;
                    var cos = math.cos(PI / 180 * angle),
                        sin = math.sin(PI / 180 * angle),
                        x = (x1 - x2) / 2,
                        y = (y1 - y2) / 2;
                    var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
                    if (h > 1) {
                        h = math.sqrt(h);
                        rx = h * rx;
                        ry = h * ry;
                    }
                    var rx2 = rx * rx,
                        ry2 = ry * ry,
                        k = (large_arc_flag == sweep_flag ? -1 : 1) *
                            math.sqrt(abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
                        cx = k * rx * y / ry + (x1 + x2) / 2,
                        cy = k * -ry * x / rx + (y1 + y2) / 2,
                        f1 = math.asin(((y1 - cy) / ry).toFixed(9)),
                        f2 = math.asin(((y2 - cy) / ry).toFixed(9));
    
                    f1 = x1 < cx ? PI - f1 : f1;
                    f2 = x2 < cx ? PI - f2 : f2;
                    f1 < 0 && (f1 = PI * 2 + f1);
                    f2 < 0 && (f2 = PI * 2 + f2);
                    if (sweep_flag && f1 > f2) {
                        f1 = f1 - PI * 2;
                    }
                    if (!sweep_flag && f2 > f1) {
                        f2 = f2 - PI * 2;
                    }
                } else {
                    f1 = recursive[0];
                    f2 = recursive[1];
                    cx = recursive[2];
                    cy = recursive[3];
                }
                var df = f2 - f1;
                if (abs(df) > _120) {
                    var f2old = f2,
                        x2old = x2,
                        y2old = y2;
                    f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
                    x2 = cx + rx * math.cos(f2);
                    y2 = cy + ry * math.sin(f2);
                    res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
                }
                df = f2 - f1;
                var c1 = math.cos(f1),
                    s1 = math.sin(f1),
                    c2 = math.cos(f2),
                    s2 = math.sin(f2),
                    t = math.tan(df / 4),
                    hx = 4 / 3 * rx * t,
                    hy = 4 / 3 * ry * t,
                    m1 = [x1, y1],
                    m2 = [x1 + hx * s1, y1 - hy * c1],
                    m3 = [x2 + hx * s2, y2 - hy * c2],
                    m4 = [x2, y2];
                m2[0] = 2 * m1[0] - m2[0];
                m2[1] = 2 * m1[1] - m2[1];
                if (recursive) {
                    return [m2, m3, m4][concat](res);
                } else {
                    res = [m2, m3, m4][concat](res).join()[split](",");
                    var newres = [];
                    for (var i = 0, ii = res.length; i < ii; i++) {
                        newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
                    }
                    return newres;
                }
            },
            findDotAtSegment = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
                var t1 = 1 - t;
                return {
                    x: pow(t1, 3) * p1x + pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + pow(t, 3) * p2x,
                    y: pow(t1, 3) * p1y + pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + pow(t, 3) * p2y
                };
            },
            curveDim = cacher(function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
                var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
                    b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
                    c = p1x - c1x,
                    t1 = (-b + math.sqrt(b * b - 4 * a * c)) / 2 / a,
                    t2 = (-b - math.sqrt(b * b - 4 * a * c)) / 2 / a,
                    y = [p1y, p2y],
                    x = [p1x, p2x],
                    dot;
                abs(t1) > "1e12" && (t1 = .5);
                abs(t2) > "1e12" && (t2 = .5);
                if (t1 > 0 && t1 < 1) {
                    dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
                    x.push(dot.x);
                    y.push(dot.y);
                }
                if (t2 > 0 && t2 < 1) {
                    dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
                    x.push(dot.x);
                    y.push(dot.y);
                }
                a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
                b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
                c = p1y - c1y;
                t1 = (-b + math.sqrt(b * b - 4 * a * c)) / 2 / a;
                t2 = (-b - math.sqrt(b * b - 4 * a * c)) / 2 / a;
                abs(t1) > "1e12" && (t1 = .5);
                abs(t2) > "1e12" && (t2 = .5);
                if (t1 > 0 && t1 < 1) {
                    dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
                    x.push(dot.x);
                    y.push(dot.y);
                }
                if (t2 > 0 && t2 < 1) {
                    dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
                    x.push(dot.x);
                    y.push(dot.y);
                }
                return {
                    min: {x: mmin[apply](0, x), y: mmin[apply](0, y)},
                    max: {x: mmax[apply](0, x), y: mmax[apply](0, y)}
                };
            }),
            path2curve = R._path2curve = cacher(function (path, path2) {
                var pth = !path2 && paths(path);
                if (!path2 && pth.curve) {
                    return pathClone(pth.curve);
                }
                var p = pathToAbsolute(path),
                    p2 = path2 && pathToAbsolute(path2),
                    attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
                    attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
                    processPath = function (path, d, pcom) {
                        var nx, ny;
                        if (!path) {
                            return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
                        }
                        !(path[0] in {T:1, Q:1}) && (d.qx = d.qy = null);
                        switch (path[0]) {
                            case "M":
                                d.X = path[1];
                                d.Y = path[2];
                                break;
                            case "A":
                                path = ["C"][concat](a2c[apply](0, [d.x, d.y][concat](path.slice(1))));
                                break;
                            case "S":
                                if (pcom == "C" || pcom == "S") { // In "S" case we have to take into account, if the previous command is C/S.
                                    nx = d.x * 2 - d.bx;          // And reflect the previous
                                    ny = d.y * 2 - d.by;          // command's control point relative to the current point.
                                }
                                else {                            // or some else or nothing
                                    nx = d.x;
                                    ny = d.y;
                                }
                                path = ["C", nx, ny][concat](path.slice(1));
                                break;
                            case "T":
                                if (pcom == "Q" || pcom == "T") { // In "T" case we have to take into account, if the previous command is Q/T.
                                    d.qx = d.x * 2 - d.qx;        // And make a reflection similar
                                    d.qy = d.y * 2 - d.qy;        // to case "S".
                                }
                                else {                            // or something else or nothing
                                    d.qx = d.x;
                                    d.qy = d.y;
                                }
                                path = ["C"][concat](q2c(d.x, d.y, d.qx, d.qy, path[1], path[2]));
                                break;
                            case "Q":
                                d.qx = path[1];
                                d.qy = path[2];
                                path = ["C"][concat](q2c(d.x, d.y, path[1], path[2], path[3], path[4]));
                                break;
                            case "L":
                                path = ["C"][concat](l2c(d.x, d.y, path[1], path[2]));
                                break;
                            case "H":
                                path = ["C"][concat](l2c(d.x, d.y, path[1], d.y));
                                break;
                            case "V":
                                path = ["C"][concat](l2c(d.x, d.y, d.x, path[1]));
                                break;
                            case "Z":
                                path = ["C"][concat](l2c(d.x, d.y, d.X, d.Y));
                                break;
                        }
                        return path;
                    },
                    fixArc = function (pp, i) {
                        if (pp[i].length > 7) {
                            pp[i].shift();
                            var pi = pp[i];
                            while (pi.length) {
                                pp.splice(i++, 0, ["C"][concat](pi.splice(0, 6)));
                            }
                            pp.splice(i, 1);
                            ii = mmax(p.length, p2 && p2.length || 0);
                        }
                    },
                    fixM = function (path1, path2, a1, a2, i) {
                        if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
                            path2.splice(i, 0, ["M", a2.x, a2.y]);
                            a1.bx = 0;
                            a1.by = 0;
                            a1.x = path1[i][1];
                            a1.y = path1[i][2];
                            ii = mmax(p.length, p2 && p2.length || 0);
                        }
                    };
                for (var i = 0, ii = mmax(p.length, p2 && p2.length || 0); i < ii; i++) {
                    p[i] = processPath(p[i], attrs);
                    fixArc(p, i);
                    p2 && (p2[i] = processPath(p2[i], attrs2));
                    p2 && fixArc(p2, i);
                    fixM(p, p2, attrs, attrs2, i);
                    fixM(p2, p, attrs2, attrs, i);
                    var seg = p[i],
                        seg2 = p2 && p2[i],
                        seglen = seg.length,
                        seg2len = p2 && seg2.length;
                    attrs.x = seg[seglen - 2];
                    attrs.y = seg[seglen - 1];
                    attrs.bx = toFloat(seg[seglen - 4]) || attrs.x;
                    attrs.by = toFloat(seg[seglen - 3]) || attrs.y;
                    attrs2.bx = p2 && (toFloat(seg2[seg2len - 4]) || attrs2.x);
                    attrs2.by = p2 && (toFloat(seg2[seg2len - 3]) || attrs2.y);
                    attrs2.x = p2 && seg2[seg2len - 2];
                    attrs2.y = p2 && seg2[seg2len - 1];
                }
                if (!p2) {
                    pth.curve = pathClone(p);
                }
                return p2 ? [p, p2] : p;
            }, null, pathClone),
            parseDots = R._parseDots = cacher(function (gradient) {
                var dots = [];
                for (var i = 0, ii = gradient.length; i < ii; i++) {
                    var dot = {},
                        par = gradient[i].match(/^([^:]*):?([\d\.]*)/);
                    dot.color = R.getRGB(par[1]);
                    if (dot.color.error) {
                        return null;
                    }
                    dot.color = dot.color.hex;
                    par[2] && (dot.offset = par[2] + "%");
                    dots.push(dot);
                }
                for (i = 1, ii = dots.length - 1; i < ii; i++) {
                    if (!dots[i].offset) {
                        var start = toFloat(dots[i - 1].offset || 0),
                            end = 0;
                        for (var j = i + 1; j < ii; j++) {
                            if (dots[j].offset) {
                                end = dots[j].offset;
                                break;
                            }
                        }
                        if (!end) {
                            end = 100;
                            j = ii;
                        }
                        end = toFloat(end);
                        var d = (end - start) / (j - i + 1);
                        for (; i < j; i++) {
                            start += d;
                            dots[i].offset = start + "%";
                        }
                    }
                }
                return dots;
            }),
            tear = R._tear = function (el, paper) {
                el == paper.top && (paper.top = el.prev);
                el == paper.bottom && (paper.bottom = el.next);
                el.next && (el.next.prev = el.prev);
                el.prev && (el.prev.next = el.next);
            },
            tofront = R._tofront = function (el, paper) {
                if (paper.top === el) {
                    return;
                }
                tear(el, paper);
                el.next = null;
                el.prev = paper.top;
                paper.top.next = el;
                paper.top = el;
            },
            toback = R._toback = function (el, paper) {
                if (paper.bottom === el) {
                    return;
                }
                tear(el, paper);
                el.next = paper.bottom;
                el.prev = null;
                paper.bottom.prev = el;
                paper.bottom = el;
            },
            insertafter = R._insertafter = function (el, el2, paper) {
                tear(el, paper);
                el2 == paper.top && (paper.top = el);
                el2.next && (el2.next.prev = el);
                el.next = el2.next;
                el.prev = el2;
                el2.next = el;
            },
            insertbefore = R._insertbefore = function (el, el2, paper) {
                tear(el, paper);
                el2 == paper.bottom && (paper.bottom = el);
                el2.prev && (el2.prev.next = el);
                el.prev = el2.prev;
                el2.prev = el;
                el.next = el2;
            },
            /*\
             * Raphael.toMatrix
             [ method ]
             **
             * Utility method
             **
             * Returns matrix of transformations applied to a given path
             > Parameters
             - path (string) path string
             - transform (string|array) transformation string
             = (object) @Matrix
            \*/
            toMatrix = R.toMatrix = function (path, transform) {
                var bb = pathDimensions(path),
                    el = {
                        _: {
                            transform: E
                        },
                        getBBox: function () {
                            return bb;
                        }
                    };
                extractTransform(el, transform);
                return el.matrix;
            },
            /*\
             * Raphael.transformPath
             [ method ]
             **
             * Utility method
             **
             * Returns path transformed by a given transformation
             > Parameters
             - path (string) path string
             - transform (string|array) transformation string
             = (string) path
            \*/
            transformPath = R.transformPath = function (path, transform) {
                return mapPath(path, toMatrix(path, transform));
            },
            extractTransform = R._extractTransform = function (el, tstr) {
                if (tstr == null) {
                    return el._.transform;
                }
                tstr = Str(tstr).replace(/\.{3}|\u2026/g, el._.transform || E);
                var tdata = R.parseTransformString(tstr),
                    deg = 0,
                    dx = 0,
                    dy = 0,
                    sx = 1,
                    sy = 1,
                    _ = el._,
                    m = new Matrix;
                _.transform = tdata || [];
                if (tdata) {
                    for (var i = 0, ii = tdata.length; i < ii; i++) {
                        var t = tdata[i],
                            tlen = t.length,
                            command = Str(t[0]).toLowerCase(),
                            absolute = t[0] != command,
                            inver = absolute ? m.invert() : 0,
                            x1,
                            y1,
                            x2,
                            y2,
                            bb;
                        if (command == "t" && tlen == 3) {
                            if (absolute) {
                                x1 = inver.x(0, 0);
                                y1 = inver.y(0, 0);
                                x2 = inver.x(t[1], t[2]);
                                y2 = inver.y(t[1], t[2]);
                                m.translate(x2 - x1, y2 - y1);
                            } else {
                                m.translate(t[1], t[2]);
                            }
                        } else if (command == "r") {
                            if (tlen == 2) {
                                bb = bb || el.getBBox(1);
                                m.rotate(t[1], bb.x + bb.width / 2, bb.y + bb.height / 2);
                                deg += t[1];
                            } else if (tlen == 4) {
                                if (absolute) {
                                    x2 = inver.x(t[2], t[3]);
                                    y2 = inver.y(t[2], t[3]);
                                    m.rotate(t[1], x2, y2);
                                } else {
                                    m.rotate(t[1], t[2], t[3]);
                                }
                                deg += t[1];
                            }
                        } else if (command == "s") {
                            if (tlen == 2 || tlen == 3) {
                                bb = bb || el.getBBox(1);
                                m.scale(t[1], t[tlen - 1], bb.x + bb.width / 2, bb.y + bb.height / 2);
                                sx *= t[1];
                                sy *= t[tlen - 1];
                            } else if (tlen == 5) {
                                if (absolute) {
                                    x2 = inver.x(t[3], t[4]);
                                    y2 = inver.y(t[3], t[4]);
                                    m.scale(t[1], t[2], x2, y2);
                                } else {
                                    m.scale(t[1], t[2], t[3], t[4]);
                                }
                                sx *= t[1];
                                sy *= t[2];
                            }
                        } else if (command == "m" && tlen == 7) {
                            m.add(t[1], t[2], t[3], t[4], t[5], t[6]);
                        }
                        _.dirtyT = 1;
                        el.matrix = m;
                    }
                }
    
                /*\
                 * Element.matrix
                 [ property (object) ]
                 **
                 * Keeps @Matrix object, which represents element transformation
                \*/
                el.matrix = m;
    
                _.sx = sx;
                _.sy = sy;
                _.deg = deg;
                _.dx = dx = m.e;
                _.dy = dy = m.f;
    
                if (sx == 1 && sy == 1 && !deg && _.bbox) {
                    _.bbox.x += +dx;
                    _.bbox.y += +dy;
                } else {
                    _.dirtyT = 1;
                }
            },
            getEmpty = function (item) {
                var l = item[0];
                switch (l.toLowerCase()) {
                    case "t": return [l, 0, 0];
                    case "m": return [l, 1, 0, 0, 1, 0, 0];
                    case "r": if (item.length == 4) {
                        return [l, 0, item[2], item[3]];
                    } else {
                        return [l, 0];
                    }
                    case "s": if (item.length == 5) {
                        return [l, 1, 1, item[3], item[4]];
                    } else if (item.length == 3) {
                        return [l, 1, 1];
                    } else {
                        return [l, 1];
                    }
                }
            },
            equaliseTransform = R._equaliseTransform = function (t1, t2) {
                t2 = Str(t2).replace(/\.{3}|\u2026/g, t1);
                t1 = R.parseTransformString(t1) || [];
                t2 = R.parseTransformString(t2) || [];
                var maxlength = mmax(t1.length, t2.length),
                    from = [],
                    to = [],
                    i = 0, j, jj,
                    tt1, tt2;
                for (; i < maxlength; i++) {
                    tt1 = t1[i] || getEmpty(t2[i]);
                    tt2 = t2[i] || getEmpty(tt1);
                    if ((tt1[0] != tt2[0]) ||
                        (tt1[0].toLowerCase() == "r" && (tt1[2] != tt2[2] || tt1[3] != tt2[3])) ||
                        (tt1[0].toLowerCase() == "s" && (tt1[3] != tt2[3] || tt1[4] != tt2[4]))
                        ) {
                        return;
                    }
                    from[i] = [];
                    to[i] = [];
                    for (j = 0, jj = mmax(tt1.length, tt2.length); j < jj; j++) {
                        j in tt1 && (from[i][j] = tt1[j]);
                        j in tt2 && (to[i][j] = tt2[j]);
                    }
                }
                return {
                    from: from,
                    to: to
                };
            };
        R._getContainer = function (x, y, w, h) {
            var container;
            container = h == null && !R.is(x, "object") ? g.doc.getElementById(x) : x;
            if (container == null) {
                return;
            }
            if (container.tagName) {
                if (y == null) {
                    return {
                        container: container,
                        width: container.style.pixelWidth || container.offsetWidth,
                        height: container.style.pixelHeight || container.offsetHeight
                    };
                } else {
                    return {
                        container: container,
                        width: y,
                        height: w
                    };
                }
            }
            return {
                container: 1,
                x: x,
                y: y,
                width: w,
                height: h
            };
        };
        /*\
         * Raphael.pathToRelative
         [ method ]
         **
         * Utility method
         **
         * Converts path to relative form
         > Parameters
         - pathString (string|array) path string or array of segments
         = (array) array of segments.
        \*/
        R.pathToRelative = pathToRelative;
        R._engine = {};
        /*\
         * Raphael.path2curve
         [ method ]
         **
         * Utility method
         **
         * Converts path to a new path where all segments are cubic bezier curves.
         > Parameters
         - pathString (string|array) path string or array of segments
         = (array) array of segments.
        \*/
        R.path2curve = path2curve;
        /*\
         * Raphael.matrix
         [ method ]
         **
         * Utility method
         **
         * Returns matrix based on given parameters.
         > Parameters
         - a (number)
         - b (number)
         - c (number)
         - d (number)
         - e (number)
         - f (number)
         = (object) @Matrix
        \*/
        R.matrix = function (a, b, c, d, e, f) {
            return new Matrix(a, b, c, d, e, f);
        };
        function Matrix(a, b, c, d, e, f) {
            if (a != null) {
                this.a = +a;
                this.b = +b;
                this.c = +c;
                this.d = +d;
                this.e = +e;
                this.f = +f;
            } else {
                this.a = 1;
                this.b = 0;
                this.c = 0;
                this.d = 1;
                this.e = 0;
                this.f = 0;
            }
        }
        (function (matrixproto) {
            /*\
             * Matrix.add
             [ method ]
             **
             * Adds given matrix to existing one.
             > Parameters
             - a (number)
             - b (number)
             - c (number)
             - d (number)
             - e (number)
             - f (number)
             or
             - matrix (object) @Matrix
            \*/
            matrixproto.add = function (a, b, c, d, e, f) {
                var out = [[], [], []],
                    m = [[this.a, this.c, this.e], [this.b, this.d, this.f], [0, 0, 1]],
                    matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
                    x, y, z, res;
    
                if (a && a instanceof Matrix) {
                    matrix = [[a.a, a.c, a.e], [a.b, a.d, a.f], [0, 0, 1]];
                }
    
                for (x = 0; x < 3; x++) {
                    for (y = 0; y < 3; y++) {
                        res = 0;
                        for (z = 0; z < 3; z++) {
                            res += m[x][z] * matrix[z][y];
                        }
                        out[x][y] = res;
                    }
                }
                this.a = out[0][0];
                this.b = out[1][0];
                this.c = out[0][1];
                this.d = out[1][1];
                this.e = out[0][2];
                this.f = out[1][2];
            };
            /*\
             * Matrix.invert
             [ method ]
             **
             * Returns inverted version of the matrix
             = (object) @Matrix
            \*/
            matrixproto.invert = function () {
                var me = this,
                    x = me.a * me.d - me.b * me.c;
                return new Matrix(me.d / x, -me.b / x, -me.c / x, me.a / x, (me.c * me.f - me.d * me.e) / x, (me.b * me.e - me.a * me.f) / x);
            };
            /*\
             * Matrix.clone
             [ method ]
             **
             * Returns copy of the matrix
             = (object) @Matrix
            \*/
            matrixproto.clone = function () {
                return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f);
            };
            /*\
             * Matrix.translate
             [ method ]
             **
             * Translate the matrix
             > Parameters
             - x (number)
             - y (number)
            \*/
            matrixproto.translate = function (x, y) {
                this.add(1, 0, 0, 1, x, y);
            };
            /*\
             * Matrix.scale
             [ method ]
             **
             * Scales the matrix
             > Parameters
             - x (number)
             - y (number) #optional
             - cx (number) #optional
             - cy (number) #optional
            \*/
            matrixproto.scale = function (x, y, cx, cy) {
                y == null && (y = x);
                (cx || cy) && this.add(1, 0, 0, 1, cx, cy);
                this.add(x, 0, 0, y, 0, 0);
                (cx || cy) && this.add(1, 0, 0, 1, -cx, -cy);
            };
            /*\
             * Matrix.rotate
             [ method ]
             **
             * Rotates the matrix
             > Parameters
             - a (number)
             - x (number)
             - y (number)
            \*/
            matrixproto.rotate = function (a, x, y) {
                a = R.rad(a);
                x = x || 0;
                y = y || 0;
                var cos = +math.cos(a).toFixed(9),
                    sin = +math.sin(a).toFixed(9);
                this.add(cos, sin, -sin, cos, x, y);
                this.add(1, 0, 0, 1, -x, -y);
            };
            /*\
             * Matrix.x
             [ method ]
             **
             * Return x coordinate for given point after transformation described by the matrix. See also @Matrix.y
             > Parameters
             - x (number)
             - y (number)
             = (number) x
            \*/
            matrixproto.x = function (x, y) {
                return x * this.a + y * this.c + this.e;
            };
            /*\
             * Matrix.y
             [ method ]
             **
             * Return y coordinate for given point after transformation described by the matrix. See also @Matrix.x
             > Parameters
             - x (number)
             - y (number)
             = (number) y
            \*/
            matrixproto.y = function (x, y) {
                return x * this.b + y * this.d + this.f;
            };
            matrixproto.get = function (i) {
                return +this[Str.fromCharCode(97 + i)].toFixed(4);
            };
            matrixproto.toString = function () {
                return R.svg ?
                    "matrix(" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)].join() + ")" :
                    [this.get(0), this.get(2), this.get(1), this.get(3), 0, 0].join();
            };
            matrixproto.toFilter = function () {
                return "progid:DXImageTransform.Microsoft.Matrix(M11=" + this.get(0) +
                    ", M12=" + this.get(2) + ", M21=" + this.get(1) + ", M22=" + this.get(3) +
                    ", Dx=" + this.get(4) + ", Dy=" + this.get(5) + ", sizingmethod='auto expand')";
            };
            matrixproto.offset = function () {
                return [this.e.toFixed(4), this.f.toFixed(4)];
            };
            function norm(a) {
                return a[0] * a[0] + a[1] * a[1];
            }
            function normalize(a) {
                var mag = math.sqrt(norm(a));
                a[0] && (a[0] /= mag);
                a[1] && (a[1] /= mag);
            }
            /*\
             * Matrix.split
             [ method ]
             **
             * Splits matrix into primitive transformations
             = (object) in format:
             o dx (number) translation by x
             o dy (number) translation by y
             o scalex (number) scale by x
             o scaley (number) scale by y
             o shear (number) shear
             o rotate (number) rotation in deg
             o isSimple (boolean) could it be represented via simple transformations
            \*/
            matrixproto.split = function () {
                var out = {};
                // translation
                out.dx = this.e;
                out.dy = this.f;
    
                // scale and shear
                var row = [[this.a, this.c], [this.b, this.d]];
                out.scalex = math.sqrt(norm(row[0]));
                normalize(row[0]);
    
                out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
                row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
    
                out.scaley = math.sqrt(norm(row[1]));
                normalize(row[1]);
                out.shear /= out.scaley;
    
                // rotation
                var sin = -row[0][1],
                    cos = row[1][1];
                if (cos < 0) {
                    out.rotate = R.deg(math.acos(cos));
                    if (sin < 0) {
                        out.rotate = 360 - out.rotate;
                    }
                } else {
                    out.rotate = R.deg(math.asin(sin));
                }
    
                out.isSimple = !+out.shear.toFixed(9) && (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate);
                out.isSuperSimple = !+out.shear.toFixed(9) && out.scalex.toFixed(9) == out.scaley.toFixed(9) && !out.rotate;
                out.noRotation = !+out.shear.toFixed(9) && !out.rotate;
                return out;
            };
            /*\
             * Matrix.toTransformString
             [ method ]
             **
             * Return transform string that represents given matrix
             = (string) transform string
            \*/
            matrixproto.toTransformString = function (shorter) {
                var s = shorter || this[split]();
                if (s.isSimple) {
                    s.scalex = +s.scalex.toFixed(4);
                    s.scaley = +s.scaley.toFixed(4);
                    s.rotate = +s.rotate.toFixed(4);
                    return  (s.dx || s.dy ? "t" + [s.dx, s.dy] : E) +
                            (s.scalex != 1 || s.scaley != 1 ? "s" + [s.scalex, s.scaley, 0, 0] : E) +
                            (s.rotate ? "r" + [s.rotate, 0, 0] : E);
                } else {
                    return "m" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)];
                }
            };
        })(Matrix.prototype);
    
        // WebKit rendering bug workaround method
        var version = navigator.userAgent.match(/Version\/(.*?)\s/) || navigator.userAgent.match(/Chrome\/(\d+)/);
        if ((navigator.vendor == "Apple Computer, Inc.") && (version && version[1] < 4 || navigator.platform.slice(0, 2) == "iP") ||
            (navigator.vendor == "Google Inc." && version && version[1] < 8)) {
            /*\
             * Paper.safari
             [ method ]
             **
             * There is an inconvenient rendering bug in Safari (WebKit):
             * sometimes the rendering should be forced.
             * This method should help with dealing with this bug.
            \*/
            paperproto.safari = function () {
                var rect = this.rect(-99, -99, this.width + 99, this.height + 99).attr({stroke: "none"});
                setTimeout(function () {rect.remove();});
            };
        } else {
            paperproto.safari = fun;
        }
    
        var preventDefault = function () {
            this.returnValue = false;
        },
        preventTouch = function () {
            return this.originalEvent.preventDefault();
        },
        stopPropagation = function () {
            this.cancelBubble = true;
        },
        stopTouch = function () {
            return this.originalEvent.stopPropagation();
        },
        getEventPosition = function (e) {
            var scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
                scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft;
    
            return {
                x: e.clientX + scrollX,
                y: e.clientY + scrollY
            };
        },
        addEvent = (function () {
            if (g.doc.addEventListener) {
                return function (obj, type, fn, element) {
                    var f = function (e) {
                        var pos = getEventPosition(e);
                        return fn.call(element, e, pos.x, pos.y);
                    };
                    obj.addEventListener(type, f, false);
    
                    if (supportsTouch && touchMap[type]) {
                        var _f = function (e) {
                            var pos = getEventPosition(e),
                                olde = e;
    
                            for (var i = 0, ii = e.targetTouches && e.targetTouches.length; i < ii; i++) {
                                if (e.targetTouches[i].target == obj) {
                                    e = e.targetTouches[i];
                                    e.originalEvent = olde;
                                    e.preventDefault = preventTouch;
                                    e.stopPropagation = stopTouch;
                                    break;
                                }
                            }
    
                            return fn.call(element, e, pos.x, pos.y);
                        };
                        obj.addEventListener(touchMap[type], _f, false);
                    }
    
                    return function () {
                        obj.removeEventListener(type, f, false);
    
                        if (supportsTouch && touchMap[type])
                            obj.removeEventListener(touchMap[type], f, false);
    
                        return true;
                    };
                };
            } else if (g.doc.attachEvent) {
                return function (obj, type, fn, element) {
                    var f = function (e) {
                        e = e || g.win.event;
                        var scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
                            scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft,
                            x = e.clientX + scrollX,
                            y = e.clientY + scrollY;
                        e.preventDefault = e.preventDefault || preventDefault;
                        e.stopPropagation = e.stopPropagation || stopPropagation;
                        return fn.call(element, e, x, y);
                    };
                    obj.attachEvent("on" + type, f);
                    var detacher = function () {
                        obj.detachEvent("on" + type, f);
                        return true;
                    };
                    return detacher;
                };
            }
        })(),
        drag = [],
        dragMove = function (e) {
            var x = e.clientX,
                y = e.clientY,
                scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
                scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft,
                dragi,
                j = drag.length;
            while (j--) {
                dragi = drag[j];
                if (supportsTouch && e.touches) {
                    var i = e.touches.length,
                        touch;
                    while (i--) {
                        touch = e.touches[i];
                        if (touch.identifier == dragi.el._drag.id) {
                            x = touch.clientX;
                            y = touch.clientY;
                            (e.originalEvent ? e.originalEvent : e).preventDefault();
                            break;
                        }
                    }
                } else {
                    e.preventDefault();
                }
                var node = dragi.el.node,
                    o,
                    next = node.nextSibling,
                    parent = node.parentNode,
                    display = node.style.display;
                g.win.opera && parent.removeChild(node);
                node.style.display = "none";
                o = dragi.el.paper.getElementByPoint(x, y);
                node.style.display = display;
                g.win.opera && (next ? parent.insertBefore(node, next) : parent.appendChild(node));
                o && eve("raphael.drag.over." + dragi.el.id, dragi.el, o);
                x += scrollX;
                y += scrollY;
                eve("raphael.drag.move." + dragi.el.id, dragi.move_scope || dragi.el, x - dragi.el._drag.x, y - dragi.el._drag.y, x, y, e);
            }
        },
        dragUp = function (e) {
            R.unmousemove(dragMove).unmouseup(dragUp);
            var i = drag.length,
                dragi;
            while (i--) {
                dragi = drag[i];
                dragi.el._drag = {};
                eve("raphael.drag.end." + dragi.el.id, dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, e);
            }
            drag = [];
        },
        /*\
         * Raphael.el
         [ property (object) ]
         **
         * You can add your own method to elements. This is usefull when you want to hack default functionality or
         * want to wrap some common transformation or attributes in one method. In difference to canvas methods,
         * you can redefine element method at any time. Expending element methods wouldn’t affect set.
         > Usage
         | Raphael.el.red = function () {
         |     this.attr({fill: "#f00"});
         | };
         | // then use it
         | paper.circle(100, 100, 20).red();
        \*/
        elproto = R.el = {};
        /*\
         * Element.click
         [ method ]
         **
         * Adds event handler for click for the element.
         > Parameters
         - handler (function) handler for the event
         = (object) @Element
        \*/
        /*\
         * Element.unclick
         [ method ]
         **
         * Removes event handler for click for the element.
         > Parameters
         - handler (function) #optional handler for the event
         = (object) @Element
        \*/
    
        /*\
         * Element.dblclick
         [ method ]
         **
         * Adds event handler for double click for the element.
         > Parameters
         - handler (function) handler for the event
         = (object) @Element
        \*/
        /*\
         * Element.undblclick
         [ method ]
         **
         * Removes event handler for double click for the element.
         > Parameters
         - handler (function) #optional handler for the event
         = (object) @Element
        \*/
    
        /*\
         * Element.mousedown
         [ method ]
         **
         * Adds event handler for mousedown for the element.
         > Parameters
         - handler (function) handler for the event
         = (object) @Element
        \*/
        /*\
         * Element.unmousedown
         [ method ]
         **
         * Removes event handler for mousedown for the element.
         > Parameters
         - handler (function) #optional handler for the event
         = (object) @Element
        \*/
    
        /*\
         * Element.mousemove
         [ method ]
         **
         * Adds event handler for mousemove for the element.
         > Parameters
         - handler (function) handler for the event
         = (object) @Element
        \*/
        /*\
         * Element.unmousemove
         [ method ]
         **
         * Removes event handler for mousemove for the element.
         > Parameters
         - handler (function) #optional handler for the event
         = (object) @Element
        \*/
    
        /*\
         * Element.mouseout
         [ method ]
         **
         * Adds event handler for mouseout for the element.
         > Parameters
         - handler (function) handler for the event
         = (object) @Element
        \*/
        /*\
         * Element.unmouseout
         [ method ]
         **
         * Removes event handler for mouseout for the element.
         > Parameters
         - handler (function) #optional handler for the event
         = (object) @Element
        \*/
    
        /*\
         * Element.mouseover
         [ method ]
         **
         * Adds event handler for mouseover for the element.
         > Parameters
         - handler (function) handler for the event
         = (object) @Element
        \*/
        /*\
         * Element.unmouseover
         [ method ]
         **
         * Removes event handler for mouseover for the element.
         > Parameters
         - handler (function) #optional handler for the event
         = (object) @Element
        \*/
    
        /*\
         * Element.mouseup
         [ method ]
         **
         * Adds event handler for mouseup for the element.
         > Parameters
         - handler (function) handler for the event
         = (object) @Element
        \*/
        /*\
         * Element.unmouseup
         [ method ]
         **
         * Removes event handler for mouseup for the element.
         > Parameters
         - handler (function) #optional handler for the event
         = (object) @Element
        \*/
    
        /*\
         * Element.touchstart
         [ method ]
         **
         * Adds event handler for touchstart for the element.
         > Parameters
         - handler (function) handler for the event
         = (object) @Element
        \*/
        /*\
         * Element.untouchstart
         [ method ]
         **
         * Removes event handler for touchstart for the element.
         > Parameters
         - handler (function) #optional handler for the event
         = (object) @Element
        \*/
    
        /*\
         * Element.touchmove
         [ method ]
         **
         * Adds event handler for touchmove for the element.
         > Parameters
         - handler (function) handler for the event
         = (object) @Element
        \*/
        /*\
         * Element.untouchmove
         [ method ]
         **
         * Removes event handler for touchmove for the element.
         > Parameters
         - handler (function) #optional handler for the event
         = (object) @Element
        \*/
    
        /*\
         * Element.touchend
         [ method ]
         **
         * Adds event handler for touchend for the element.
         > Parameters
         - handler (function) handler for the event
         = (object) @Element
        \*/
        /*\
         * Element.untouchend
         [ method ]
         **
         * Removes event handler for touchend for the element.
         > Parameters
         - handler (function) #optional handler for the event
         = (object) @Element
        \*/
    
        /*\
         * Element.touchcancel
         [ method ]
         **
         * Adds event handler for touchcancel for the element.
         > Parameters
         - handler (function) handler for the event
         = (object) @Element
        \*/
        /*\
         * Element.untouchcancel
         [ method ]
         **
         * Removes event handler for touchcancel for the element.
         > Parameters
         - handler (function) #optional handler for the event
         = (object) @Element
        \*/
        for (var i = events.length; i--;) {
            (function (eventName) {
                R[eventName] = elproto[eventName] = function (fn, scope) {
                    if (R.is(fn, "function")) {
                        this.events = this.events || [];
                        this.events.push({name: eventName, f: fn, unbind: addEvent(this.shape || this.node || g.doc, eventName, fn, scope || this)});
                    }
                    return this;
                };
                R["un" + eventName] = elproto["un" + eventName] = function (fn) {
                    var events = this.events || [],
                        l = events.length;
                    while (l--){
                        if (events[l].name == eventName && (R.is(fn, "undefined") || events[l].f == fn)) {
                            events[l].unbind();
                            events.splice(l, 1);
                            !events.length && delete this.events;
                        }
                    }
                    return this;
                };
            })(events[i]);
        }
    
        /*\
         * Element.data
         [ method ]
         **
         * Adds or retrieves given value asociated with given key.
         ** 
         * See also @Element.removeData
         > Parameters
         - key (string) key to store data
         - value (any) #optional value to store
         = (object) @Element
         * or, if value is not specified:
         = (any) value
         * or, if key and value are not specified:
         = (object) Key/value pairs for all the data associated with the element.
         > Usage
         | for (var i = 0, i < 5, i++) {
         |     paper.circle(10 + 15 * i, 10, 10)
         |          .attr({fill: "#000"})
         |          .data("i", i)
         |          .click(function () {
         |             alert(this.data("i"));
         |          });
         | }
        \*/
        elproto.data = function (key, value) {
            var data = eldata[this.id] = eldata[this.id] || {};
            if (arguments.length == 0) {
                return data;
            }
            if (arguments.length == 1) {
                if (R.is(key, "object")) {
                    for (var i in key) if (key[has](i)) {
                        this.data(i, key[i]);
                    }
                    return this;
                }
                eve("raphael.data.get." + this.id, this, data[key], key);
                return data[key];
            }
            data[key] = value;
            eve("raphael.data.set." + this.id, this, value, key);
            return this;
        };
        /*\
         * Element.removeData
         [ method ]
         **
         * Removes value associated with an element by given key.
         * If key is not provided, removes all the data of the element.
         > Parameters
         - key (string) #optional key
         = (object) @Element
        \*/
        elproto.removeData = function (key) {
            if (key == null) {
                eldata[this.id] = {};
            } else {
                eldata[this.id] && delete eldata[this.id][key];
            }
            return this;
        };
         /*\
         * Element.getData
         [ method ]
         **
         * Retrieves the element data
         = (object) data
        \*/
        elproto.getData = function () {
            return clone(eldata[this.id] || {});
        };
        /*\
         * Element.hover
         [ method ]
         **
         * Adds event handlers for hover for the element.
         > Parameters
         - f_in (function) handler for hover in
         - f_out (function) handler for hover out
         - icontext (object) #optional context for hover in handler
         - ocontext (object) #optional context for hover out handler
         = (object) @Element
        \*/
        elproto.hover = function (f_in, f_out, scope_in, scope_out) {
            return this.mouseover(f_in, scope_in).mouseout(f_out, scope_out || scope_in);
        };
        /*\
         * Element.unhover
         [ method ]
         **
         * Removes event handlers for hover for the element.
         > Parameters
         - f_in (function) handler for hover in
         - f_out (function) handler for hover out
         = (object) @Element
        \*/
        elproto.unhover = function (f_in, f_out) {
            return this.unmouseover(f_in).unmouseout(f_out);
        };
        var draggable = [];
        /*\
         * Element.drag
         [ method ]
         **
         * Adds event handlers for drag of the element.
         > Parameters
         - onmove (function) handler for moving
         - onstart (function) handler for drag start
         - onend (function) handler for drag end
         - mcontext (object) #optional context for moving handler
         - scontext (object) #optional context for drag start handler
         - econtext (object) #optional context for drag end handler
         * Additionaly following `drag` events will be triggered: `drag.start.<id>` on start, 
         * `drag.end.<id>` on end and `drag.move.<id>` on every move. When element will be dragged over another element 
         * `drag.over.<id>` will be fired as well.
         *
         * Start event and start handler will be called in specified context or in context of the element with following parameters:
         o x (number) x position of the mouse
         o y (number) y position of the mouse
         o event (object) DOM event object
         * Move event and move handler will be called in specified context or in context of the element with following parameters:
         o dx (number) shift by x from the start point
         o dy (number) shift by y from the start point
         o x (number) x position of the mouse
         o y (number) y position of the mouse
         o event (object) DOM event object
         * End event and end handler will be called in specified context or in context of the element with following parameters:
         o event (object) DOM event object
         = (object) @Element
        \*/
        elproto.drag = function (onmove, onstart, onend, move_scope, start_scope, end_scope) {
            function start(e) {
                (e.originalEvent || e).preventDefault();
                var x = e.clientX,
                    y = e.clientY,
                    scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
                    scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft;
                this._drag.id = e.identifier;
                if (supportsTouch && e.touches) {
                    var i = e.touches.length, touch;
                    while (i--) {
                        touch = e.touches[i];
                        this._drag.id = touch.identifier;
                        if (touch.identifier == this._drag.id) {
                            x = touch.clientX;
                            y = touch.clientY;
                            break;
                        }
                    }
                }
                this._drag.x = x + scrollX;
                this._drag.y = y + scrollY;
                !drag.length && R.mousemove(dragMove).mouseup(dragUp);
                drag.push({el: this, move_scope: move_scope, start_scope: start_scope, end_scope: end_scope});
                onstart && eve.on("raphael.drag.start." + this.id, onstart);
                onmove && eve.on("raphael.drag.move." + this.id, onmove);
                onend && eve.on("raphael.drag.end." + this.id, onend);
                eve("raphael.drag.start." + this.id, start_scope || move_scope || this, e.clientX + scrollX, e.clientY + scrollY, e);
            }
            this._drag = {};
            draggable.push({el: this, start: start});
            this.mousedown(start);
            return this;
        };
        /*\
         * Element.onDragOver
         [ method ]
         **
         * Shortcut for assigning event handler for `drag.over.<id>` event, where id is id of the element (see @Element.id).
         > Parameters
         - f (function) handler for event, first argument would be the element you are dragging over
        \*/
        elproto.onDragOver = function (f) {
            f ? eve.on("raphael.drag.over." + this.id, f) : eve.unbind("raphael.drag.over." + this.id);
        };
        /*\
         * Element.undrag
         [ method ]
         **
         * Removes all drag event handlers from given element.
        \*/
        elproto.undrag = function () {
            var i = draggable.length;
            while (i--) if (draggable[i].el == this) {
                this.unmousedown(draggable[i].start);
                draggable.splice(i, 1);
                eve.unbind("raphael.drag.*." + this.id);
            }
            !draggable.length && R.unmousemove(dragMove).unmouseup(dragUp);
            drag = [];
        };
        /*\
         * Paper.circle
         [ method ]
         **
         * Draws a circle.
         **
         > Parameters
         **
         - x (number) x coordinate of the centre
         - y (number) y coordinate of the centre
         - r (number) radius
         = (object) Raphaël element object with type “circle”
         **
         > Usage
         | var c = paper.circle(50, 50, 40);
        \*/
        paperproto.circle = function (x, y, r) {
            var out = R._engine.circle(this, x || 0, y || 0, r || 0);
            this.__set__ && this.__set__.push(out);
            return out;
        };
        /*\
         * Paper.rect
         [ method ]
         *
         * Draws a rectangle.
         **
         > Parameters
         **
         - x (number) x coordinate of the top left corner
         - y (number) y coordinate of the top left corner
         - width (number) width
         - height (number) height
         - r (number) #optional radius for rounded corners, default is 0
         = (object) Raphaël element object with type “rect”
         **
         > Usage
         | // regular rectangle
         | var c = paper.rect(10, 10, 50, 50);
         | // rectangle with rounded corners
         | var c = paper.rect(40, 40, 50, 50, 10);
        \*/
        paperproto.rect = function (x, y, w, h, r) {
            var out = R._engine.rect(this, x || 0, y || 0, w || 0, h || 0, r || 0);
            this.__set__ && this.__set__.push(out);
            return out;
        };
        /*\
         * Paper.ellipse
         [ method ]
         **
         * Draws an ellipse.
         **
         > Parameters
         **
         - x (number) x coordinate of the centre
         - y (number) y coordinate of the centre
         - rx (number) horizontal radius
         - ry (number) vertical radius
         = (object) Raphaël element object with type “ellipse”
         **
         > Usage
         | var c = paper.ellipse(50, 50, 40, 20);
        \*/
        paperproto.ellipse = function (x, y, rx, ry) {
            var out = R._engine.ellipse(this, x || 0, y || 0, rx || 0, ry || 0);
            this.__set__ && this.__set__.push(out);
            return out;
        };
        /*\
         * Paper.path
         [ method ]
         **
         * Creates a path element by given path data string.
         > Parameters
         - pathString (string) #optional path string in SVG format.
         * Path string consists of one-letter commands, followed by comma seprarated arguments in numercal form. Example:
         | "M10,20L30,40"
         * Here we can see two commands: “M”, with arguments `(10, 20)` and “L” with arguments `(30, 40)`. Upper case letter mean command is absolute, lower case—relative.
         *
         # <p>Here is short list of commands available, for more details see <a href="http://www.w3.org/TR/SVG/paths.html#PathData" title="Details of a path's data attribute's format are described in the SVG specification.">SVG path string format</a>.</p>
         # <table><thead><tr><th>Command</th><th>Name</th><th>Parameters</th></tr></thead><tbody>
         # <tr><td>M</td><td>moveto</td><td>(x y)+</td></tr>
         # <tr><td>Z</td><td>closepath</td><td>(none)</td></tr>
         # <tr><td>L</td><td>lineto</td><td>(x y)+</td></tr>
         # <tr><td>H</td><td>horizontal lineto</td><td>x+</td></tr>
         # <tr><td>V</td><td>vertical lineto</td><td>y+</td></tr>
         # <tr><td>C</td><td>curveto</td><td>(x1 y1 x2 y2 x y)+</td></tr>
         # <tr><td>S</td><td>smooth curveto</td><td>(x2 y2 x y)+</td></tr>
         # <tr><td>Q</td><td>quadratic Bézier curveto</td><td>(x1 y1 x y)+</td></tr>
         # <tr><td>T</td><td>smooth quadratic Bézier curveto</td><td>(x y)+</td></tr>
         # <tr><td>A</td><td>elliptical arc</td><td>(rx ry x-axis-rotation large-arc-flag sweep-flag x y)+</td></tr>
         # <tr><td>R</td><td><a href="http://en.wikipedia.org/wiki/Catmull–Rom_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom curveto</a>*</td><td>x1 y1 (x y)+</td></tr></tbody></table>
         * * “Catmull-Rom curveto” is a not standard SVG command and added in 2.0 to make life easier.
         * Note: there is a special case when path consist of just three commands: “M10,10R…z”. In this case path will smoothly connects to its beginning.
         > Usage
         | var c = paper.path("M10 10L90 90");
         | // draw a diagonal line:
         | // move to 10,10, line to 90,90
         * For example of path strings, check out these icons: http://raphaeljs.com/icons/
        \*/
        paperproto.path = function (pathString) {
            pathString && !R.is(pathString, string) && !R.is(pathString[0], array) && (pathString += E);
            var out = R._engine.path(R.format[apply](R, arguments), this);
            this.__set__ && this.__set__.push(out);
            return out;
        };
        /*\
         * Paper.image
         [ method ]
         **
         * Embeds an image into the surface.
         **
         > Parameters
         **
         - src (string) URI of the source image
         - x (number) x coordinate position
         - y (number) y coordinate position
         - width (number) width of the image
         - height (number) height of the image
         = (object) Raphaël element object with type “image”
         **
         > Usage
         | var c = paper.image("apple.png", 10, 10, 80, 80);
        \*/
        paperproto.image = function (src, x, y, w, h) {
            var out = R._engine.image(this, src || "about:blank", x || 0, y || 0, w || 0, h || 0);
            this.__set__ && this.__set__.push(out);
            return out;
        };
        /*\
         * Paper.text
         [ method ]
         **
         * Draws a text string. If you need line breaks, put “\n” in the string.
         **
         > Parameters
         **
         - x (number) x coordinate position
         - y (number) y coordinate position
         - text (string) The text string to draw
         = (object) Raphaël element object with type “text”
         **
         > Usage
         | var t = paper.text(50, 50, "Raphaël\nkicks\nbutt!");
        \*/
        paperproto.text = function (x, y, text) {
            var out = R._engine.text(this, x || 0, y || 0, Str(text));
            this.__set__ && this.__set__.push(out);
            return out;
        };
        /*\
         * Paper.set
         [ method ]
         **
         * Creates array-like object to keep and operate several elements at once.
         * Warning: it doesn’t create any elements for itself in the page, it just groups existing elements.
         * Sets act as pseudo elements — all methods available to an element can be used on a set.
         = (object) array-like object that represents set of elements
         **
         > Usage
         | var st = paper.set();
         | st.push(
         |     paper.circle(10, 10, 5),
         |     paper.circle(30, 10, 5)
         | );
         | st.attr({fill: "red"}); // changes the fill of both circles
        \*/
        paperproto.set = function (itemsArray) {
            !R.is(itemsArray, "array") && (itemsArray = Array.prototype.splice.call(arguments, 0, arguments.length));
            var out = new Set(itemsArray);
            this.__set__ && this.__set__.push(out);
            out["paper"] = this;
            out["type"] = "set";
            return out;
        };
        /*\
         * Paper.setStart
         [ method ]
         **
         * Creates @Paper.set. All elements that will be created after calling this method and before calling
         * @Paper.setFinish will be added to the set.
         **
         > Usage
         | paper.setStart();
         | paper.circle(10, 10, 5),
         | paper.circle(30, 10, 5)
         | var st = paper.setFinish();
         | st.attr({fill: "red"}); // changes the fill of both circles
        \*/
        paperproto.setStart = function (set) {
            this.__set__ = set || this.set();
        };
        /*\
         * Paper.setFinish
         [ method ]
         **
         * See @Paper.setStart. This method finishes catching and returns resulting set.
         **
         = (object) set
        \*/
        paperproto.setFinish = function (set) {
            var out = this.__set__;
            delete this.__set__;
            return out;
        };
        /*\
         * Paper.setSize
         [ method ]
         **
         * If you need to change dimensions of the canvas call this method
         **
         > Parameters
         **
         - width (number) new width of the canvas
         - height (number) new height of the canvas
        \*/
        paperproto.setSize = function (width, height) {
            return R._engine.setSize.call(this, width, height);
        };
        /*\
         * Paper.setViewBox
         [ method ]
         **
         * Sets the view box of the paper. Practically it gives you ability to zoom and pan whole paper surface by 
         * specifying new boundaries.
         **
         > Parameters
         **
         - x (number) new x position, default is `0`
         - y (number) new y position, default is `0`
         - w (number) new width of the canvas
         - h (number) new height of the canvas
         - fit (boolean) `true` if you want graphics to fit into new boundary box
        \*/
        paperproto.setViewBox = function (x, y, w, h, fit) {
            return R._engine.setViewBox.call(this, x, y, w, h, fit);
        };
        /*\
         * Paper.top
         [ property ]
         **
         * Points to the topmost element on the paper
        \*/
        /*\
         * Paper.bottom
         [ property ]
         **
         * Points to the bottom element on the paper
        \*/
        paperproto.top = paperproto.bottom = null;
        /*\
         * Paper.raphael
         [ property ]
         **
         * Points to the @Raphael object/function
        \*/
        paperproto.raphael = R;
        var getOffset = function (elem) {
            var box = elem.getBoundingClientRect(),
                doc = elem.ownerDocument,
                body = doc.body,
                docElem = doc.documentElement,
                clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0,
                top  = box.top  + (g.win.pageYOffset || docElem.scrollTop || body.scrollTop ) - clientTop,
                left = box.left + (g.win.pageXOffset || docElem.scrollLeft || body.scrollLeft) - clientLeft;
            return {
                y: top,
                x: left
            };
        };
        /*\
         * Paper.getElementByPoint
         [ method ]
         **
         * Returns you topmost element under given point.
         **
         = (object) Raphaël element object
         > Parameters
         **
         - x (number) x coordinate from the top left corner of the window
         - y (number) y coordinate from the top left corner of the window
         > Usage
         | paper.getElementByPoint(mouseX, mouseY).attr({stroke: "#f00"});
        \*/
        paperproto.getElementByPoint = function (x, y) {
            var paper = this,
                svg = paper.canvas,
                target = g.doc.elementFromPoint(x, y);
            if (g.win.opera && target.tagName == "svg") {
                var so = getOffset(svg),
                    sr = svg.createSVGRect();
                sr.x = x - so.x;
                sr.y = y - so.y;
                sr.width = sr.height = 1;
                var hits = svg.getIntersectionList(sr, null);
                if (hits.length) {
                    target = hits[hits.length - 1];
                }
            }
            if (!target) {
                return null;
            }
            while (target.parentNode && target != svg.parentNode && !target.raphael) {
                target = target.parentNode;
            }
            target == paper.canvas.parentNode && (target = svg);
            target = target && target.raphael ? paper.getById(target.raphaelid) : null;
            return target;
        };
    
        /*\
         * Paper.getElementsByBBox
         [ method ]
         **
         * Returns set of elements that have an intersecting bounding box
         **
         > Parameters
         **
         - bbox (object) bbox to check with
         = (object) @Set
         \*/
        paperproto.getElementsByBBox = function (bbox) {
            var set = this.set();
            this.forEach(function (el) {
                if (R.isBBoxIntersect(el.getBBox(), bbox)) {
                    set.push(el);
                }
            });
            return set;
        };
    
        /*\
         * Paper.getById
         [ method ]
         **
         * Returns you element by its internal ID.
         **
         > Parameters
         **
         - id (number) id
         = (object) Raphaël element object
        \*/
        paperproto.getById = function (id) {
            var bot = this.bottom;
            while (bot) {
                if (bot.id == id) {
                    return bot;
                }
                bot = bot.next;
            }
            return null;
        };
        /*\
         * Paper.forEach
         [ method ]
         **
         * Executes given function for each element on the paper
         *
         * If callback function returns `false` it will stop loop running.
         **
         > Parameters
         **
         - callback (function) function to run
         - thisArg (object) context object for the callback
         = (object) Paper object
         > Usage
         | paper.forEach(function (el) {
         |     el.attr({ stroke: "blue" });
         | });
        \*/
        paperproto.forEach = function (callback, thisArg) {
            var bot = this.bottom;
            while (bot) {
                if (callback.call(thisArg, bot) === false) {
                    return this;
                }
                bot = bot.next;
            }
            return this;
        };
        /*\
         * Paper.getElementsByPoint
         [ method ]
         **
         * Returns set of elements that have common point inside
         **
         > Parameters
         **
         - x (number) x coordinate of the point
         - y (number) y coordinate of the point
         = (object) @Set
        \*/
        paperproto.getElementsByPoint = function (x, y) {
            var set = this.set();
            this.forEach(function (el) {
                if (el.isPointInside(x, y)) {
                    set.push(el);
                }
            });
            return set;
        };
        function x_y() {
            return this.x + S + this.y;
        }
        function x_y_w_h() {
            return this.x + S + this.y + S + this.width + " \xd7 " + this.height;
        }
        /*\
         * Element.isPointInside
         [ method ]
         **
         * Determine if given point is inside this element’s shape
         **
         > Parameters
         **
         - x (number) x coordinate of the point
         - y (number) y coordinate of the point
         = (boolean) `true` if point inside the shape
        \*/
        elproto.isPointInside = function (x, y) {
            var rp = this.realPath = getPath[this.type](this);
            if (this.attr('transform') && this.attr('transform').length) {
                rp = R.transformPath(rp, this.attr('transform'));
            }
            return R.isPointInsidePath(rp, x, y);
        };
        /*\
         * Element.getBBox
         [ method ]
         **
         * Return bounding box for a given element
         **
         > Parameters
         **
         - isWithoutTransform (boolean) flag, `true` if you want to have bounding box before transformations. Default is `false`.
         = (object) Bounding box object:
         o {
         o     x: (number) top left corner x
         o     y: (number) top left corner y
         o     x2: (number) bottom right corner x
         o     y2: (number) bottom right corner y
         o     width: (number) width
         o     height: (number) height
         o }
        \*/
        elproto.getBBox = function (isWithoutTransform) {
            if (this.removed) {
                return {};
            }
            var _ = this._;
            if (isWithoutTransform) {
                if (_.dirty || !_.bboxwt) {
                    this.realPath = getPath[this.type](this);
                    _.bboxwt = pathDimensions(this.realPath);
                    _.bboxwt.toString = x_y_w_h;
                    _.dirty = 0;
                }
                return _.bboxwt;
            }
            if (_.dirty || _.dirtyT || !_.bbox) {
                if (_.dirty || !this.realPath) {
                    _.bboxwt = 0;
                    this.realPath = getPath[this.type](this);
                }
                _.bbox = pathDimensions(mapPath(this.realPath, this.matrix));
                _.bbox.toString = x_y_w_h;
                _.dirty = _.dirtyT = 0;
            }
            return _.bbox;
        };
        /*\
         * Element.clone
         [ method ]
         **
         = (object) clone of a given element
         **
        \*/
        elproto.clone = function () {
            if (this.removed) {
                return null;
            }
            var out = this.paper[this.type]().attr(this.attr());
            this.__set__ && this.__set__.push(out);
            return out;
        };
        /*\
         * Element.glow
         [ method ]
         **
         * Return set of elements that create glow-like effect around given element. See @Paper.set.
         *
         * Note: Glow is not connected to the element. If you change element attributes it won’t adjust itself.
         **
         > Parameters
         **
         - glow (object) #optional parameters object with all properties optional:
         o {
         o     width (number) size of the glow, default is `10`
         o     fill (boolean) will it be filled, default is `false`
         o     opacity (number) opacity, default is `0.5`
         o     offsetx (number) horizontal offset, default is `0`
         o     offsety (number) vertical offset, default is `0`
         o     color (string) glow colour, default is `black`
         o }
         = (object) @Paper.set of elements that represents glow
        \*/
        elproto.glow = function (glow) {
            if (this.type == "text") {
                return null;
            }
            glow = glow || {};
            var s = {
                width: (glow.width || 10) + (+this.attr("stroke-width") || 1),
                fill: glow.fill || false,
                opacity: glow.opacity || .5,
                offsetx: glow.offsetx || 0,
                offsety: glow.offsety || 0,
                color: glow.color || "#000"
            },
                c = s.width / 2,
                r = this.paper,
                out = r.set(),
                path = this.realPath || getPath[this.type](this);
            path = this.matrix ? mapPath(path, this.matrix) : path;
            for (var i = 1; i < c + 1; i++) {
                out.push(r.path(path).attr({
                    stroke: s.color,
                    fill: s.fill ? s.color : "none",
                    "stroke-linejoin": "round",
                    "stroke-linecap": "round",
                    "stroke-width": +(s.width / c * i).toFixed(3),
                    opacity: +(s.opacity / c).toFixed(3)
                }));
            }
            return out.insertBefore(this).translate(s.offsetx, s.offsety);
        };
        var curveslengths = {},
        getPointAtSegmentLength = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) {
            if (length == null) {
                return bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y);
            } else {
                return R.findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, getTatLen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length));
            }
        },
        getLengthFactory = function (istotal, subpath) {
            return function (path, length, onlystart) {
                path = path2curve(path);
                var x, y, p, l, sp = "", subpaths = {}, point,
                    len = 0;
                for (var i = 0, ii = path.length; i < ii; i++) {
                    p = path[i];
                    if (p[0] == "M") {
                        x = +p[1];
                        y = +p[2];
                    } else {
                        l = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
                        if (len + l > length) {
                            if (subpath && !subpaths.start) {
                                point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
                                sp += ["C" + point.start.x, point.start.y, point.m.x, point.m.y, point.x, point.y];
                                if (onlystart) {return sp;}
                                subpaths.start = sp;
                                sp = ["M" + point.x, point.y + "C" + point.n.x, point.n.y, point.end.x, point.end.y, p[5], p[6]].join();
                                len += l;
                                x = +p[5];
                                y = +p[6];
                                continue;
                            }
                            if (!istotal && !subpath) {
                                point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
                                return {x: point.x, y: point.y, alpha: point.alpha};
                            }
                        }
                        len += l;
                        x = +p[5];
                        y = +p[6];
                    }
                    sp += p.shift() + p;
                }
                subpaths.end = sp;
                point = istotal ? len : subpath ? subpaths : R.findDotsAtSegment(x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1);
                point.alpha && (point = {x: point.x, y: point.y, alpha: point.alpha});
                return point;
            };
        };
        var getTotalLength = getLengthFactory(1),
            getPointAtLength = getLengthFactory(),
            getSubpathsAtLength = getLengthFactory(0, 1);
        /*\
         * Raphael.getTotalLength
         [ method ]
         **
         * Returns length of the given path in pixels.
         **
         > Parameters
         **
         - path (string) SVG path string.
         **
         = (number) length.
        \*/
        R.getTotalLength = getTotalLength;
        /*\
         * Raphael.getPointAtLength
         [ method ]
         **
         * Return coordinates of the point located at the given length on the given path.
         **
         > Parameters
         **
         - path (string) SVG path string
         - length (number)
         **
         = (object) representation of the point:
         o {
         o     x: (number) x coordinate
         o     y: (number) y coordinate
         o     alpha: (number) angle of derivative
         o }
        \*/
        R.getPointAtLength = getPointAtLength;
        /*\
         * Raphael.getSubpath
         [ method ]
         **
         * Return subpath of a given path from given length to given length.
         **
         > Parameters
         **
         - path (string) SVG path string
         - from (number) position of the start of the segment
         - to (number) position of the end of the segment
         **
         = (string) pathstring for the segment
        \*/
        R.getSubpath = function (path, from, to) {
            if (this.getTotalLength(path) - to < 1e-6) {
                return getSubpathsAtLength(path, from).end;
            }
            var a = getSubpathsAtLength(path, to, 1);
            return from ? getSubpathsAtLength(a, from).end : a;
        };
        /*\
         * Element.getTotalLength
         [ method ]
         **
         * Returns length of the path in pixels. Only works for element of “path” type.
         = (number) length.
        \*/
        elproto.getTotalLength = function () {
            var path = this.getPath();
            if (!path) {
                return;
            }
    
            if (this.node.getTotalLength) {
                return this.node.getTotalLength();
            }
    
            return getTotalLength(path);
        };
        /*\
         * Element.getPointAtLength
         [ method ]
         **
         * Return coordinates of the point located at the given length on the given path. Only works for element of “path” type.
         **
         > Parameters
         **
         - length (number)
         **
         = (object) representation of the point:
         o {
         o     x: (number) x coordinate
         o     y: (number) y coordinate
         o     alpha: (number) angle of derivative
         o }
        \*/
        elproto.getPointAtLength = function (length) {
            var path = this.getPath();
            if (!path) {
                return;
            }
    
            return getPointAtLength(path, length);
        };
        /*\
         * Element.getPath
         [ method ]
         **
         * Returns path of the element. Only works for elements of “path” type and simple elements like circle.
         = (object) path
         **
        \*/
        elproto.getPath = function () {
            var path,
                getPath = R._getPath[this.type];
            
            if (this.type == "text" || this.type == "set") {
                return;
            }
    
            if (getPath) {
                path = getPath(this);
            }
    
            return path;
        };
        /*\
         * Element.getSubpath
         [ method ]
         **
         * Return subpath of a given element from given length to given length. Only works for element of “path” type.
         **
         > Parameters
         **
         - from (number) position of the start of the segment
         - to (number) position of the end of the segment
         **
         = (string) pathstring for the segment
        \*/
        elproto.getSubpath = function (from, to) {
            var path = this.getPath();
            if (!path) {
                return;
            }
    
            return R.getSubpath(path, from, to);
        };
        /*\
         * Raphael.easing_formulas
         [ property ]
         **
         * Object that contains easing formulas for animation. You could extend it with your own. By default it has following list of easing:
         # <ul>
         #     <li>“linear”</li>
         #     <li>“&lt;” or “easeIn” or “ease-in”</li>
         #     <li>“>” or “easeOut” or “ease-out”</li>
         #     <li>“&lt;>” or “easeInOut” or “ease-in-out”</li>
         #     <li>“backIn” or “back-in”</li>
         #     <li>“backOut” or “back-out”</li>
         #     <li>“elastic”</li>
         #     <li>“bounce”</li>
         # </ul>
         # <p>See also <a href="http://raphaeljs.com/easing.html">Easing demo</a>.</p>
        \*/
        var ef = R.easing_formulas = {
            linear: function (n) {
                return n;
            },
            "<": function (n) {
                return pow(n, 1.7);
            },
            ">": function (n) {
                return pow(n, .48);
            },
            "<>": function (n) {
                var q = .48 - n / 1.04,
                    Q = math.sqrt(.1734 + q * q),
                    x = Q - q,
                    X = pow(abs(x), 1 / 3) * (x < 0 ? -1 : 1),
                    y = -Q - q,
                    Y = pow(abs(y), 1 / 3) * (y < 0 ? -1 : 1),
                    t = X + Y + .5;
                return (1 - t) * 3 * t * t + t * t * t;
            },
            backIn: function (n) {
                var s = 1.70158;
                return n * n * ((s + 1) * n - s);
            },
            backOut: function (n) {
                n = n - 1;
                var s = 1.70158;
                return n * n * ((s + 1) * n + s) + 1;
            },
            elastic: function (n) {
                if (n == !!n) {
                    return n;
                }
                return pow(2, -10 * n) * math.sin((n - .075) * (2 * PI) / .3) + 1;
            },
            bounce: function (n) {
                var s = 7.5625,
                    p = 2.75,
                    l;
                if (n < (1 / p)) {
                    l = s * n * n;
                } else {
                    if (n < (2 / p)) {
                        n -= (1.5 / p);
                        l = s * n * n + .75;
                    } else {
                        if (n < (2.5 / p)) {
                            n -= (2.25 / p);
                            l = s * n * n + .9375;
                        } else {
                            n -= (2.625 / p);
                            l = s * n * n + .984375;
                        }
                    }
                }
                return l;
            }
        };
        ef.easeIn = ef["ease-in"] = ef["<"];
        ef.easeOut = ef["ease-out"] = ef[">"];
        ef.easeInOut = ef["ease-in-out"] = ef["<>"];
        ef["back-in"] = ef.backIn;
        ef["back-out"] = ef.backOut;
    
        var animationElements = [],
            requestAnimFrame = window.requestAnimationFrame       ||
                               window.webkitRequestAnimationFrame ||
                               window.mozRequestAnimationFrame    ||
                               window.oRequestAnimationFrame      ||
                               window.msRequestAnimationFrame     ||
                               function (callback) {
                                   setTimeout(callback, 16);
                               },
            animation = function () {
                var Now = +new Date,
                    l = 0;
                for (; l < animationElements.length; l++) {
                    var e = animationElements[l];
                    if (e.el.removed || e.paused) {
                        continue;
                    }
                    var time = Now - e.start,
                        ms = e.ms,
                        easing = e.easing,
                        from = e.from,
                        diff = e.diff,
                        to = e.to,
                        t = e.t,
                        that = e.el,
                        set = {},
                        now,
                        init = {},
                        key;
                    if (e.initstatus) {
                        time = (e.initstatus * e.anim.top - e.prev) / (e.percent - e.prev) * ms;
                        e.status = e.initstatus;
                        delete e.initstatus;
                        e.stop && animationElements.splice(l--, 1);
                    } else {
                        e.status = (e.prev + (e.percent - e.prev) * (time / ms)) / e.anim.top;
                    }
                    if (time < 0) {
                        continue;
                    }
                    if (time < ms) {
                        var pos = easing(time / ms);
                        for (var attr in from) if (from[has](attr)) {
                            switch (availableAnimAttrs[attr]) {
                                case nu:
                                    now = +from[attr] + pos * ms * diff[attr];
                                    break;
                                case "colour":
                                    now = "rgb(" + [
                                        upto255(round(from[attr].r + pos * ms * diff[attr].r)),
                                        upto255(round(from[attr].g + pos * ms * diff[attr].g)),
                                        upto255(round(from[attr].b + pos * ms * diff[attr].b))
                                    ].join(",") + ")";
                                    break;
                                case "path":
                                    now = [];
                                    for (var i = 0, ii = from[attr].length; i < ii; i++) {
                                        now[i] = [from[attr][i][0]];
                                        for (var j = 1, jj = from[attr][i].length; j < jj; j++) {
                                            now[i][j] = +from[attr][i][j] + pos * ms * diff[attr][i][j];
                                        }
                                        now[i] = now[i].join(S);
                                    }
                                    now = now.join(S);
                                    break;
                                case "transform":
                                    if (diff[attr].real) {
                                        now = [];
                                        for (i = 0, ii = from[attr].length; i < ii; i++) {
                                            now[i] = [from[attr][i][0]];
                                            for (j = 1, jj = from[attr][i].length; j < jj; j++) {
                                                now[i][j] = from[attr][i][j] + pos * ms * diff[attr][i][j];
                                            }
                                        }
                                    } else {
                                        var get = function (i) {
                                            return +from[attr][i] + pos * ms * diff[attr][i];
                                        };
                                        // now = [["r", get(2), 0, 0], ["t", get(3), get(4)], ["s", get(0), get(1), 0, 0]];
                                        now = [["m", get(0), get(1), get(2), get(3), get(4), get(5)]];
                                    }
                                    break;
                                case "csv":
                                    if (attr == "clip-rect") {
                                        now = [];
                                        i = 4;
                                        while (i--) {
                                            now[i] = +from[attr][i] + pos * ms * diff[attr][i];
                                        }
                                    }
                                    break;
                                default:
                                    var from2 = [][concat](from[attr]);
                                    now = [];
                                    i = that.paper.customAttributes[attr].length;
                                    while (i--) {
                                        now[i] = +from2[i] + pos * ms * diff[attr][i];
                                    }
                                    break;
                            }
                            set[attr] = now;
                        }
                        that.attr(set);
                        (function (id, that, anim) {
                            setTimeout(function () {
                                eve("raphael.anim.frame." + id, that, anim);
                            });
                        })(that.id, that, e.anim);
                    } else {
                        (function(f, el, a) {
                            setTimeout(function() {
                                eve("raphael.anim.frame." + el.id, el, a);
                                eve("raphael.anim.finish." + el.id, el, a);
                                R.is(f, "function") && f.call(el);
                            });
                        })(e.callback, that, e.anim);
                        that.attr(to);
                        animationElements.splice(l--, 1);
                        if (e.repeat > 1 && !e.next) {
                            for (key in to) if (to[has](key)) {
                                init[key] = e.totalOrigin[key];
                            }
                            e.el.attr(init);
                            runAnimation(e.anim, e.el, e.anim.percents[0], null, e.totalOrigin, e.repeat - 1);
                        }
                        if (e.next && !e.stop) {
                            runAnimation(e.anim, e.el, e.next, null, e.totalOrigin, e.repeat);
                        }
                    }
                }
                R.svg && that && that.paper && that.paper.safari();
                animationElements.length && requestAnimFrame(animation);
            },
            upto255 = function (color) {
                return color > 255 ? 255 : color < 0 ? 0 : color;
            };
        /*\
         * Element.animateWith
         [ method ]
         **
         * Acts similar to @Element.animate, but ensure that given animation runs in sync with another given element.
         **
         > Parameters
         **
         - el (object) element to sync with
         - anim (object) animation to sync with
         - params (object) #optional final attributes for the element, see also @Element.attr
         - ms (number) #optional number of milliseconds for animation to run
         - easing (string) #optional easing type. Accept on of @Raphael.easing_formulas or CSS format: `cubic&#x2010;bezier(XX,&#160;XX,&#160;XX,&#160;XX)`
         - callback (function) #optional callback function. Will be called at the end of animation.
         * or
         - element (object) element to sync with
         - anim (object) animation to sync with
         - animation (object) #optional animation object, see @Raphael.animation
         **
         = (object) original element
        \*/
        elproto.animateWith = function (el, anim, params, ms, easing, callback) {
            var element = this;
            if (element.removed) {
                callback && callback.call(element);
                return element;
            }
            var a = params instanceof Animation ? params : R.animation(params, ms, easing, callback),
                x, y;
            runAnimation(a, element, a.percents[0], null, element.attr());
            for (var i = 0, ii = animationElements.length; i < ii; i++) {
                if (animationElements[i].anim == anim && animationElements[i].el == el) {
                    animationElements[ii - 1].start = animationElements[i].start;
                    break;
                }
            }
            return element;
            // 
            // 
            // var a = params ? R.animation(params, ms, easing, callback) : anim,
            //     status = element.status(anim);
            // return this.animate(a).status(a, status * anim.ms / a.ms);
        };
        function CubicBezierAtTime(t, p1x, p1y, p2x, p2y, duration) {
            var cx = 3 * p1x,
                bx = 3 * (p2x - p1x) - cx,
                ax = 1 - cx - bx,
                cy = 3 * p1y,
                by = 3 * (p2y - p1y) - cy,
                ay = 1 - cy - by;
            function sampleCurveX(t) {
                return ((ax * t + bx) * t + cx) * t;
            }
            function solve(x, epsilon) {
                var t = solveCurveX(x, epsilon);
                return ((ay * t + by) * t + cy) * t;
            }
            function solveCurveX(x, epsilon) {
                var t0, t1, t2, x2, d2, i;
                for(t2 = x, i = 0; i < 8; i++) {
                    x2 = sampleCurveX(t2) - x;
                    if (abs(x2) < epsilon) {
                        return t2;
                    }
                    d2 = (3 * ax * t2 + 2 * bx) * t2 + cx;
                    if (abs(d2) < 1e-6) {
                        break;
                    }
                    t2 = t2 - x2 / d2;
                }
                t0 = 0;
                t1 = 1;
                t2 = x;
                if (t2 < t0) {
                    return t0;
                }
                if (t2 > t1) {
                    return t1;
                }
                while (t0 < t1) {
                    x2 = sampleCurveX(t2);
                    if (abs(x2 - x) < epsilon) {
                        return t2;
                    }
                    if (x > x2) {
                        t0 = t2;
                    } else {
                        t1 = t2;
                    }
                    t2 = (t1 - t0) / 2 + t0;
                }
                return t2;
            }
            return solve(t, 1 / (200 * duration));
        }
        elproto.onAnimation = function (f) {
            f ? eve.on("raphael.anim.frame." + this.id, f) : eve.unbind("raphael.anim.frame." + this.id);
            return this;
        };
        function Animation(anim, ms) {
            var percents = [],
                newAnim = {};
            this.ms = ms;
            this.times = 1;
            if (anim) {
                for (var attr in anim) if (anim[has](attr)) {
                    newAnim[toFloat(attr)] = anim[attr];
                    percents.push(toFloat(attr));
                }
                percents.sort(sortByNumber);
            }
            this.anim = newAnim;
            this.top = percents[percents.length - 1];
            this.percents = percents;
        }
        /*\
         * Animation.delay
         [ method ]
         **
         * Creates a copy of existing animation object with given delay.
         **
         > Parameters
         **
         - delay (number) number of ms to pass between animation start and actual animation
         **
         = (object) new altered Animation object
         | var anim = Raphael.animation({cx: 10, cy: 20}, 2e3);
         | circle1.animate(anim); // run the given animation immediately
         | circle2.animate(anim.delay(500)); // run the given animation after 500 ms
        \*/
        Animation.prototype.delay = function (delay) {
            var a = new Animation(this.anim, this.ms);
            a.times = this.times;
            a.del = +delay || 0;
            return a;
        };
        /*\
         * Animation.repeat
         [ method ]
         **
         * Creates a copy of existing animation object with given repetition.
         **
         > Parameters
         **
         - repeat (number) number iterations of animation. For infinite animation pass `Infinity`
         **
         = (object) new altered Animation object
        \*/
        Animation.prototype.repeat = function (times) {
            var a = new Animation(this.anim, this.ms);
            a.del = this.del;
            a.times = math.floor(mmax(times, 0)) || 1;
            return a;
        };
        function runAnimation(anim, element, percent, status, totalOrigin, times) {
            percent = toFloat(percent);
            var params,
                isInAnim,
                isInAnimSet,
                percents = [],
                next,
                prev,
                timestamp,
                ms = anim.ms,
                from = {},
                to = {},
                diff = {};
            if (status) {
                for (i = 0, ii = animationElements.length; i < ii; i++) {
                    var e = animationElements[i];
                    if (e.el.id == element.id && e.anim == anim) {
                        if (e.percent != percent) {
                            animationElements.splice(i, 1);
                            isInAnimSet = 1;
                        } else {
                            isInAnim = e;
                        }
                        element.attr(e.totalOrigin);
                        break;
                    }
                }
            } else {
                status = +to; // NaN
            }
            for (var i = 0, ii = anim.percents.length; i < ii; i++) {
                if (anim.percents[i] == percent || anim.percents[i] > status * anim.top) {
                    percent = anim.percents[i];
                    prev = anim.percents[i - 1] || 0;
                    ms = ms / anim.top * (percent - prev);
                    next = anim.percents[i + 1];
                    params = anim.anim[percent];
                    break;
                } else if (status) {
                    element.attr(anim.anim[anim.percents[i]]);
                }
            }
            if (!params) {
                return;
            }
            if (!isInAnim) {
                for (var attr in params) if (params[has](attr)) {
                    if (availableAnimAttrs[has](attr) || element.paper.customAttributes[has](attr)) {
                        from[attr] = element.attr(attr);
                        (from[attr] == null) && (from[attr] = availableAttrs[attr]);
                        to[attr] = params[attr];
                        switch (availableAnimAttrs[attr]) {
                            case nu:
                                diff[attr] = (to[attr] - from[attr]) / ms;
                                break;
                            case "colour":
                                from[attr] = R.getRGB(from[attr]);
                                var toColour = R.getRGB(to[attr]);
                                diff[attr] = {
                                    r: (toColour.r - from[attr].r) / ms,
                                    g: (toColour.g - from[attr].g) / ms,
                                    b: (toColour.b - from[attr].b) / ms
                                };
                                break;
                            case "path":
                                var pathes = path2curve(from[attr], to[attr]),
                                    toPath = pathes[1];
                                from[attr] = pathes[0];
                                diff[attr] = [];
                                for (i = 0, ii = from[attr].length; i < ii; i++) {
                                    diff[attr][i] = [0];
                                    for (var j = 1, jj = from[attr][i].length; j < jj; j++) {
                                        diff[attr][i][j] = (toPath[i][j] - from[attr][i][j]) / ms;
                                    }
                                }
                                break;
                            case "transform":
                                var _ = element._,
                                    eq = equaliseTransform(_[attr], to[attr]);
                                if (eq) {
                                    from[attr] = eq.from;
                                    to[attr] = eq.to;
                                    diff[attr] = [];
                                    diff[attr].real = true;
                                    for (i = 0, ii = from[attr].length; i < ii; i++) {
                                        diff[attr][i] = [from[attr][i][0]];
                                        for (j = 1, jj = from[attr][i].length; j < jj; j++) {
                                            diff[attr][i][j] = (to[attr][i][j] - from[attr][i][j]) / ms;
                                        }
                                    }
                                } else {
                                    var m = (element.matrix || new Matrix),
                                        to2 = {
                                            _: {transform: _.transform},
                                            getBBox: function () {
                                                return element.getBBox(1);
                                            }
                                        };
                                    from[attr] = [
                                        m.a,
                                        m.b,
                                        m.c,
                                        m.d,
                                        m.e,
                                        m.f
                                    ];
                                    extractTransform(to2, to[attr]);
                                    to[attr] = to2._.transform;
                                    diff[attr] = [
                                        (to2.matrix.a - m.a) / ms,
                                        (to2.matrix.b - m.b) / ms,
                                        (to2.matrix.c - m.c) / ms,
                                        (to2.matrix.d - m.d) / ms,
                                        (to2.matrix.e - m.e) / ms,
                                        (to2.matrix.f - m.f) / ms
                                    ];
                                    // from[attr] = [_.sx, _.sy, _.deg, _.dx, _.dy];
                                    // var to2 = {_:{}, getBBox: function () { return element.getBBox(); }};
                                    // extractTransform(to2, to[attr]);
                                    // diff[attr] = [
                                    //     (to2._.sx - _.sx) / ms,
                                    //     (to2._.sy - _.sy) / ms,
                                    //     (to2._.deg - _.deg) / ms,
                                    //     (to2._.dx - _.dx) / ms,
                                    //     (to2._.dy - _.dy) / ms
                                    // ];
                                }
                                break;
                            case "csv":
                                var values = Str(params[attr])[split](separator),
                                    from2 = Str(from[attr])[split](separator);
                                if (attr == "clip-rect") {
                                    from[attr] = from2;
                                    diff[attr] = [];
                                    i = from2.length;
                                    while (i--) {
                                        diff[attr][i] = (values[i] - from[attr][i]) / ms;
                                    }
                                }
                                to[attr] = values;
                                break;
                            default:
                                values = [][concat](params[attr]);
                                from2 = [][concat](from[attr]);
                                diff[attr] = [];
                                i = element.paper.customAttributes[attr].length;
                                while (i--) {
                                    diff[attr][i] = ((values[i] || 0) - (from2[i] || 0)) / ms;
                                }
                                break;
                        }
                    }
                }
                var easing = params.easing,
                    easyeasy = R.easing_formulas[easing];
                if (!easyeasy) {
                    easyeasy = Str(easing).match(bezierrg);
                    if (easyeasy && easyeasy.length == 5) {
                        var curve = easyeasy;
                        easyeasy = function (t) {
                            return CubicBezierAtTime(t, +curve[1], +curve[2], +curve[3], +curve[4], ms);
                        };
                    } else {
                        easyeasy = pipe;
                    }
                }
                timestamp = params.start || anim.start || +new Date;
                e = {
                    anim: anim,
                    percent: percent,
                    timestamp: timestamp,
                    start: timestamp + (anim.del || 0),
                    status: 0,
                    initstatus: status || 0,
                    stop: false,
                    ms: ms,
                    easing: easyeasy,
                    from: from,
                    diff: diff,
                    to: to,
                    el: element,
                    callback: params.callback,
                    prev: prev,
                    next: next,
                    repeat: times || anim.times,
                    origin: element.attr(),
                    totalOrigin: totalOrigin
                };
                animationElements.push(e);
                if (status && !isInAnim && !isInAnimSet) {
                    e.stop = true;
                    e.start = new Date - ms * status;
                    if (animationElements.length == 1) {
                        return animation();
                    }
                }
                if (isInAnimSet) {
                    e.start = new Date - e.ms * status;
                }
                animationElements.length == 1 && requestAnimFrame(animation);
            } else {
                isInAnim.initstatus = status;
                isInAnim.start = new Date - isInAnim.ms * status;
            }
            eve("raphael.anim.start." + element.id, element, anim);
        }
        /*\
         * Raphael.animation
         [ method ]
         **
         * Creates an animation object that can be passed to the @Element.animate or @Element.animateWith methods.
         * See also @Animation.delay and @Animation.repeat methods.
         **
         > Parameters
         **
         - params (object) final attributes for the element, see also @Element.attr
         - ms (number) number of milliseconds for animation to run
         - easing (string) #optional easing type. Accept one of @Raphael.easing_formulas or CSS format: `cubic&#x2010;bezier(XX,&#160;XX,&#160;XX,&#160;XX)`
         - callback (function) #optional callback function. Will be called at the end of animation.
         **
         = (object) @Animation
        \*/
        R.animation = function (params, ms, easing, callback) {
            if (params instanceof Animation) {
                return params;
            }
            if (R.is(easing, "function") || !easing) {
                callback = callback || easing || null;
                easing = null;
            }
            params = Object(params);
            ms = +ms || 0;
            var p = {},
                json,
                attr;
            for (attr in params) if (params[has](attr) && toFloat(attr) != attr && toFloat(attr) + "%" != attr) {
                json = true;
                p[attr] = params[attr];
            }
            if (!json) {
                return new Animation(params, ms);
            } else {
                easing && (p.easing = easing);
                callback && (p.callback = callback);
                return new Animation({100: p}, ms);
            }
        };
        /*\
         * Element.animate
         [ method ]
         **
         * Creates and starts animation for given element.
         **
         > Parameters
         **
         - params (object) final attributes for the element, see also @Element.attr
         - ms (number) number of milliseconds for animation to run
         - easing (string) #optional easing type. Accept one of @Raphael.easing_formulas or CSS format: `cubic&#x2010;bezier(XX,&#160;XX,&#160;XX,&#160;XX)`
         - callback (function) #optional callback function. Will be called at the end of animation.
         * or
         - animation (object) animation object, see @Raphael.animation
         **
         = (object) original element
        \*/
        elproto.animate = function (params, ms, easing, callback) {
            var element = this;
            if (element.removed) {
                callback && callback.call(element);
                return element;
            }
            var anim = params instanceof Animation ? params : R.animation(params, ms, easing, callback);
            runAnimation(anim, element, anim.percents[0], null, element.attr());
            return element;
        };
        /*\
         * Element.setTime
         [ method ]
         **
         * Sets the status of animation of the element in milliseconds. Similar to @Element.status method.
         **
         > Parameters
         **
         - anim (object) animation object
         - value (number) number of milliseconds from the beginning of the animation
         **
         = (object) original element if `value` is specified
         * Note, that during animation following events are triggered:
         *
         * On each animation frame event `anim.frame.<id>`, on start `anim.start.<id>` and on end `anim.finish.<id>`.
        \*/
        elproto.setTime = function (anim, value) {
            if (anim && value != null) {
                this.status(anim, mmin(value, anim.ms) / anim.ms);
            }
            return this;
        };
        /*\
         * Element.status
         [ method ]
         **
         * Gets or sets the status of animation of the element.
         **
         > Parameters
         **
         - anim (object) #optional animation object
         - value (number) #optional 0 – 1. If specified, method works like a setter and sets the status of a given animation to the value. This will cause animation to jump to the given position.
         **
         = (number) status
         * or
         = (array) status if `anim` is not specified. Array of objects in format:
         o {
         o     anim: (object) animation object
         o     status: (number) status
         o }
         * or
         = (object) original element if `value` is specified
        \*/
        elproto.status = function (anim, value) {
            var out = [],
                i = 0,
                len,
                e;
            if (value != null) {
                runAnimation(anim, this, -1, mmin(value, 1));
                return this;
            } else {
                len = animationElements.length;
                for (; i < len; i++) {
                    e = animationElements[i];
                    if (e.el.id == this.id && (!anim || e.anim == anim)) {
                        if (anim) {
                            return e.status;
                        }
                        out.push({
                            anim: e.anim,
                            status: e.status
                        });
                    }
                }
                if (anim) {
                    return 0;
                }
                return out;
            }
        };
        /*\
         * Element.pause
         [ method ]
         **
         * Stops animation of the element with ability to resume it later on.
         **
         > Parameters
         **
         - anim (object) #optional animation object
         **
         = (object) original element
        \*/
        elproto.pause = function (anim) {
            for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) {
                if (eve("raphael.anim.pause." + this.id, this, animationElements[i].anim) !== false) {
                    animationElements[i].paused = true;
                }
            }
            return this;
        };
        /*\
         * Element.resume
         [ method ]
         **
         * Resumes animation if it was paused with @Element.pause method.
         **
         > Parameters
         **
         - anim (object) #optional animation object
         **
         = (object) original element
        \*/
        elproto.resume = function (anim) {
            for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) {
                var e = animationElements[i];
                if (eve("raphael.anim.resume." + this.id, this, e.anim) !== false) {
                    delete e.paused;
                    this.status(e.anim, e.status);
                }
            }
            return this;
        };
        /*\
         * Element.stop
         [ method ]
         **
         * Stops animation of the element.
         **
         > Parameters
         **
         - anim (object) #optional animation object
         **
         = (object) original element
        \*/
        elproto.stop = function (anim) {
            for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) {
                if (eve("raphael.anim.stop." + this.id, this, animationElements[i].anim) !== false) {
                    animationElements.splice(i--, 1);
                }
            }
            return this;
        };
        function stopAnimation(paper) {
            for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.paper == paper) {
                animationElements.splice(i--, 1);
            }
        }
        eve.on("raphael.remove", stopAnimation);
        eve.on("raphael.clear", stopAnimation);
        elproto.toString = function () {
            return "Rapha\xebl\u2019s object";
        };
    
        // Set
        var Set = function (items) {
            this.items = [];
            this.length = 0;
            this.type = "set";
            if (items) {
                for (var i = 0, ii = items.length; i < ii; i++) {
                    if (items[i] && (items[i].constructor == elproto.constructor || items[i].constructor == Set)) {
                        this[this.items.length] = this.items[this.items.length] = items[i];
                        this.length++;
                    }
                }
            }
        },
        setproto = Set.prototype;
        /*\
         * Set.push
         [ method ]
         **
         * Adds each argument to the current set.
         = (object) original element
        \*/
        setproto.push = function () {
            var item,
                len;
            for (var i = 0, ii = arguments.length; i < ii; i++) {
                item = arguments[i];
                if (item && (item.constructor == elproto.constructor || item.constructor == Set)) {
                    len = this.items.length;
                    this[len] = this.items[len] = item;
                    this.length++;
                }
            }
            return this;
        };
        /*\
         * Set.pop
         [ method ]
         **
         * Removes last element and returns it.
         = (object) element
        \*/
        setproto.pop = function () {
            this.length && delete this[this.length--];
            return this.items.pop();
        };
        /*\
         * Set.forEach
         [ method ]
         **
         * Executes given function for each element in the set.
         *
         * If function returns `false` it will stop loop running.
         **
         > Parameters
         **
         - callback (function) function to run
         - thisArg (object) context object for the callback
         = (object) Set object
        \*/
        setproto.forEach = function (callback, thisArg) {
            for (var i = 0, ii = this.items.length; i < ii; i++) {
                if (callback.call(thisArg, this.items[i], i) === false) {
                    return this;
                }
            }
            return this;
        };
        for (var method in elproto) if (elproto[has](method)) {
            setproto[method] = (function (methodname) {
                return function () {
                    var arg = arguments;
                    return this.forEach(function (el) {
                        el[methodname][apply](el, arg);
                    });
                };
            })(method);
        }
        setproto.attr = function (name, value) {
            if (name && R.is(name, array) && R.is(name[0], "object")) {
                for (var j = 0, jj = name.length; j < jj; j++) {
                    this.items[j].attr(name[j]);
                }
            } else {
                for (var i = 0, ii = this.items.length; i < ii; i++) {
                    this.items[i].attr(name, value);
                }
            }
            return this;
        };
        /*\
         * Set.clear
         [ method ]
         **
         * Removeds all elements from the set
        \*/
        setproto.clear = function () {
            while (this.length) {
                this.pop();
            }
        };
        /*\
         * Set.splice
         [ method ]
         **
         * Removes given element from the set
         **
         > Parameters
         **
         - index (number) position of the deletion
         - count (number) number of element to remove
         - insertion… (object) #optional elements to insert
         = (object) set elements that were deleted
        \*/
        setproto.splice = function (index, count, insertion) {
            index = index < 0 ? mmax(this.length + index, 0) : index;
            count = mmax(0, mmin(this.length - index, count));
            var tail = [],
                todel = [],
                args = [],
                i;
            for (i = 2; i < arguments.length; i++) {
                args.push(arguments[i]);
            }
            for (i = 0; i < count; i++) {
                todel.push(this[index + i]);
            }
            for (; i < this.length - index; i++) {
                tail.push(this[index + i]);
            }
            var arglen = args.length;
            for (i = 0; i < arglen + tail.length; i++) {
                this.items[index + i] = this[index + i] = i < arglen ? args[i] : tail[i - arglen];
            }
            i = this.items.length = this.length -= count - arglen;
            while (this[i]) {
                delete this[i++];
            }
            return new Set(todel);
        };
        /*\
         * Set.exclude
         [ method ]
         **
         * Removes given element from the set
         **
         > Parameters
         **
         - element (object) element to remove
         = (boolean) `true` if object was found & removed from the set
        \*/
        setproto.exclude = function (el) {
            for (var i = 0, ii = this.length; i < ii; i++) if (this[i] == el) {
                this.splice(i, 1);
                return true;
            }
        };
        setproto.animate = function (params, ms, easing, callback) {
            (R.is(easing, "function") || !easing) && (callback = easing || null);
            var len = this.items.length,
                i = len,
                item,
                set = this,
                collector;
            if (!len) {
                return this;
            }
            callback && (collector = function () {
                !--len && callback.call(set);
            });
            easing = R.is(easing, string) ? easing : collector;
            var anim = R.animation(params, ms, easing, collector);
            item = this.items[--i].animate(anim);
            while (i--) {
                this.items[i] && !this.items[i].removed && this.items[i].animateWith(item, anim, anim);
                (this.items[i] && !this.items[i].removed) || len--;
            }
            return this;
        };
        setproto.insertAfter = function (el) {
            var i = this.items.length;
            while (i--) {
                this.items[i].insertAfter(el);
            }
            return this;
        };
        setproto.getBBox = function () {
            var x = [],
                y = [],
                x2 = [],
                y2 = [];
            for (var i = this.items.length; i--;) if (!this.items[i].removed) {
                var box = this.items[i].getBBox();
                x.push(box.x);
                y.push(box.y);
                x2.push(box.x + box.width);
                y2.push(box.y + box.height);
            }
            x = mmin[apply](0, x);
            y = mmin[apply](0, y);
            x2 = mmax[apply](0, x2);
            y2 = mmax[apply](0, y2);
            return {
                x: x,
                y: y,
                x2: x2,
                y2: y2,
                width: x2 - x,
                height: y2 - y
            };
        };
        setproto.clone = function (s) {
            s = this.paper.set();
            for (var i = 0, ii = this.items.length; i < ii; i++) {
                s.push(this.items[i].clone());
            }
            return s;
        };
        setproto.toString = function () {
            return "Rapha\xebl\u2018s set";
        };
    
        setproto.glow = function(glowConfig) {
            var ret = this.paper.set();
            this.forEach(function(shape, index){
                var g = shape.glow(glowConfig);
                if(g != null){
                    g.forEach(function(shape2, index2){
                        ret.push(shape2);
                    });
                }
            });
            return ret;
        };
    
    
        /*\
         * Set.isPointInside
         [ method ]
         **
         * Determine if given point is inside this set’s elements
         **
         > Parameters
         **
         - x (number) x coordinate of the point
         - y (number) y coordinate of the point
         = (boolean) `true` if point is inside any of the set's elements
         \*/
        setproto.isPointInside = function (x, y) {
            var isPointInside = false;
            this.forEach(function (el) {
                if (el.isPointInside(x, y)) {
                    console.log('runned');
                    isPointInside = true;
                    return false; // stop loop
                }
            });
            return isPointInside;
        };
    
        /*\
         * Raphael.registerFont
         [ method ]
         **
         * Adds given font to the registered set of fonts for Raphaël. Should be used as an internal call from within Cufón’s font file.
         * Returns original parameter, so it could be used with chaining.
         # <a href="http://wiki.github.com/sorccu/cufon/about">More about Cufón and how to convert your font form TTF, OTF, etc to JavaScript file.</a>
         **
         > Parameters
         **
         - font (object) the font to register
         = (object) the font you passed in
         > Usage
         | Cufon.registerFont(Raphael.registerFont({…}));
        \*/
        R.registerFont = function (font) {
            if (!font.face) {
                return font;
            }
            this.fonts = this.fonts || {};
            var fontcopy = {
                    w: font.w,
                    face: {},
                    glyphs: {}
                },
                family = font.face["font-family"];
            for (var prop in font.face) if (font.face[has](prop)) {
                fontcopy.face[prop] = font.face[prop];
            }
            if (this.fonts[family]) {
                this.fonts[family].push(fontcopy);
            } else {
                this.fonts[family] = [fontcopy];
            }
            if (!font.svg) {
                fontcopy.face["units-per-em"] = toInt(font.face["units-per-em"], 10);
                for (var glyph in font.glyphs) if (font.glyphs[has](glyph)) {
                    var path = font.glyphs[glyph];
                    fontcopy.glyphs[glyph] = {
                        w: path.w,
                        k: {},
                        d: path.d && "M" + path.d.replace(/[mlcxtrv]/g, function (command) {
                                return {l: "L", c: "C", x: "z", t: "m", r: "l", v: "c"}[command] || "M";
                            }) + "z"
                    };
                    if (path.k) {
                        for (var k in path.k) if (path[has](k)) {
                            fontcopy.glyphs[glyph].k[k] = path.k[k];
                        }
                    }
                }
            }
            return font;
        };
        /*\
         * Paper.getFont
         [ method ]
         **
         * Finds font object in the registered fonts by given parameters. You could specify only one word from the font name, like “Myriad” for “Myriad Pro”.
         **
         > Parameters
         **
         - family (string) font family name or any word from it
         - weight (string) #optional font weight
         - style (string) #optional font style
         - stretch (string) #optional font stretch
         = (object) the font object
         > Usage
         | paper.print(100, 100, "Test string", paper.getFont("Times", 800), 30);
        \*/
        paperproto.getFont = function (family, weight, style, stretch) {
            stretch = stretch || "normal";
            style = style || "normal";
            weight = +weight || {normal: 400, bold: 700, lighter: 300, bolder: 800}[weight] || 400;
            if (!R.fonts) {
                return;
            }
            var font = R.fonts[family];
            if (!font) {
                var name = new RegExp("(^|\\s)" + family.replace(/[^\w\d\s+!~.:_-]/g, E) + "(\\s|$)", "i");
                for (var fontName in R.fonts) if (R.fonts[has](fontName)) {
                    if (name.test(fontName)) {
                        font = R.fonts[fontName];
                        break;
                    }
                }
            }
            var thefont;
            if (font) {
                for (var i = 0, ii = font.length; i < ii; i++) {
                    thefont = font[i];
                    if (thefont.face["font-weight"] == weight && (thefont.face["font-style"] == style || !thefont.face["font-style"]) && thefont.face["font-stretch"] == stretch) {
                        break;
                    }
                }
            }
            return thefont;
        };
        /*\
         * Paper.print
         [ method ]
         **
         * Creates path that represent given text written using given font at given position with given size.
         * Result of the method is path element that contains whole text as a separate path.
         **
         > Parameters
         **
         - x (number) x position of the text
         - y (number) y position of the text
         - string (string) text to print
         - font (object) font object, see @Paper.getFont
         - size (number) #optional size of the font, default is `16`
         - origin (string) #optional could be `"baseline"` or `"middle"`, default is `"middle"`
         - letter_spacing (number) #optional number in range `-1..1`, default is `0`
         - line_spacing (number) #optional number in range `1..3`, default is `1`
         = (object) resulting path element, which consist of all letters
         > Usage
         | var txt = r.print(10, 50, "print", r.getFont("Museo"), 30).attr({fill: "#fff"});
        \*/
        paperproto.print = function (x, y, string, font, size, origin, letter_spacing, line_spacing) {
            origin = origin || "middle"; // baseline|middle
            letter_spacing = mmax(mmin(letter_spacing || 0, 1), -1);
            line_spacing = mmax(mmin(line_spacing || 1, 3), 1);
            var letters = Str(string)[split](E),
                shift = 0,
                notfirst = 0,
                path = E,
                scale;
            R.is(font, "string") && (font = this.getFont(font));
            if (font) {
                scale = (size || 16) / font.face["units-per-em"];
                var bb = font.face.bbox[split](separator),
                    top = +bb[0],
                    lineHeight = bb[3] - bb[1],
                    shifty = 0,
                    height = +bb[1] + (origin == "baseline" ? lineHeight + (+font.face.descent) : lineHeight / 2);
                for (var i = 0, ii = letters.length; i < ii; i++) {
                    if (letters[i] == "\n") {
                        shift = 0;
                        curr = 0;
                        notfirst = 0;
                        shifty += lineHeight * line_spacing;
                    } else {
                        var prev = notfirst && font.glyphs[letters[i - 1]] || {},
                            curr = font.glyphs[letters[i]];
                        shift += notfirst ? (prev.w || font.w) + (prev.k && prev.k[letters[i]] || 0) + (font.w * letter_spacing) : 0;
                        notfirst = 1;
                    }
                    if (curr && curr.d) {
                        path += R.transformPath(curr.d, ["t", shift * scale, shifty * scale, "s", scale, scale, top, height, "t", (x - top) / scale, (y - height) / scale]);
                    }
                }
            }
            return this.path(path).attr({
                fill: "#000",
                stroke: "none"
            });
        };
    
        /*\
         * Paper.add
         [ method ]
         **
         * Imports elements in JSON array in format `{type: type, <attributes>}`
         **
         > Parameters
         **
         - json (array)
         = (object) resulting set of imported elements
         > Usage
         | paper.add([
         |     {
         |         type: "circle",
         |         cx: 10,
         |         cy: 10,
         |         r: 5
         |     },
         |     {
         |         type: "rect",
         |         x: 10,
         |         y: 10,
         |         width: 10,
         |         height: 10,
         |         fill: "#fc0"
         |     }
         | ]);
        \*/
        paperproto.add = function (json) {
            if (R.is(json, "array")) {
                var res = this.set(),
                    i = 0,
                    ii = json.length,
                    j;
                for (; i < ii; i++) {
                    j = json[i] || {};
                    elements[has](j.type) && res.push(this[j.type]().attr(j));
                }
            }
            return res;
        };
    
        /*\
         * Raphael.format
         [ method ]
         **
         * Simple format function. Replaces construction of type “`{<number>}`” to the corresponding argument.
         **
         > Parameters
         **
         - token (string) string to format
         - … (string) rest of arguments will be treated as parameters for replacement
         = (string) formated string
         > Usage
         | var x = 10,
         |     y = 20,
         |     width = 40,
         |     height = 50;
         | // this will draw a rectangular shape equivalent to "M10,20h40v50h-40z"
         | paper.path(Raphael.format("M{0},{1}h{2}v{3}h{4}z", x, y, width, height, -width));
        \*/
        R.format = function (token, params) {
            var args = R.is(params, array) ? [0][concat](params) : arguments;
            token && R.is(token, string) && args.length - 1 && (token = token.replace(formatrg, function (str, i) {
                return args[++i] == null ? E : args[i];
            }));
            return token || E;
        };
        /*\
         * Raphael.fullfill
         [ method ]
         **
         * A little bit more advanced format function than @Raphael.format. Replaces construction of type “`{<name>}`” to the corresponding argument.
         **
         > Parameters
         **
         - token (string) string to format
         - json (object) object which properties will be used as a replacement
         = (string) formated string
         > Usage
         | // this will draw a rectangular shape equivalent to "M10,20h40v50h-40z"
         | paper.path(Raphael.fullfill("M{x},{y}h{dim.width}v{dim.height}h{dim['negative width']}z", {
         |     x: 10,
         |     y: 20,
         |     dim: {
         |         width: 40,
         |         height: 50,
         |         "negative width": -40
         |     }
         | }));
        \*/
        R.fullfill = (function () {
            var tokenRegex = /\{([^\}]+)\}/g,
                objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties
                replacer = function (all, key, obj) {
                    var res = obj;
                    key.replace(objNotationRegex, function (all, name, quote, quotedName, isFunc) {
                        name = name || quotedName;
                        if (res) {
                            if (name in res) {
                                res = res[name];
                            }
                            typeof res == "function" && isFunc && (res = res());
                        }
                    });
                    res = (res == null || res == obj ? all : res) + "";
                    return res;
                };
            return function (str, obj) {
                return String(str).replace(tokenRegex, function (all, key) {
                    return replacer(all, key, obj);
                });
            };
        })();
        /*\
         * Raphael.ninja
         [ method ]
         **
         * If you want to leave no trace of Raphaël (Well, Raphaël creates only one global variable `Raphael`, but anyway.) You can use `ninja` method.
         * Beware, that in this case plugins could stop working, because they are depending on global variable existance.
         **
         = (object) Raphael object
         > Usage
         | (function (local_raphael) {
         |     var paper = local_raphael(10, 10, 320, 200);
         |     …
         | })(Raphael.ninja());
        \*/
        R.ninja = function () {
            oldRaphael.was ? (g.win.Raphael = oldRaphael.is) : delete Raphael;
            return R;
        };
        /*\
         * Raphael.st
         [ property (object) ]
         **
         * You can add your own method to elements and sets. It is wise to add a set method for each element method
         * you added, so you will be able to call the same method on sets too.
         **
         * See also @Raphael.el.
         > Usage
         | Raphael.el.red = function () {
         |     this.attr({fill: "#f00"});
         | };
         | Raphael.st.red = function () {
         |     this.forEach(function (el) {
         |         el.red();
         |     });
         | };
         | // then use it
         | paper.set(paper.circle(100, 100, 20), paper.circle(110, 100, 20)).red();
        \*/
        R.st = setproto;
        // Firefox <3.6 fix: http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html
        (function (doc, loaded, f) {
            if (doc.readyState == null && doc.addEventListener){
                doc.addEventListener(loaded, f = function () {
                    doc.removeEventListener(loaded, f, false);
                    doc.readyState = "complete";
                }, false);
                doc.readyState = "loading";
            }
            function isLoaded() {
                (/in/).test(doc.readyState) ? setTimeout(isLoaded, 9) : R.eve("raphael.DOMload");
            }
            isLoaded();
        })(document, "DOMContentLoaded");
    
        eve.on("raphael.DOMload", function () {
            loaded = true;
        });
    
    // ┌─────────────────────────────────────────────────────────────────────┐ \\
    // │ Raphaël - JavaScript Vector Library                                 │ \\
    // ├─────────────────────────────────────────────────────────────────────┤ \\
    // │ SVG Module                                                          │ \\
    // ├─────────────────────────────────────────────────────────────────────┤ \\
    // │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com)   │ \\
    // │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com)             │ \\
    // │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\
    // └─────────────────────────────────────────────────────────────────────┘ \\
    
    (function(){
        if (!R.svg) {
            return;
        }
        var has = "hasOwnProperty",
            Str = String,
            toFloat = parseFloat,
            toInt = parseInt,
            math = Math,
            mmax = math.max,
            abs = math.abs,
            pow = math.pow,
            separator = /[, ]+/,
            eve = R.eve,
            E = "",
            S = " ";
        var xlink = "http://www.w3.org/1999/xlink",
            markers = {
                block: "M5,0 0,2.5 5,5z",
                classic: "M5,0 0,2.5 5,5 3.5,3 3.5,2z",
                diamond: "M2.5,0 5,2.5 2.5,5 0,2.5z",
                open: "M6,1 1,3.5 6,6",
                oval: "M2.5,0A2.5,2.5,0,0,1,2.5,5 2.5,2.5,0,0,1,2.5,0z"
            },
            markerCounter = {};
        R.toString = function () {
            return  "Your browser supports SVG.\nYou are running Rapha\xebl " + this.version;
        };
        var $ = function (el, attr) {
            if (attr) {
                if (typeof el == "string") {
                    el = $(el);
                }
                for (var key in attr) if (attr[has](key)) {
                    if (key.substring(0, 6) == "xlink:") {
                        el.setAttributeNS(xlink, key.substring(6), Str(attr[key]));
                    } else {
                        el.setAttribute(key, Str(attr[key]));
                    }
                }
            } else {
                el = R._g.doc.createElementNS("http://www.w3.org/2000/svg", el);
                el.style && (el.style.webkitTapHighlightColor = "rgba(0,0,0,0)");
            }
            return el;
        },
        addGradientFill = function (element, gradient) {
            var type = "linear",
                id = element.id + gradient,
                fx = .5, fy = .5,
                o = element.node,
                SVG = element.paper,
                s = o.style,
                el = R._g.doc.getElementById(id);
            if (!el) {
                gradient = Str(gradient).replace(R._radial_gradient, function (all, _fx, _fy) {
                    type = "radial";
                    if (_fx && _fy) {
                        fx = toFloat(_fx);
                        fy = toFloat(_fy);
                        var dir = ((fy > .5) * 2 - 1);
                        pow(fx - .5, 2) + pow(fy - .5, 2) > .25 &&
                            (fy = math.sqrt(.25 - pow(fx - .5, 2)) * dir + .5) &&
                            fy != .5 &&
                            (fy = fy.toFixed(5) - 1e-5 * dir);
                    }
                    return E;
                });
                gradient = gradient.split(/\s*\-\s*/);
                if (type == "linear") {
                    var angle = gradient.shift();
                    angle = -toFloat(angle);
                    if (isNaN(angle)) {
                        return null;
                    }
                    var vector = [0, 0, math.cos(R.rad(angle)), math.sin(R.rad(angle))],
                        max = 1 / (mmax(abs(vector[2]), abs(vector[3])) || 1);
                    vector[2] *= max;
                    vector[3] *= max;
                    if (vector[2] < 0) {
                        vector[0] = -vector[2];
                        vector[2] = 0;
                    }
                    if (vector[3] < 0) {
                        vector[1] = -vector[3];
                        vector[3] = 0;
                    }
                }
                var dots = R._parseDots(gradient);
                if (!dots) {
                    return null;
                }
                id = id.replace(/[\(\)\s,\xb0#]/g, "_");
                
                if (element.gradient && id != element.gradient.id) {
                    SVG.defs.removeChild(element.gradient);
                    delete element.gradient;
                }
    
                if (!element.gradient) {
                    el = $(type + "Gradient", {id: id});
                    element.gradient = el;
                    $(el, type == "radial" ? {
                        fx: fx,
                        fy: fy
                    } : {
                        x1: vector[0],
                        y1: vector[1],
                        x2: vector[2],
                        y2: vector[3],
                        gradientTransform: element.matrix.invert()
                    });
                    SVG.defs.appendChild(el);
                    for (var i = 0, ii = dots.length; i < ii; i++) {
                        el.appendChild($("stop", {
                            offset: dots[i].offset ? dots[i].offset : i ? "100%" : "0%",
                            "stop-color": dots[i].color || "#fff"
                        }));
                    }
                }
            }
            $(o, {
                fill: "url(#" + id + ")",
                opacity: 1,
                "fill-opacity": 1
            });
            s.fill = E;
            s.opacity = 1;
            s.fillOpacity = 1;
            return 1;
        },
        updatePosition = function (o) {
            var bbox = o.getBBox(1);
            $(o.pattern, {patternTransform: o.matrix.invert() + " translate(" + bbox.x + "," + bbox.y + ")"});
        },
        addArrow = function (o, value, isEnd) {
            if (o.type == "path") {
                var values = Str(value).toLowerCase().split("-"),
                    p = o.paper,
                    se = isEnd ? "end" : "start",
                    node = o.node,
                    attrs = o.attrs,
                    stroke = attrs["stroke-width"],
                    i = values.length,
                    type = "classic",
                    from,
                    to,
                    dx,
                    refX,
                    attr,
                    w = 3,
                    h = 3,
                    t = 5;
                while (i--) {
                    switch (values[i]) {
                        case "block":
                        case "classic":
                        case "oval":
                        case "diamond":
                        case "open":
                        case "none":
                            type = values[i];
                            break;
                        case "wide": h = 5; break;
                        case "narrow": h = 2; break;
                        case "long": w = 5; break;
                        case "short": w = 2; break;
                    }
                }
                if (type == "open") {
                    w += 2;
                    h += 2;
                    t += 2;
                    dx = 1;
                    refX = isEnd ? 4 : 1;
                    attr = {
                        fill: "none",
                        stroke: attrs.stroke
                    };
                } else {
                    refX = dx = w / 2;
                    attr = {
                        fill: attrs.stroke,
                        stroke: "none"
                    };
                }
                if (o._.arrows) {
                    if (isEnd) {
                        o._.arrows.endPath && markerCounter[o._.arrows.endPath]--;
                        o._.arrows.endMarker && markerCounter[o._.arrows.endMarker]--;
                    } else {
                        o._.arrows.startPath && markerCounter[o._.arrows.startPath]--;
                        o._.arrows.startMarker && markerCounter[o._.arrows.startMarker]--;
                    }
                } else {
                    o._.arrows = {};
                }
                if (type != "none") {
                    var pathId = "raphael-marker-" + type,
                        markerId = "raphael-marker-" + se + type + w + h;
                    if (!R._g.doc.getElementById(pathId)) {
                        p.defs.appendChild($($("path"), {
                            "stroke-linecap": "round",
                            d: markers[type],
                            id: pathId
                        }));
                        markerCounter[pathId] = 1;
                    } else {
                        markerCounter[pathId]++;
                    }
                    var marker = R._g.doc.getElementById(markerId),
                        use;
                    if (!marker) {
                        marker = $($("marker"), {
                            id: markerId,
                            markerHeight: h,
                            markerWidth: w,
                            orient: "auto",
                            refX: refX,
                            refY: h / 2
                        });
                        use = $($("use"), {
                            "xlink:href": "#" + pathId,
                            transform: (isEnd ? "rotate(180 " + w / 2 + " " + h / 2 + ") " : E) + "scale(" + w / t + "," + h / t + ")",
                            "stroke-width": (1 / ((w / t + h / t) / 2)).toFixed(4)
                        });
                        marker.appendChild(use);
                        p.defs.appendChild(marker);
                        markerCounter[markerId] = 1;
                    } else {
                        markerCounter[markerId]++;
                        use = marker.getElementsByTagName("use")[0];
                    }
                    $(use, attr);
                    var delta = dx * (type != "diamond" && type != "oval");
                    if (isEnd) {
                        from = o._.arrows.startdx * stroke || 0;
                        to = R.getTotalLength(attrs.path) - delta * stroke;
                    } else {
                        from = delta * stroke;
                        to = R.getTotalLength(attrs.path) - (o._.arrows.enddx * stroke || 0);
                    }
                    attr = {};
                    attr["marker-" + se] = "url(#" + markerId + ")";
                    if (to || from) {
                        attr.d = R.getSubpath(attrs.path, from, to);
                    }
                    $(node, attr);
                    o._.arrows[se + "Path"] = pathId;
                    o._.arrows[se + "Marker"] = markerId;
                    o._.arrows[se + "dx"] = delta;
                    o._.arrows[se + "Type"] = type;
                    o._.arrows[se + "String"] = value;
                } else {
                    if (isEnd) {
                        from = o._.arrows.startdx * stroke || 0;
                        to = R.getTotalLength(attrs.path) - from;
                    } else {
                        from = 0;
                        to = R.getTotalLength(attrs.path) - (o._.arrows.enddx * stroke || 0);
                    }
                    o._.arrows[se + "Path"] && $(node, {d: R.getSubpath(attrs.path, from, to)});
                    delete o._.arrows[se + "Path"];
                    delete o._.arrows[se + "Marker"];
                    delete o._.arrows[se + "dx"];
                    delete o._.arrows[se + "Type"];
                    delete o._.arrows[se + "String"];
                }
                for (attr in markerCounter) if (markerCounter[has](attr) && !markerCounter[attr]) {
                    var item = R._g.doc.getElementById(attr);
                    item && item.parentNode.removeChild(item);
                }
            }
        },
        dasharray = {
            "": [0],
            "none": [0],
            "-": [3, 1],
            ".": [1, 1],
            "-.": [3, 1, 1, 1],
            "-..": [3, 1, 1, 1, 1, 1],
            ". ": [1, 3],
            "- ": [4, 3],
            "--": [8, 3],
            "- .": [4, 3, 1, 3],
            "--.": [8, 3, 1, 3],
            "--..": [8, 3, 1, 3, 1, 3]
        },
        addDashes = function (o, value, params) {
            value = dasharray[Str(value).toLowerCase()];
            if (value) {
                var width = o.attrs["stroke-width"] || "1",
                    butt = {round: width, square: width, butt: 0}[o.attrs["stroke-linecap"] || params["stroke-linecap"]] || 0,
                    dashes = [],
                    i = value.length;
                while (i--) {
                    dashes[i] = value[i] * width + ((i % 2) ? 1 : -1) * butt;
                }
                $(o.node, {"stroke-dasharray": dashes.join(",")});
            }
        },
        setFillAndStroke = function (o, params) {
            var node = o.node,
                attrs = o.attrs,
                vis = node.style.visibility;
            node.style.visibility = "hidden";
            for (var att in params) {
                if (params[has](att)) {
                    if (!R._availableAttrs[has](att)) {
                        continue;
                    }
                    var value = params[att];
                    attrs[att] = value;
                    switch (att) {
                        case "blur":
                            o.blur(value);
                            break;
                        case "href":
                        case "title":
                            var hl = $("title");
                            var val = R._g.doc.createTextNode(value);
                            hl.appendChild(val);
                            node.appendChild(hl);
                            break;
                        case "target":
                            var pn = node.parentNode;
                            if (pn.tagName.toLowerCase() != "a") {
                                var hl = $("a");
                                pn.insertBefore(hl, node);
                                hl.appendChild(node);
                                pn = hl;
                            }
                            if (att == "target") {
                                pn.setAttributeNS(xlink, "show", value == "blank" ? "new" : value);
                            } else {
                                pn.setAttributeNS(xlink, att, value);
                            }
                            break;
                        case "cursor":
                            node.style.cursor = value;
                            break;
                        case "transform":
                            o.transform(value);
                            break;
                        case "arrow-start":
                            addArrow(o, value);
                            break;
                        case "arrow-end":
                            addArrow(o, value, 1);
                            break;
                        case "clip-rect":
                            var rect = Str(value).split(separator);
                            if (rect.length == 4) {
                                o.clip && o.clip.parentNode.parentNode.removeChild(o.clip.parentNode);
                                var el = $("clipPath"),
                                    rc = $("rect");
                                el.id = R.createUUID();
                                $(rc, {
                                    x: rect[0],
                                    y: rect[1],
                                    width: rect[2],
                                    height: rect[3]
                                });
                                el.appendChild(rc);
                                o.paper.defs.appendChild(el);
                                $(node, {"clip-path": "url(#" + el.id + ")"});
                                o.clip = rc;
                            }
                            if (!value) {
                                var path = node.getAttribute("clip-path");
                                if (path) {
                                    var clip = R._g.doc.getElementById(path.replace(/(^url\(#|\)$)/g, E));
                                    clip && clip.parentNode.removeChild(clip);
                                    $(node, {"clip-path": E});
                                    delete o.clip;
                                }
                            }
                        break;
                        case "path":
                            if (o.type == "path") {
                                $(node, {d: value ? attrs.path = R._pathToAbsolute(value) : "M0,0"});
                                o._.dirty = 1;
                                if (o._.arrows) {
                                    "startString" in o._.arrows && addArrow(o, o._.arrows.startString);
                                    "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1);
                                }
                            }
                            break;
                        case "width":
                            node.setAttribute(att, value);
                            o._.dirty = 1;
                            if (attrs.fx) {
                                att = "x";
                                value = attrs.x;
                            } else {
                                break;
                            }
                        case "x":
                            if (attrs.fx) {
                                value = -attrs.x - (attrs.width || 0);
                            }
                        case "rx":
                            if (att == "rx" && o.type == "rect") {
                                break;
                            }
                        case "cx":
                            node.setAttribute(att, value);
                            o.pattern && updatePosition(o);
                            o._.dirty = 1;
                            break;
                        case "height":
                            node.setAttribute(att, value);
                            o._.dirty = 1;
                            if (attrs.fy) {
                                att = "y";
                                value = attrs.y;
                            } else {
                                break;
                            }
                        case "y":
                            if (attrs.fy) {
                                value = -attrs.y - (attrs.height || 0);
                            }
                        case "ry":
                            if (att == "ry" && o.type == "rect") {
                                break;
                            }
                        case "cy":
                            node.setAttribute(att, value);
                            o.pattern && updatePosition(o);
                            o._.dirty = 1;
                            break;
                        case "r":
                            if (o.type == "rect") {
                                $(node, {rx: value, ry: value});
                            } else {
                                node.setAttribute(att, value);
                            }
                            o._.dirty = 1;
                            break;
                        case "src":
                            if (o.type == "image") {
                                node.setAttributeNS(xlink, "href", value);
                            }
                            break;
                        case "stroke-width":
                            if (o._.sx != 1 || o._.sy != 1) {
                                value /= mmax(abs(o._.sx), abs(o._.sy)) || 1;
                            }
                            if (o.paper._vbSize) {
                                value *= o.paper._vbSize;
                            }
                            node.setAttribute(att, value);
                            if (attrs["stroke-dasharray"]) {
                                addDashes(o, attrs["stroke-dasharray"], params);
                            }
                            if (o._.arrows) {
                                "startString" in o._.arrows && addArrow(o, o._.arrows.startString);
                                "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1);
                            }
                            break;
                        case "stroke-dasharray":
                            addDashes(o, value, params);
                            break;
                        case "fill":
                            var isURL = Str(value).match(R._ISURL);
                            if (isURL) {
                                el = $("pattern");
                                var ig = $("image");
                                el.id = R.createUUID();
                                $(el, {x: 0, y: 0, patternUnits: "userSpaceOnUse", height: 1, width: 1});
                                $(ig, {x: 0, y: 0, "xlink:href": isURL[1]});
                                el.appendChild(ig);
    
                                (function (el) {
                                    R._preload(isURL[1], function () {
                                        var w = this.offsetWidth,
                                            h = this.offsetHeight;
                                        $(el, {width: w, height: h});
                                        $(ig, {width: w, height: h});
                                        o.paper.safari();
                                    });
                                })(el);
                                o.paper.defs.appendChild(el);
                                $(node, {fill: "url(#" + el.id + ")"});
                                o.pattern = el;
                                o.pattern && updatePosition(o);
                                break;
                            }
                            var clr = R.getRGB(value);
                            if (!clr.error) {
                                delete params.gradient;
                                delete attrs.gradient;
                                !R.is(attrs.opacity, "undefined") &&
                                    R.is(params.opacity, "undefined") &&
                                    $(node, {opacity: attrs.opacity});
                                !R.is(attrs["fill-opacity"], "undefined") &&
                                    R.is(params["fill-opacity"], "undefined") &&
                                    $(node, {"fill-opacity": attrs["fill-opacity"]});
                            } else if ((o.type == "circle" || o.type == "ellipse" || Str(value).charAt() != "r") && addGradientFill(o, value)) {
                                if ("opacity" in attrs || "fill-opacity" in attrs) {
                                    var gradient = R._g.doc.getElementById(node.getAttribute("fill").replace(/^url\(#|\)$/g, E));
                                    if (gradient) {
                                        var stops = gradient.getElementsByTagName("stop");
                                        $(stops[stops.length - 1], {"stop-opacity": ("opacity" in attrs ? attrs.opacity : 1) * ("fill-opacity" in attrs ? attrs["fill-opacity"] : 1)});
                                    }
                                }
                                attrs.gradient = value;
                                attrs.fill = "none";
                                break;
                            }
                            clr[has]("opacity") && $(node, {"fill-opacity": clr.opacity > 1 ? clr.opacity / 100 : clr.opacity});
                        case "stroke":
                            clr = R.getRGB(value);
                            node.setAttribute(att, clr.hex);
                            att == "stroke" && clr[has]("opacity") && $(node, {"stroke-opacity": clr.opacity > 1 ? clr.opacity / 100 : clr.opacity});
                            if (att == "stroke" && o._.arrows) {
                                "startString" in o._.arrows && addArrow(o, o._.arrows.startString);
                                "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1);
                            }
                            break;
                        case "gradient":
                            (o.type == "circle" || o.type == "ellipse" || Str(value).charAt() != "r") && addGradientFill(o, value);
                            break;
                        case "opacity":
                            if (attrs.gradient && !attrs[has]("stroke-opacity")) {
                                $(node, {"stroke-opacity": value > 1 ? value / 100 : value});
                            }
                            // fall
                        case "fill-opacity":
                            if (attrs.gradient) {
                                gradient = R._g.doc.getElementById(node.getAttribute("fill").replace(/^url\(#|\)$/g, E));
                                if (gradient) {
                                    stops = gradient.getElementsByTagName("stop");
                                    $(stops[stops.length - 1], {"stop-opacity": value});
                                }
                                break;
                            }
                        default:
                            att == "font-size" && (value = toInt(value, 10) + "px");
                            var cssrule = att.replace(/(\-.)/g, function (w) {
                                return w.substring(1).toUpperCase();
                            });
                            node.style[cssrule] = value;
                            o._.dirty = 1;
                            node.setAttribute(att, value);
                            break;
                    }
                }
            }
    
            tuneText(o, params);
            node.style.visibility = vis;
        },
        leading = 1.2,
        tuneText = function (el, params) {
            if (el.type != "text" || !(params[has]("text") || params[has]("font") || params[has]("font-size") || params[has]("x") || params[has]("y"))) {
                return;
            }
            var a = el.attrs,
                node = el.node,
                fontSize = node.firstChild ? toInt(R._g.doc.defaultView.getComputedStyle(node.firstChild, E).getPropertyValue("font-size"), 10) : 10;
    
            if (params[has]("text")) {
                a.text = params.text;
                while (node.firstChild) {
                    node.removeChild(node.firstChild);
                }
                var texts = Str(params.text).split("\n"),
                    tspans = [],
                    tspan;
                for (var i = 0, ii = texts.length; i < ii; i++) {
                    tspan = $("tspan");
                    i && $(tspan, {dy: fontSize * leading, x: a.x});
                    tspan.appendChild(R._g.doc.createTextNode(texts[i]));
                    node.appendChild(tspan);
                    tspans[i] = tspan;
                }
            } else {
                tspans = node.getElementsByTagName("tspan");
                for (i = 0, ii = tspans.length; i < ii; i++) if (i) {
                    $(tspans[i], {dy: fontSize * leading, x: a.x});
                } else {
                    $(tspans[0], {dy: 0});
                }
            }
            $(node, {x: a.x, y: a.y});
            el._.dirty = 1;
            var bb = el._getBBox(),
                dif = a.y - (bb.y + bb.height / 2);
            dif && R.is(dif, "finite") && $(tspans[0], {dy: dif});
        },
        Element = function (node, svg) {
            var X = 0,
                Y = 0;
            /*\
             * Element.node
             [ property (object) ]
             **
             * Gives you a reference to the DOM object, so you can assign event handlers or just mess around.
             **
             * Note: Don’t mess with it.
             > Usage
             | // draw a circle at coordinate 10,10 with radius of 10
             | var c = paper.circle(10, 10, 10);
             | c.node.onclick = function () {
             |     c.attr("fill", "red");
             | };
            \*/
            this[0] = this.node = node;
            /*\
             * Element.raphael
             [ property (object) ]
             **
             * Internal reference to @Raphael object. In case it is not available.
             > Usage
             | Raphael.el.red = function () {
             |     var hsb = this.paper.raphael.rgb2hsb(this.attr("fill"));
             |     hsb.h = 1;
             |     this.attr({fill: this.paper.raphael.hsb2rgb(hsb).hex});
             | }
            \*/
            node.raphael = true;
            /*\
             * Element.id
             [ property (number) ]
             **
             * Unique id of the element. Especially usesful when you want to listen to events of the element, 
             * because all events are fired in format `<module>.<action>.<id>`. Also useful for @Paper.getById method.
            \*/
            this.id = R._oid++;
            node.raphaelid = this.id;
            this.matrix = R.matrix();
            this.realPath = null;
            /*\
             * Element.paper
             [ property (object) ]
             **
             * Internal reference to “paper” where object drawn. Mainly for use in plugins and element extensions.
             > Usage
             | Raphael.el.cross = function () {
             |     this.attr({fill: "red"});
             |     this.paper.path("M10,10L50,50M50,10L10,50")
             |         .attr({stroke: "red"});
             | }
            \*/
            this.paper = svg;
            this.attrs = this.attrs || {};
            this._ = {
                transform: [],
                sx: 1,
                sy: 1,
                deg: 0,
                dx: 0,
                dy: 0,
                dirty: 1
            };
            !svg.bottom && (svg.bottom = this);
            /*\
             * Element.prev
             [ property (object) ]
             **
             * Reference to the previous element in the hierarchy.
            \*/
            this.prev = svg.top;
            svg.top && (svg.top.next = this);
            svg.top = this;
            /*\
             * Element.next
             [ property (object) ]
             **
             * Reference to the next element in the hierarchy.
            \*/
            this.next = null;
        },
        elproto = R.el;
    
        Element.prototype = elproto;
        elproto.constructor = Element;
    
        R._engine.path = function (pathString, SVG) {
            var el = $("path");
            SVG.canvas && SVG.canvas.appendChild(el);
            var p = new Element(el, SVG);
            p.type = "path";
            setFillAndStroke(p, {
                fill: "none",
                stroke: "#000",
                path: pathString
            });
            return p;
        };
        /*\
         * Element.rotate
         [ method ]
         **
         * Deprecated! Use @Element.transform instead.
         * Adds rotation by given angle around given point to the list of
         * transformations of the element.
         > Parameters
         - deg (number) angle in degrees
         - cx (number) #optional x coordinate of the centre of rotation
         - cy (number) #optional y coordinate of the centre of rotation
         * If cx & cy aren’t specified centre of the shape is used as a point of rotation.
         = (object) @Element
        \*/
        elproto.rotate = function (deg, cx, cy) {
            if (this.removed) {
                return this;
            }
            deg = Str(deg).split(separator);
            if (deg.length - 1) {
                cx = toFloat(deg[1]);
                cy = toFloat(deg[2]);
            }
            deg = toFloat(deg[0]);
            (cy == null) && (cx = cy);
            if (cx == null || cy == null) {
                var bbox = this.getBBox(1);
                cx = bbox.x + bbox.width / 2;
                cy = bbox.y + bbox.height / 2;
            }
            this.transform(this._.transform.concat([["r", deg, cx, cy]]));
            return this;
        };
        /*\
         * Element.scale
         [ method ]
         **
         * Deprecated! Use @Element.transform instead.
         * Adds scale by given amount relative to given point to the list of
         * transformations of the element.
         > Parameters
         - sx (number) horisontal scale amount
         - sy (number) vertical scale amount
         - cx (number) #optional x coordinate of the centre of scale
         - cy (number) #optional y coordinate of the centre of scale
         * If cx & cy aren’t specified centre of the shape is used instead.
         = (object) @Element
        \*/
        elproto.scale = function (sx, sy, cx, cy) {
            if (this.removed) {
                return this;
            }
            sx = Str(sx).split(separator);
            if (sx.length - 1) {
                sy = toFloat(sx[1]);
                cx = toFloat(sx[2]);
                cy = toFloat(sx[3]);
            }
            sx = toFloat(sx[0]);
            (sy == null) && (sy = sx);
            (cy == null) && (cx = cy);
            if (cx == null || cy == null) {
                var bbox = this.getBBox(1);
            }
            cx = cx == null ? bbox.x + bbox.width / 2 : cx;
            cy = cy == null ? bbox.y + bbox.height / 2 : cy;
            this.transform(this._.transform.concat([["s", sx, sy, cx, cy]]));
            return this;
        };
        /*\
         * Element.translate
         [ method ]
         **
         * Deprecated! Use @Element.transform instead.
         * Adds translation by given amount to the list of transformations of the element.
         > Parameters
         - dx (number) horisontal shift
         - dy (number) vertical shift
         = (object) @Element
        \*/
        elproto.translate = function (dx, dy) {
            if (this.removed) {
                return this;
            }
            dx = Str(dx).split(separator);
            if (dx.length - 1) {
                dy = toFloat(dx[1]);
            }
            dx = toFloat(dx[0]) || 0;
            dy = +dy || 0;
            this.transform(this._.transform.concat([["t", dx, dy]]));
            return this;
        };
        /*\
         * Element.transform
         [ method ]
         **
         * Adds transformation to the element which is separate to other attributes,
         * i.e. translation doesn’t change `x` or `y` of the rectange. The format
         * of transformation string is similar to the path string syntax:
         | "t100,100r30,100,100s2,2,100,100r45s1.5"
         * Each letter is a command. There are four commands: `t` is for translate, `r` is for rotate, `s` is for
         * scale and `m` is for matrix.
         *
         * There are also alternative “absolute” translation, rotation and scale: `T`, `R` and `S`. They will not take previous transformation into account. For example, `...T100,0` will always move element 100 px horisontally, while `...t100,0` could move it vertically if there is `r90` before. Just compare results of `r90t100,0` and `r90T100,0`.
         *
         * So, the example line above could be read like “translate by 100, 100; rotate 30° around 100, 100; scale twice around 100, 100;
         * rotate 45° around centre; scale 1.5 times relative to centre”. As you can see rotate and scale commands have origin
         * coordinates as optional parameters, the default is the centre point of the element.
         * Matrix accepts six parameters.
         > Usage
         | var el = paper.rect(10, 20, 300, 200);
         | // translate 100, 100, rotate 45°, translate -100, 0
         | el.transform("t100,100r45t-100,0");
         | // if you want you can append or prepend transformations
         | el.transform("...t50,50");
         | el.transform("s2...");
         | // or even wrap
         | el.transform("t50,50...t-50-50");
         | // to reset transformation call method with empty string
         | el.transform("");
         | // to get current value call it without parameters
         | console.log(el.transform());
         > Parameters
         - tstr (string) #optional transformation string
         * If tstr isn’t specified
         = (string) current transformation string
         * else
         = (object) @Element
        \*/
        elproto.transform = function (tstr) {
            var _ = this._;
            if (tstr == null) {
                return _.transform;
            }
            R._extractTransform(this, tstr);
    
            this.clip && $(this.clip, {transform: this.matrix.invert()});
            this.pattern && updatePosition(this);
            this.node && $(this.node, {transform: this.matrix});
        
            if (_.sx != 1 || _.sy != 1) {
                var sw = this.attrs[has]("stroke-width") ? this.attrs["stroke-width"] : 1;
                this.attr({"stroke-width": sw});
            }
    
            return this;
        };
        /*\
         * Element.hide
         [ method ]
         **
         * Makes element invisible. See @Element.show.
         = (object) @Element
        \*/
        elproto.hide = function () {
            !this.removed && this.paper.safari(this.node.style.display = "none");
            return this;
        };
        /*\
         * Element.show
         [ method ]
         **
         * Makes element visible. See @Element.hide.
         = (object) @Element
        \*/
        elproto.show = function () {
            !this.removed && this.paper.safari(this.node.style.display = "");
            return this;
        };
        /*\
         * Element.remove
         [ method ]
         **
         * Removes element from the paper.
        \*/
        elproto.remove = function () {
            if (this.removed || !this.node.parentNode) {
                return;
            }
            var paper = this.paper;
            paper.__set__ && paper.__set__.exclude(this);
            eve.unbind("raphael.*.*." + this.id);
            if (this.gradient) {
                paper.defs.removeChild(this.gradient);
            }
            R._tear(this, paper);
            if (this.node.parentNode.tagName.toLowerCase() == "a") {
                this.node.parentNode.parentNode.removeChild(this.node.parentNode);
            } else {
                this.node.parentNode.removeChild(this.node);
            }
            for (var i in this) {
                this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
            }
            this.removed = true;
        };
        elproto._getBBox = function () {
            if (this.node.style.display == "none") {
                this.show();
                var hide = true;
            }
            var bbox = {};
            try {
                bbox = this.node.getBBox();
            } catch(e) {
                // Firefox 3.0.x plays badly here
            } finally {
                bbox = bbox || {};
            }
            hide && this.hide();
            return bbox;
        };
        /*\
         * Element.attr
         [ method ]
         **
         * Sets the attributes of the element.
         > Parameters
         - attrName (string) attribute’s name
         - value (string) value
         * or
         - params (object) object of name/value pairs
         * or
         - attrName (string) attribute’s name
         * or
         - attrNames (array) in this case method returns array of current values for given attribute names
         = (object) @Element if attrsName & value or params are passed in.
         = (...) value of the attribute if only attrsName is passed in.
         = (array) array of values of the attribute if attrsNames is passed in.
         = (object) object of attributes if nothing is passed in.
         > Possible parameters
         # <p>Please refer to the <a href="http://www.w3.org/TR/SVG/" title="The W3C Recommendation for the SVG language describes these properties in detail.">SVG specification</a> for an explanation of these parameters.</p>
         o arrow-end (string) arrowhead on the end of the path. The format for string is `<type>[-<width>[-<length>]]`. Possible types: `classic`, `block`, `open`, `oval`, `diamond`, `none`, width: `wide`, `narrow`, `medium`, length: `long`, `short`, `midium`.
         o clip-rect (string) comma or space separated values: x, y, width and height
         o cursor (string) CSS type of the cursor
         o cx (number) the x-axis coordinate of the center of the circle, or ellipse
         o cy (number) the y-axis coordinate of the center of the circle, or ellipse
         o fill (string) colour, gradient or image
         o fill-opacity (number)
         o font (string)
         o font-family (string)
         o font-size (number) font size in pixels
         o font-weight (string)
         o height (number)
         o href (string) URL, if specified element behaves as hyperlink
         o opacity (number)
         o path (string) SVG path string format
         o r (number) radius of the circle, ellipse or rounded corner on the rect
         o rx (number) horisontal radius of the ellipse
         o ry (number) vertical radius of the ellipse
         o src (string) image URL, only works for @Element.image element
         o stroke (string) stroke colour
         o stroke-dasharray (string) [“”, “`-`”, “`.`”, “`-.`”, “`-..`”, “`. `”, “`- `”, “`--`”, “`- .`”, “`--.`”, “`--..`”]
         o stroke-linecap (string) [“`butt`”, “`square`”, “`round`”]
         o stroke-linejoin (string) [“`bevel`”, “`round`”, “`miter`”]
         o stroke-miterlimit (number)
         o stroke-opacity (number)
         o stroke-width (number) stroke width in pixels, default is '1'
         o target (string) used with href
         o text (string) contents of the text element. Use `\n` for multiline text
         o text-anchor (string) [“`start`”, “`middle`”, “`end`”], default is “`middle`”
         o title (string) will create tooltip with a given text
         o transform (string) see @Element.transform
         o width (number)
         o x (number)
         o y (number)
         > Gradients
         * Linear gradient format: “`‹angle›-‹colour›[-‹colour›[:‹offset›]]*-‹colour›`”, example: “`90-#fff-#000`” – 90°
         * gradient from white to black or “`0-#fff-#f00:20-#000`” – 0° gradient from white via red (at 20%) to black.
         *
         * radial gradient: “`r[(‹fx›, ‹fy›)]‹colour›[-‹colour›[:‹offset›]]*-‹colour›`”, example: “`r#fff-#000`” –
         * gradient from white to black or “`r(0.25, 0.75)#fff-#000`” – gradient from white to black with focus point
         * at 0.25, 0.75. Focus point coordinates are in 0..1 range. Radial gradients can only be applied to circles and ellipses.
         > Path String
         # <p>Please refer to <a href="http://www.w3.org/TR/SVG/paths.html#PathData" title="Details of a path’s data attribute’s format are described in the SVG specification.">SVG documentation regarding path string</a>. Raphaël fully supports it.</p>
         > Colour Parsing
         # <ul>
         #     <li>Colour name (“<code>red</code>”, “<code>green</code>”, “<code>cornflowerblue</code>”, etc)</li>
         #     <li>#••• — shortened HTML colour: (“<code>#000</code>”, “<code>#fc0</code>”, etc)</li>
         #     <li>#•••••• — full length HTML colour: (“<code>#000000</code>”, “<code>#bd2300</code>”)</li>
         #     <li>rgb(•••, •••, •••) — red, green and blue channels’ values: (“<code>rgb(200,&nbsp;100,&nbsp;0)</code>”)</li>
         #     <li>rgb(•••%, •••%, •••%) — same as above, but in %: (“<code>rgb(100%,&nbsp;175%,&nbsp;0%)</code>”)</li>
         #     <li>rgba(•••, •••, •••, •••) — red, green and blue channels’ values: (“<code>rgba(200,&nbsp;100,&nbsp;0, .5)</code>”)</li>
         #     <li>rgba(•••%, •••%, •••%, •••%) — same as above, but in %: (“<code>rgba(100%,&nbsp;175%,&nbsp;0%, 50%)</code>”)</li>
         #     <li>hsb(•••, •••, •••) — hue, saturation and brightness values: (“<code>hsb(0.5,&nbsp;0.25,&nbsp;1)</code>”)</li>
         #     <li>hsb(•••%, •••%, •••%) — same as above, but in %</li>
         #     <li>hsba(•••, •••, •••, •••) — same as above, but with opacity</li>
         #     <li>hsl(•••, •••, •••) — almost the same as hsb, see <a href="http://en.wikipedia.org/wiki/HSL_and_HSV" title="HSL and HSV - Wikipedia, the free encyclopedia">Wikipedia page</a></li>
         #     <li>hsl(•••%, •••%, •••%) — same as above, but in %</li>
         #     <li>hsla(•••, •••, •••, •••) — same as above, but with opacity</li>
         #     <li>Optionally for hsb and hsl you could specify hue as a degree: “<code>hsl(240deg,&nbsp;1,&nbsp;.5)</code>” or, if you want to go fancy, “<code>hsl(240°,&nbsp;1,&nbsp;.5)</code>”</li>
         # </ul>
        \*/
        elproto.attr = function (name, value) {
            if (this.removed) {
                return this;
            }
            if (name == null) {
                var res = {};
                for (var a in this.attrs) if (this.attrs[has](a)) {
                    res[a] = this.attrs[a];
                }
                res.gradient && res.fill == "none" && (res.fill = res.gradient) && delete res.gradient;
                res.transform = this._.transform;
                return res;
            }
            if (value == null && R.is(name, "string")) {
                if (name == "fill" && this.attrs.fill == "none" && this.attrs.gradient) {
                    return this.attrs.gradient;
                }
                if (name == "transform") {
                    return this._.transform;
                }
                var names = name.split(separator),
                    out = {};
                for (var i = 0, ii = names.length; i < ii; i++) {
                    name = names[i];
                    if (name in this.attrs) {
                        out[name] = this.attrs[name];
                    } else if (R.is(this.paper.customAttributes[name], "function")) {
                        out[name] = this.paper.customAttributes[name].def;
                    } else {
                        out[name] = R._availableAttrs[name];
                    }
                }
                return ii - 1 ? out : out[names[0]];
            }
            if (value == null && R.is(name, "array")) {
                out = {};
                for (i = 0, ii = name.length; i < ii; i++) {
                    out[name[i]] = this.attr(name[i]);
                }
                return out;
            }
            if (value != null) {
                var params = {};
                params[name] = value;
            } else if (name != null && R.is(name, "object")) {
                params = name;
            }
            for (var key in params) {
                eve("raphael.attr." + key + "." + this.id, this, params[key]);
            }
            for (key in this.paper.customAttributes) if (this.paper.customAttributes[has](key) && params[has](key) && R.is(this.paper.customAttributes[key], "function")) {
                var par = this.paper.customAttributes[key].apply(this, [].concat(params[key]));
                this.attrs[key] = params[key];
                for (var subkey in par) if (par[has](subkey)) {
                    params[subkey] = par[subkey];
                }
            }
            setFillAndStroke(this, params);
            return this;
        };
        /*\
         * Element.toFront
         [ method ]
         **
         * Moves the element so it is the closest to the viewer’s eyes, on top of other elements.
         = (object) @Element
        \*/
        elproto.toFront = function () {
            if (this.removed) {
                return this;
            }
            if (this.node.parentNode.tagName.toLowerCase() == "a") {
                this.node.parentNode.parentNode.appendChild(this.node.parentNode);
            } else {
                this.node.parentNode.appendChild(this.node);
            }
            var svg = this.paper;
            svg.top != this && R._tofront(this, svg);
            return this;
        };
        /*\
         * Element.toBack
         [ method ]
         **
         * Moves the element so it is the furthest from the viewer’s eyes, behind other elements.
         = (object) @Element
        \*/
        elproto.toBack = function () {
            if (this.removed) {
                return this;
            }
            var parent = this.node.parentNode;
            if (parent.tagName.toLowerCase() == "a") {
                parent.parentNode.insertBefore(this.node.parentNode, this.node.parentNode.parentNode.firstChild); 
            } else if (parent.firstChild != this.node) {
                parent.insertBefore(this.node, this.node.parentNode.firstChild);
            }
            R._toback(this, this.paper);
            var svg = this.paper;
            return this;
        };
        /*\
         * Element.insertAfter
         [ method ]
         **
         * Inserts current object after the given one.
         = (object) @Element
        \*/
        elproto.insertAfter = function (element) {
            if (this.removed) {
                return this;
            }
            var node = element.node || element[element.length - 1].node;
            if (node.nextSibling) {
                node.parentNode.insertBefore(this.node, node.nextSibling);
            } else {
                node.parentNode.appendChild(this.node);
            }
            R._insertafter(this, element, this.paper);
            return this;
        };
        /*\
         * Element.insertBefore
         [ method ]
         **
         * Inserts current object before the given one.
         = (object) @Element
        \*/
        elproto.insertBefore = function (element) {
            if (this.removed) {
                return this;
            }
            var node = element.node || element[0].node;
            node.parentNode.insertBefore(this.node, node);
            R._insertbefore(this, element, this.paper);
            return this;
        };
        elproto.blur = function (size) {
            // Experimental. No Safari support. Use it on your own risk.
            var t = this;
            if (+size !== 0) {
                var fltr = $("filter"),
                    blur = $("feGaussianBlur");
                t.attrs.blur = size;
                fltr.id = R.createUUID();
                $(blur, {stdDeviation: +size || 1.5});
                fltr.appendChild(blur);
                t.paper.defs.appendChild(fltr);
                t._blur = fltr;
                $(t.node, {filter: "url(#" + fltr.id + ")"});
            } else {
                if (t._blur) {
                    t._blur.parentNode.removeChild(t._blur);
                    delete t._blur;
                    delete t.attrs.blur;
                }
                t.node.removeAttribute("filter");
            }
            return t;
        };
        R._engine.circle = function (svg, x, y, r) {
            var el = $("circle");
            svg.canvas && svg.canvas.appendChild(el);
            var res = new Element(el, svg);
            res.attrs = {cx: x, cy: y, r: r, fill: "none", stroke: "#000"};
            res.type = "circle";
            $(el, res.attrs);
            return res;
        };
        R._engine.rect = function (svg, x, y, w, h, r) {
            var el = $("rect");
            svg.canvas && svg.canvas.appendChild(el);
            var res = new Element(el, svg);
            res.attrs = {x: x, y: y, width: w, height: h, r: r || 0, rx: r || 0, ry: r || 0, fill: "none", stroke: "#000"};
            res.type = "rect";
            $(el, res.attrs);
            return res;
        };
        R._engine.ellipse = function (svg, x, y, rx, ry) {
            var el = $("ellipse");
            svg.canvas && svg.canvas.appendChild(el);
            var res = new Element(el, svg);
            res.attrs = {cx: x, cy: y, rx: rx, ry: ry, fill: "none", stroke: "#000"};
            res.type = "ellipse";
            $(el, res.attrs);
            return res;
        };
        R._engine.image = function (svg, src, x, y, w, h) {
            var el = $("image");
            $(el, {x: x, y: y, width: w, height: h, preserveAspectRatio: "none"});
            el.setAttributeNS(xlink, "href", src);
            svg.canvas && svg.canvas.appendChild(el);
            var res = new Element(el, svg);
            res.attrs = {x: x, y: y, width: w, height: h, src: src};
            res.type = "image";
            return res;
        };
        R._engine.text = function (svg, x, y, text) {
            var el = $("text");
            svg.canvas && svg.canvas.appendChild(el);
            var res = new Element(el, svg);
            res.attrs = {
                x: x,
                y: y,
                "text-anchor": "middle",
                text: text,
                font: R._availableAttrs.font,
                stroke: "none",
                fill: "#000"
            };
            res.type = "text";
            setFillAndStroke(res, res.attrs);
            return res;
        };
        R._engine.setSize = function (width, height) {
            this.width = width || this.width;
            this.height = height || this.height;
            this.canvas.setAttribute("width", this.width);
            this.canvas.setAttribute("height", this.height);
            if (this._viewBox) {
                this.setViewBox.apply(this, this._viewBox);
            }
            return this;
        };
        R._engine.create = function () {
            var con = R._getContainer.apply(0, arguments),
                container = con && con.container,
                x = con.x,
                y = con.y,
                width = con.width,
                height = con.height;
            if (!container) {
                throw new Error("SVG container not found.");
            }
            var cnvs = $("svg"),
                css = "overflow:hidden;",
                isFloating;
            x = x || 0;
            y = y || 0;
            width = width || 512;
            height = height || 342;
            $(cnvs, {
                height: height,
                version: 1.1,
                width: width,
                xmlns: "http://www.w3.org/2000/svg"
            });
            if (container == 1) {
                cnvs.style.cssText = css + "position:absolute;left:" + x + "px;top:" + y + "px";
                R._g.doc.body.appendChild(cnvs);
                isFloating = 1;
            } else {
                cnvs.style.cssText = css + "position:relative";
                if (container.firstChild) {
                    container.insertBefore(cnvs, container.firstChild);
                } else {
                    container.appendChild(cnvs);
                }
            }
            container = new R._Paper;
            container.width = width;
            container.height = height;
            container.canvas = cnvs;
            container.clear();
            container._left = container._top = 0;
            isFloating && (container.renderfix = function () {});
            container.renderfix();
            return container;
        };
        R._engine.setViewBox = function (x, y, w, h, fit) {
            eve("raphael.setViewBox", this, this._viewBox, [x, y, w, h, fit]);
            var size = mmax(w / this.width, h / this.height),
                top = this.top,
                aspectRatio = fit ? "meet" : "xMinYMin",
                vb,
                sw;
            if (x == null) {
                if (this._vbSize) {
                    size = 1;
                }
                delete this._vbSize;
                vb = "0 0 " + this.width + S + this.height;
            } else {
                this._vbSize = size;
                vb = x + S + y + S + w + S + h;
            }
            $(this.canvas, {
                viewBox: vb,
                preserveAspectRatio: aspectRatio
            });
            while (size && top) {
                sw = "stroke-width" in top.attrs ? top.attrs["stroke-width"] : 1;
                top.attr({"stroke-width": sw});
                top._.dirty = 1;
                top._.dirtyT = 1;
                top = top.prev;
            }
            this._viewBox = [x, y, w, h, !!fit];
            return this;
        };
        /*\
         * Paper.renderfix
         [ method ]
         **
         * Fixes the issue of Firefox and IE9 regarding subpixel rendering. If paper is dependant
         * on other elements after reflow it could shift half pixel which cause for lines to lost their crispness.
         * This method fixes the issue.
         **
           Special thanks to Mariusz Nowak (http://www.medikoo.com/) for this method.
        \*/
        R.prototype.renderfix = function () {
            var cnvs = this.canvas,
                s = cnvs.style,
                pos;
            try {
                pos = cnvs.getScreenCTM() || cnvs.createSVGMatrix();
            } catch (e) {
                pos = cnvs.createSVGMatrix();
            }
            var left = -pos.e % 1,
                top = -pos.f % 1;
            if (left || top) {
                if (left) {
                    this._left = (this._left + left) % 1;
                    s.left = this._left + "px";
                }
                if (top) {
                    this._top = (this._top + top) % 1;
                    s.top = this._top + "px";
                }
            }
        };
        /*\
         * Paper.clear
         [ method ]
         **
         * Clears the paper, i.e. removes all the elements.
        \*/
        R.prototype.clear = function () {
            R.eve("raphael.clear", this);
            var c = this.canvas;
            while (c.firstChild) {
                c.removeChild(c.firstChild);
            }
            this.bottom = this.top = null;
            (this.desc = $("desc")).appendChild(R._g.doc.createTextNode("Created with Rapha\xebl " + R.version));
            c.appendChild(this.desc);
            c.appendChild(this.defs = $("defs"));
        };
        /*\
         * Paper.remove
         [ method ]
         **
         * Removes the paper from the DOM.
        \*/
        R.prototype.remove = function () {
            eve("raphael.remove", this);
            this.canvas.parentNode && this.canvas.parentNode.removeChild(this.canvas);
            for (var i in this) {
                this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
            }
        };
        var setproto = R.st;
        for (var method in elproto) if (elproto[has](method) && !setproto[has](method)) {
            setproto[method] = (function (methodname) {
                return function () {
                    var arg = arguments;
                    return this.forEach(function (el) {
                        el[methodname].apply(el, arg);
                    });
                };
            })(method);
        }
    })();
    
    // ┌─────────────────────────────────────────────────────────────────────┐ \\
    // │ Raphaël - JavaScript Vector Library                                 │ \\
    // ├─────────────────────────────────────────────────────────────────────┤ \\
    // │ VML Module                                                          │ \\
    // ├─────────────────────────────────────────────────────────────────────┤ \\
    // │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com)   │ \\
    // │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com)             │ \\
    // │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\
    // └─────────────────────────────────────────────────────────────────────┘ \\
    
    (function(){
        if (!R.vml) {
            return;
        }
        var has = "hasOwnProperty",
            Str = String,
            toFloat = parseFloat,
            math = Math,
            round = math.round,
            mmax = math.max,
            mmin = math.min,
            abs = math.abs,
            fillString = "fill",
            separator = /[, ]+/,
            eve = R.eve,
            ms = " progid:DXImageTransform.Microsoft",
            S = " ",
            E = "",
            map = {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
            bites = /([clmz]),?([^clmz]*)/gi,
            blurregexp = / progid:\S+Blur\([^\)]+\)/g,
            val = /-?[^,\s-]+/g,
            cssDot = "position:absolute;left:0;top:0;width:1px;height:1px",
            zoom = 21600,
            pathTypes = {path: 1, rect: 1, image: 1},
            ovalTypes = {circle: 1, ellipse: 1},
            path2vml = function (path) {
                var total =  /[ahqstv]/ig,
                    command = R._pathToAbsolute;
                Str(path).match(total) && (command = R._path2curve);
                total = /[clmz]/g;
                if (command == R._pathToAbsolute && !Str(path).match(total)) {
                    var res = Str(path).replace(bites, function (all, command, args) {
                        var vals = [],
                            isMove = command.toLowerCase() == "m",
                            res = map[command];
                        args.replace(val, function (value) {
                            if (isMove && vals.length == 2) {
                                res += vals + map[command == "m" ? "l" : "L"];
                                vals = [];
                            }
                            vals.push(round(value * zoom));
                        });
                        return res + vals;
                    });
                    return res;
                }
                var pa = command(path), p, r;
                res = [];
                for (var i = 0, ii = pa.length; i < ii; i++) {
                    p = pa[i];
                    r = pa[i][0].toLowerCase();
                    r == "z" && (r = "x");
                    for (var j = 1, jj = p.length; j < jj; j++) {
                        r += round(p[j] * zoom) + (j != jj - 1 ? "," : E);
                    }
                    res.push(r);
                }
                return res.join(S);
            },
            compensation = function (deg, dx, dy) {
                var m = R.matrix();
                m.rotate(-deg, .5, .5);
                return {
                    dx: m.x(dx, dy),
                    dy: m.y(dx, dy)
                };
            },
            setCoords = function (p, sx, sy, dx, dy, deg) {
                var _ = p._,
                    m = p.matrix,
                    fillpos = _.fillpos,
                    o = p.node,
                    s = o.style,
                    y = 1,
                    flip = "",
                    dxdy,
                    kx = zoom / sx,
                    ky = zoom / sy;
                s.visibility = "hidden";
                if (!sx || !sy) {
                    return;
                }
                o.coordsize = abs(kx) + S + abs(ky);
                s.rotation = deg * (sx * sy < 0 ? -1 : 1);
                if (deg) {
                    var c = compensation(deg, dx, dy);
                    dx = c.dx;
                    dy = c.dy;
                }
                sx < 0 && (flip += "x");
                sy < 0 && (flip += " y") && (y = -1);
                s.flip = flip;
                o.coordorigin = (dx * -kx) + S + (dy * -ky);
                if (fillpos || _.fillsize) {
                    var fill = o.getElementsByTagName(fillString);
                    fill = fill && fill[0];
                    o.removeChild(fill);
                    if (fillpos) {
                        c = compensation(deg, m.x(fillpos[0], fillpos[1]), m.y(fillpos[0], fillpos[1]));
                        fill.position = c.dx * y + S + c.dy * y;
                    }
                    if (_.fillsize) {
                        fill.size = _.fillsize[0] * abs(sx) + S + _.fillsize[1] * abs(sy);
                    }
                    o.appendChild(fill);
                }
                s.visibility = "visible";
            };
        R.toString = function () {
            return  "Your browser doesn\u2019t support SVG. Falling down to VML.\nYou are running Rapha\xebl " + this.version;
        };
        var addArrow = function (o, value, isEnd) {
            var values = Str(value).toLowerCase().split("-"),
                se = isEnd ? "end" : "start",
                i = values.length,
                type = "classic",
                w = "medium",
                h = "medium";
            while (i--) {
                switch (values[i]) {
                    case "block":
                    case "classic":
                    case "oval":
                    case "diamond":
                    case "open":
                    case "none":
                        type = values[i];
                        break;
                    case "wide":
                    case "narrow": h = values[i]; break;
                    case "long":
                    case "short": w = values[i]; break;
                }
            }
            var stroke = o.node.getElementsByTagName("stroke")[0];
            stroke[se + "arrow"] = type;
            stroke[se + "arrowlength"] = w;
            stroke[se + "arrowwidth"] = h;
        },
        setFillAndStroke = function (o, params) {
            // o.paper.canvas.style.display = "none";
            o.attrs = o.attrs || {};
            var node = o.node,
                a = o.attrs,
                s = node.style,
                xy,
                newpath = pathTypes[o.type] && (params.x != a.x || params.y != a.y || params.width != a.width || params.height != a.height || params.cx != a.cx || params.cy != a.cy || params.rx != a.rx || params.ry != a.ry || params.r != a.r),
                isOval = ovalTypes[o.type] && (a.cx != params.cx || a.cy != params.cy || a.r != params.r || a.rx != params.rx || a.ry != params.ry),
                res = o;
    
    
            for (var par in params) if (params[has](par)) {
                a[par] = params[par];
            }
            if (newpath) {
                a.path = R._getPath[o.type](o);
                o._.dirty = 1;
            }
            params.href && (node.href = params.href);
            params.title && (node.title = params.title);
            params.target && (node.target = params.target);
            params.cursor && (s.cursor = params.cursor);
            "blur" in params && o.blur(params.blur);
            if (params.path && o.type == "path" || newpath) {
                node.path = path2vml(~Str(a.path).toLowerCase().indexOf("r") ? R._pathToAbsolute(a.path) : a.path);
                if (o.type == "image") {
                    o._.fillpos = [a.x, a.y];
                    o._.fillsize = [a.width, a.height];
                    setCoords(o, 1, 1, 0, 0, 0);
                }
            }
            "transform" in params && o.transform(params.transform);
            if (isOval) {
                var cx = +a.cx,
                    cy = +a.cy,
                    rx = +a.rx || +a.r || 0,
                    ry = +a.ry || +a.r || 0;
                node.path = R.format("ar{0},{1},{2},{3},{4},{1},{4},{1}x", round((cx - rx) * zoom), round((cy - ry) * zoom), round((cx + rx) * zoom), round((cy + ry) * zoom), round(cx * zoom));
                o._.dirty = 1;
            }
            if ("clip-rect" in params) {
                var rect = Str(params["clip-rect"]).split(separator);
                if (rect.length == 4) {
                    rect[2] = +rect[2] + (+rect[0]);
                    rect[3] = +rect[3] + (+rect[1]);
                    var div = node.clipRect || R._g.doc.createElement("div"),
                        dstyle = div.style;
                    dstyle.clip = R.format("rect({1}px {2}px {3}px {0}px)", rect);
                    if (!node.clipRect) {
                        dstyle.position = "absolute";
                        dstyle.top = 0;
                        dstyle.left = 0;
                        dstyle.width = o.paper.width + "px";
                        dstyle.height = o.paper.height + "px";
                        node.parentNode.insertBefore(div, node);
                        div.appendChild(node);
                        node.clipRect = div;
                    }
                }
                if (!params["clip-rect"]) {
                    node.clipRect && (node.clipRect.style.clip = "auto");
                }
            }
            if (o.textpath) {
                var textpathStyle = o.textpath.style;
                params.font && (textpathStyle.font = params.font);
                params["font-family"] && (textpathStyle.fontFamily = '"' + params["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g, E) + '"');
                params["font-size"] && (textpathStyle.fontSize = params["font-size"]);
                params["font-weight"] && (textpathStyle.fontWeight = params["font-weight"]);
                params["font-style"] && (textpathStyle.fontStyle = params["font-style"]);
            }
            if ("arrow-start" in params) {
                addArrow(res, params["arrow-start"]);
            }
            if ("arrow-end" in params) {
                addArrow(res, params["arrow-end"], 1);
            }
            if (params.opacity != null || 
                params["stroke-width"] != null ||
                params.fill != null ||
                params.src != null ||
                params.stroke != null ||
                params["stroke-width"] != null ||
                params["stroke-opacity"] != null ||
                params["fill-opacity"] != null ||
                params["stroke-dasharray"] != null ||
                params["stroke-miterlimit"] != null ||
                params["stroke-linejoin"] != null ||
                params["stroke-linecap"] != null) {
                var fill = node.getElementsByTagName(fillString),
                    newfill = false;
                fill = fill && fill[0];
                !fill && (newfill = fill = createNode(fillString));
                if (o.type == "image" && params.src) {
                    fill.src = params.src;
                }
                params.fill && (fill.on = true);
                if (fill.on == null || params.fill == "none" || params.fill === null) {
                    fill.on = false;
                }
                if (fill.on && params.fill) {
                    var isURL = Str(params.fill).match(R._ISURL);
                    if (isURL) {
                        fill.parentNode == node && node.removeChild(fill);
                        fill.rotate = true;
                        fill.src = isURL[1];
                        fill.type = "tile";
                        var bbox = o.getBBox(1);
                        fill.position = bbox.x + S + bbox.y;
                        o._.fillpos = [bbox.x, bbox.y];
    
                        R._preload(isURL[1], function () {
                            o._.fillsize = [this.offsetWidth, this.offsetHeight];
                        });
                    } else {
                        fill.color = R.getRGB(params.fill).hex;
                        fill.src = E;
                        fill.type = "solid";
                        if (R.getRGB(params.fill).error && (res.type in {circle: 1, ellipse: 1} || Str(params.fill).charAt() != "r") && addGradientFill(res, params.fill, fill)) {
                            a.fill = "none";
                            a.gradient = params.fill;
                            fill.rotate = false;
                        }
                    }
                }
                if ("fill-opacity" in params || "opacity" in params) {
                    var opacity = ((+a["fill-opacity"] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1) * ((+R.getRGB(params.fill).o + 1 || 2) - 1);
                    opacity = mmin(mmax(opacity, 0), 1);
                    fill.opacity = opacity;
                    if (fill.src) {
                        fill.color = "none";
                    }
                }
                node.appendChild(fill);
                var stroke = (node.getElementsByTagName("stroke") && node.getElementsByTagName("stroke")[0]),
                newstroke = false;
                !stroke && (newstroke = stroke = createNode("stroke"));
                if ((params.stroke && params.stroke != "none") ||
                    params["stroke-width"] ||
                    params["stroke-opacity"] != null ||
                    params["stroke-dasharray"] ||
                    params["stroke-miterlimit"] ||
                    params["stroke-linejoin"] ||
                    params["stroke-linecap"]) {
                    stroke.on = true;
                }
                (params.stroke == "none" || params.stroke === null || stroke.on == null || params.stroke == 0 || params["stroke-width"] == 0) && (stroke.on = false);
                var strokeColor = R.getRGB(params.stroke);
                stroke.on && params.stroke && (stroke.color = strokeColor.hex);
                opacity = ((+a["stroke-opacity"] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1) * ((+strokeColor.o + 1 || 2) - 1);
                var width = (toFloat(params["stroke-width"]) || 1) * .75;
                opacity = mmin(mmax(opacity, 0), 1);
                params["stroke-width"] == null && (width = a["stroke-width"]);
                params["stroke-width"] && (stroke.weight = width);
                width && width < 1 && (opacity *= width) && (stroke.weight = 1);
                stroke.opacity = opacity;
            
                params["stroke-linejoin"] && (stroke.joinstyle = params["stroke-linejoin"] || "miter");
                stroke.miterlimit = params["stroke-miterlimit"] || 8;
                params["stroke-linecap"] && (stroke.endcap = params["stroke-linecap"] == "butt" ? "flat" : params["stroke-linecap"] == "square" ? "square" : "round");
                if (params["stroke-dasharray"]) {
                    var dasharray = {
                        "-": "shortdash",
                        ".": "shortdot",
                        "-.": "shortdashdot",
                        "-..": "shortdashdotdot",
                        ". ": "dot",
                        "- ": "dash",
                        "--": "longdash",
                        "- .": "dashdot",
                        "--.": "longdashdot",
                        "--..": "longdashdotdot"
                    };
                    stroke.dashstyle = dasharray[has](params["stroke-dasharray"]) ? dasharray[params["stroke-dasharray"]] : E;
                }
                newstroke && node.appendChild(stroke);
            }
            if (res.type == "text") {
                res.paper.canvas.style.display = E;
                var span = res.paper.span,
                    m = 100,
                    fontSize = a.font && a.font.match(/\d+(?:\.\d*)?(?=px)/);
                s = span.style;
                a.font && (s.font = a.font);
                a["font-family"] && (s.fontFamily = a["font-family"]);
                a["font-weight"] && (s.fontWeight = a["font-weight"]);
                a["font-style"] && (s.fontStyle = a["font-style"]);
                fontSize = toFloat(a["font-size"] || fontSize && fontSize[0]) || 10;
                s.fontSize = fontSize * m + "px";
                res.textpath.string && (span.innerHTML = Str(res.textpath.string).replace(/</g, "&#60;").replace(/&/g, "&#38;").replace(/\n/g, "<br>"));
                var brect = span.getBoundingClientRect();
                res.W = a.w = (brect.right - brect.left) / m;
                res.H = a.h = (brect.bottom - brect.top) / m;
                // res.paper.canvas.style.display = "none";
                res.X = a.x;
                res.Y = a.y + res.H / 2;
    
                ("x" in params || "y" in params) && (res.path.v = R.format("m{0},{1}l{2},{1}", round(a.x * zoom), round(a.y * zoom), round(a.x * zoom) + 1));
                var dirtyattrs = ["x", "y", "text", "font", "font-family", "font-weight", "font-style", "font-size"];
                for (var d = 0, dd = dirtyattrs.length; d < dd; d++) if (dirtyattrs[d] in params) {
                    res._.dirty = 1;
                    break;
                }
            
                // text-anchor emulation
                switch (a["text-anchor"]) {
                    case "start":
                        res.textpath.style["v-text-align"] = "left";
                        res.bbx = res.W / 2;
                    break;
                    case "end":
                        res.textpath.style["v-text-align"] = "right";
                        res.bbx = -res.W / 2;
                    break;
                    default:
                        res.textpath.style["v-text-align"] = "center";
                        res.bbx = 0;
                    break;
                }
                res.textpath.style["v-text-kern"] = true;
            }
            // res.paper.canvas.style.display = E;
        },
        addGradientFill = function (o, gradient, fill) {
            o.attrs = o.attrs || {};
            var attrs = o.attrs,
                pow = Math.pow,
                opacity,
                oindex,
                type = "linear",
                fxfy = ".5 .5";
            o.attrs.gradient = gradient;
            gradient = Str(gradient).replace(R._radial_gradient, function (all, fx, fy) {
                type = "radial";
                if (fx && fy) {
                    fx = toFloat(fx);
                    fy = toFloat(fy);
                    pow(fx - .5, 2) + pow(fy - .5, 2) > .25 && (fy = math.sqrt(.25 - pow(fx - .5, 2)) * ((fy > .5) * 2 - 1) + .5);
                    fxfy = fx + S + fy;
                }
                return E;
            });
            gradient = gradient.split(/\s*\-\s*/);
            if (type == "linear") {
                var angle = gradient.shift();
                angle = -toFloat(angle);
                if (isNaN(angle)) {
                    return null;
                }
            }
            var dots = R._parseDots(gradient);
            if (!dots) {
                return null;
            }
            o = o.shape || o.node;
            if (dots.length) {
                o.removeChild(fill);
                fill.on = true;
                fill.method = "none";
                fill.color = dots[0].color;
                fill.color2 = dots[dots.length - 1].color;
                var clrs = [];
                for (var i = 0, ii = dots.length; i < ii; i++) {
                    dots[i].offset && clrs.push(dots[i].offset + S + dots[i].color);
                }
                fill.colors = clrs.length ? clrs.join() : "0% " + fill.color;
                if (type == "radial") {
                    fill.type = "gradientTitle";
                    fill.focus = "100%";
                    fill.focussize = "0 0";
                    fill.focusposition = fxfy;
                    fill.angle = 0;
                } else {
                    // fill.rotate= true;
                    fill.type = "gradient";
                    fill.angle = (270 - angle) % 360;
                }
                o.appendChild(fill);
            }
            return 1;
        },
        Element = function (node, vml) {
            this[0] = this.node = node;
            node.raphael = true;
            this.id = R._oid++;
            node.raphaelid = this.id;
            this.X = 0;
            this.Y = 0;
            this.attrs = {};
            this.paper = vml;
            this.matrix = R.matrix();
            this._ = {
                transform: [],
                sx: 1,
                sy: 1,
                dx: 0,
                dy: 0,
                deg: 0,
                dirty: 1,
                dirtyT: 1
            };
            !vml.bottom && (vml.bottom = this);
            this.prev = vml.top;
            vml.top && (vml.top.next = this);
            vml.top = this;
            this.next = null;
        };
        var elproto = R.el;
    
        Element.prototype = elproto;
        elproto.constructor = Element;
        elproto.transform = function (tstr) {
            if (tstr == null) {
                return this._.transform;
            }
            var vbs = this.paper._viewBoxShift,
                vbt = vbs ? "s" + [vbs.scale, vbs.scale] + "-1-1t" + [vbs.dx, vbs.dy] : E,
                oldt;
            if (vbs) {
                oldt = tstr = Str(tstr).replace(/\.{3}|\u2026/g, this._.transform || E);
            }
            R._extractTransform(this, vbt + tstr);
            var matrix = this.matrix.clone(),
                skew = this.skew,
                o = this.node,
                split,
                isGrad = ~Str(this.attrs.fill).indexOf("-"),
                isPatt = !Str(this.attrs.fill).indexOf("url(");
            matrix.translate(1, 1);
            if (isPatt || isGrad || this.type == "image") {
                skew.matrix = "1 0 0 1";
                skew.offset = "0 0";
                split = matrix.split();
                if ((isGrad && split.noRotation) || !split.isSimple) {
                    o.style.filter = matrix.toFilter();
                    var bb = this.getBBox(),
                        bbt = this.getBBox(1),
                        dx = bb.x - bbt.x,
                        dy = bb.y - bbt.y;
                    o.coordorigin = (dx * -zoom) + S + (dy * -zoom);
                    setCoords(this, 1, 1, dx, dy, 0);
                } else {
                    o.style.filter = E;
                    setCoords(this, split.scalex, split.scaley, split.dx, split.dy, split.rotate);
                }
            } else {
                o.style.filter = E;
                skew.matrix = Str(matrix);
                skew.offset = matrix.offset();
            }
            oldt && (this._.transform = oldt);
            return this;
        };
        elproto.rotate = function (deg, cx, cy) {
            if (this.removed) {
                return this;
            }
            if (deg == null) {
                return;
            }
            deg = Str(deg).split(separator);
            if (deg.length - 1) {
                cx = toFloat(deg[1]);
                cy = toFloat(deg[2]);
            }
            deg = toFloat(deg[0]);
            (cy == null) && (cx = cy);
            if (cx == null || cy == null) {
                var bbox = this.getBBox(1);
                cx = bbox.x + bbox.width / 2;
                cy = bbox.y + bbox.height / 2;
            }
            this._.dirtyT = 1;
            this.transform(this._.transform.concat([["r", deg, cx, cy]]));
            return this;
        };
        elproto.translate = function (dx, dy) {
            if (this.removed) {
                return this;
            }
            dx = Str(dx).split(separator);
            if (dx.length - 1) {
                dy = toFloat(dx[1]);
            }
            dx = toFloat(dx[0]) || 0;
            dy = +dy || 0;
            if (this._.bbox) {
                this._.bbox.x += dx;
                this._.bbox.y += dy;
            }
            this.transform(this._.transform.concat([["t", dx, dy]]));
            return this;
        };
        elproto.scale = function (sx, sy, cx, cy) {
            if (this.removed) {
                return this;
            }
            sx = Str(sx).split(separator);
            if (sx.length - 1) {
                sy = toFloat(sx[1]);
                cx = toFloat(sx[2]);
                cy = toFloat(sx[3]);
                isNaN(cx) && (cx = null);
                isNaN(cy) && (cy = null);
            }
            sx = toFloat(sx[0]);
            (sy == null) && (sy = sx);
            (cy == null) && (cx = cy);
            if (cx == null || cy == null) {
                var bbox = this.getBBox(1);
            }
            cx = cx == null ? bbox.x + bbox.width / 2 : cx;
            cy = cy == null ? bbox.y + bbox.height / 2 : cy;
        
            this.transform(this._.transform.concat([["s", sx, sy, cx, cy]]));
            this._.dirtyT = 1;
            return this;
        };
        elproto.hide = function () {
            !this.removed && (this.node.style.display = "none");
            return this;
        };
        elproto.show = function () {
            !this.removed && (this.node.style.display = E);
            return this;
        };
        elproto._getBBox = function () {
            if (this.removed) {
                return {};
            }
            return {
                x: this.X + (this.bbx || 0) - this.W / 2,
                y: this.Y - this.H,
                width: this.W,
                height: this.H
            };
        };
        elproto.remove = function () {
            if (this.removed || !this.node.parentNode) {
                return;
            }
            this.paper.__set__ && this.paper.__set__.exclude(this);
            R.eve.unbind("raphael.*.*." + this.id);
            R._tear(this, this.paper);
            this.node.parentNode.removeChild(this.node);
            this.shape && this.shape.parentNode.removeChild(this.shape);
            for (var i in this) {
                this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
            }
            this.removed = true;
        };
        elproto.attr = function (name, value) {
            if (this.removed) {
                return this;
            }
            if (name == null) {
                var res = {};
                for (var a in this.attrs) if (this.attrs[has](a)) {
                    res[a] = this.attrs[a];
                }
                res.gradient && res.fill == "none" && (res.fill = res.gradient) && delete res.gradient;
                res.transform = this._.transform;
                return res;
            }
            if (value == null && R.is(name, "string")) {
                if (name == fillString && this.attrs.fill == "none" && this.attrs.gradient) {
                    return this.attrs.gradient;
                }
                var names = name.split(separator),
                    out = {};
                for (var i = 0, ii = names.length; i < ii; i++) {
                    name = names[i];
                    if (name in this.attrs) {
                        out[name] = this.attrs[name];
                    } else if (R.is(this.paper.customAttributes[name], "function")) {
                        out[name] = this.paper.customAttributes[name].def;
                    } else {
                        out[name] = R._availableAttrs[name];
                    }
                }
                return ii - 1 ? out : out[names[0]];
            }
            if (this.attrs && value == null && R.is(name, "array")) {
                out = {};
                for (i = 0, ii = name.length; i < ii; i++) {
                    out[name[i]] = this.attr(name[i]);
                }
                return out;
            }
            var params;
            if (value != null) {
                params = {};
                params[name] = value;
            }
            value == null && R.is(name, "object") && (params = name);
            for (var key in params) {
                eve("raphael.attr." + key + "." + this.id, this, params[key]);
            }
            if (params) {
                for (key in this.paper.customAttributes) if (this.paper.customAttributes[has](key) && params[has](key) && R.is(this.paper.customAttributes[key], "function")) {
                    var par = this.paper.customAttributes[key].apply(this, [].concat(params[key]));
                    this.attrs[key] = params[key];
                    for (var subkey in par) if (par[has](subkey)) {
                        params[subkey] = par[subkey];
                    }
                }
                // this.paper.canvas.style.display = "none";
                if (params.text && this.type == "text") {
                    this.textpath.string = params.text;
                }
                setFillAndStroke(this, params);
                // this.paper.canvas.style.display = E;
            }
            return this;
        };
        elproto.toFront = function () {
            !this.removed && this.node.parentNode.appendChild(this.node);
            this.paper && this.paper.top != this && R._tofront(this, this.paper);
            return this;
        };
        elproto.toBack = function () {
            if (this.removed) {
                return this;
            }
            if (this.node.parentNode.firstChild != this.node) {
                this.node.parentNode.insertBefore(this.node, this.node.parentNode.firstChild);
                R._toback(this, this.paper);
            }
            return this;
        };
        elproto.insertAfter = function (element) {
            if (this.removed) {
                return this;
            }
            if (element.constructor == R.st.constructor) {
                element = element[element.length - 1];
            }
            if (element.node.nextSibling) {
                element.node.parentNode.insertBefore(this.node, element.node.nextSibling);
            } else {
                element.node.parentNode.appendChild(this.node);
            }
            R._insertafter(this, element, this.paper);
            return this;
        };
        elproto.insertBefore = function (element) {
            if (this.removed) {
                return this;
            }
            if (element.constructor == R.st.constructor) {
                element = element[0];
            }
            element.node.parentNode.insertBefore(this.node, element.node);
            R._insertbefore(this, element, this.paper);
            return this;
        };
        elproto.blur = function (size) {
            var s = this.node.runtimeStyle,
                f = s.filter;
            f = f.replace(blurregexp, E);
            if (+size !== 0) {
                this.attrs.blur = size;
                s.filter = f + S + ms + ".Blur(pixelradius=" + (+size || 1.5) + ")";
                s.margin = R.format("-{0}px 0 0 -{0}px", round(+size || 1.5));
            } else {
                s.filter = f;
                s.margin = 0;
                delete this.attrs.blur;
            }
            return this;
        };
    
        R._engine.path = function (pathString, vml) {
            var el = createNode("shape");
            el.style.cssText = cssDot;
            el.coordsize = zoom + S + zoom;
            el.coordorigin = vml.coordorigin;
            var p = new Element(el, vml),
                attr = {fill: "none", stroke: "#000"};
            pathString && (attr.path = pathString);
            p.type = "path";
            p.path = [];
            p.Path = E;
            setFillAndStroke(p, attr);
            vml.canvas.appendChild(el);
            var skew = createNode("skew");
            skew.on = true;
            el.appendChild(skew);
            p.skew = skew;
            p.transform(E);
            return p;
        };
        R._engine.rect = function (vml, x, y, w, h, r) {
            var path = R._rectPath(x, y, w, h, r),
                res = vml.path(path),
                a = res.attrs;
            res.X = a.x = x;
            res.Y = a.y = y;
            res.W = a.width = w;
            res.H = a.height = h;
            a.r = r;
            a.path = path;
            res.type = "rect";
            return res;
        };
        R._engine.ellipse = function (vml, x, y, rx, ry) {
            var res = vml.path(),
                a = res.attrs;
            res.X = x - rx;
            res.Y = y - ry;
            res.W = rx * 2;
            res.H = ry * 2;
            res.type = "ellipse";
            setFillAndStroke(res, {
                cx: x,
                cy: y,
                rx: rx,
                ry: ry
            });
            return res;
        };
        R._engine.circle = function (vml, x, y, r) {
            var res = vml.path(),
                a = res.attrs;
            res.X = x - r;
            res.Y = y - r;
            res.W = res.H = r * 2;
            res.type = "circle";
            setFillAndStroke(res, {
                cx: x,
                cy: y,
                r: r
            });
            return res;
        };
        R._engine.image = function (vml, src, x, y, w, h) {
            var path = R._rectPath(x, y, w, h),
                res = vml.path(path).attr({stroke: "none"}),
                a = res.attrs,
                node = res.node,
                fill = node.getElementsByTagName(fillString)[0];
            a.src = src;
            res.X = a.x = x;
            res.Y = a.y = y;
            res.W = a.width = w;
            res.H = a.height = h;
            a.path = path;
            res.type = "image";
            fill.parentNode == node && node.removeChild(fill);
            fill.rotate = true;
            fill.src = src;
            fill.type = "tile";
            res._.fillpos = [x, y];
            res._.fillsize = [w, h];
            node.appendChild(fill);
            setCoords(res, 1, 1, 0, 0, 0);
            return res;
        };
        R._engine.text = function (vml, x, y, text) {
            var el = createNode("shape"),
                path = createNode("path"),
                o = createNode("textpath");
            x = x || 0;
            y = y || 0;
            text = text || "";
            path.v = R.format("m{0},{1}l{2},{1}", round(x * zoom), round(y * zoom), round(x * zoom) + 1);
            path.textpathok = true;
            o.string = Str(text);
            o.on = true;
            el.style.cssText = cssDot;
            el.coordsize = zoom + S + zoom;
            el.coordorigin = "0 0";
            var p = new Element(el, vml),
                attr = {
                    fill: "#000",
                    stroke: "none",
                    font: R._availableAttrs.font,
                    text: text
                };
            p.shape = el;
            p.path = path;
            p.textpath = o;
            p.type = "text";
            p.attrs.text = Str(text);
            p.attrs.x = x;
            p.attrs.y = y;
            p.attrs.w = 1;
            p.attrs.h = 1;
            setFillAndStroke(p, attr);
            el.appendChild(o);
            el.appendChild(path);
            vml.canvas.appendChild(el);
            var skew = createNode("skew");
            skew.on = true;
            el.appendChild(skew);
            p.skew = skew;
            p.transform(E);
            return p;
        };
        R._engine.setSize = function (width, height) {
            var cs = this.canvas.style;
            this.width = width;
            this.height = height;
            width == +width && (width += "px");
            height == +height && (height += "px");
            cs.width = width;
            cs.height = height;
            cs.clip = "rect(0 " + width + " " + height + " 0)";
            if (this._viewBox) {
                R._engine.setViewBox.apply(this, this._viewBox);
            }
            return this;
        };
        R._engine.setViewBox = function (x, y, w, h, fit) {
            R.eve("raphael.setViewBox", this, this._viewBox, [x, y, w, h, fit]);
            var width = this.width,
                height = this.height,
                size = 1 / mmax(w / width, h / height),
                H, W;
            if (fit) {
                H = height / h;
                W = width / w;
                if (w * H < width) {
                    x -= (width - w * H) / 2 / H;
                }
                if (h * W < height) {
                    y -= (height - h * W) / 2 / W;
                }
            }
            this._viewBox = [x, y, w, h, !!fit];
            this._viewBoxShift = {
                dx: -x,
                dy: -y,
                scale: size
            };
            this.forEach(function (el) {
                el.transform("...");
            });
            return this;
        };
        var createNode;
        R._engine.initWin = function (win) {
                var doc = win.document;
                doc.createStyleSheet().addRule(".rvml", "behavior:url(#default#VML)");
                try {
                    !doc.namespaces.rvml && doc.namespaces.add("rvml", "urn:schemas-microsoft-com:vml");
                    createNode = function (tagName) {
                        return doc.createElement('<rvml:' + tagName + ' class="rvml">');
                    };
                } catch (e) {
                    createNode = function (tagName) {
                        return doc.createElement('<' + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');
                    };
                }
            };
        R._engine.initWin(R._g.win);
        R._engine.create = function () {
            var con = R._getContainer.apply(0, arguments),
                container = con.container,
                height = con.height,
                s,
                width = con.width,
                x = con.x,
                y = con.y;
            if (!container) {
                throw new Error("VML container not found.");
            }
            var res = new R._Paper,
                c = res.canvas = R._g.doc.createElement("div"),
                cs = c.style;
            x = x || 0;
            y = y || 0;
            width = width || 512;
            height = height || 342;
            res.width = width;
            res.height = height;
            width == +width && (width += "px");
            height == +height && (height += "px");
            res.coordsize = zoom * 1e3 + S + zoom * 1e3;
            res.coordorigin = "0 0";
            res.span = R._g.doc.createElement("span");
            res.span.style.cssText = "position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;";
            c.appendChild(res.span);
            cs.cssText = R.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden", width, height);
            if (container == 1) {
                R._g.doc.body.appendChild(c);
                cs.left = x + "px";
                cs.top = y + "px";
                cs.position = "absolute";
            } else {
                if (container.firstChild) {
                    container.insertBefore(c, container.firstChild);
                } else {
                    container.appendChild(c);
                }
            }
            res.renderfix = function () {};
            return res;
        };
        R.prototype.clear = function () {
            R.eve("raphael.clear", this);
            this.canvas.innerHTML = E;
            this.span = R._g.doc.createElement("span");
            this.span.style.cssText = "position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";
            this.canvas.appendChild(this.span);
            this.bottom = this.top = null;
        };
        R.prototype.remove = function () {
            R.eve("raphael.remove", this);
            this.canvas.parentNode.removeChild(this.canvas);
            for (var i in this) {
                this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
            }
            return true;
        };
    
        var setproto = R.st;
        for (var method in elproto) if (elproto[has](method) && !setproto[has](method)) {
            setproto[method] = (function (methodname) {
                return function () {
                    var arg = arguments;
                    return this.forEach(function (el) {
                        el[methodname].apply(el, arg);
                    });
                };
            })(method);
        }
    })();
    
        // EXPOSE
        // SVG and VML are appended just before the EXPOSE line
        // Even with AMD, Raphael should be defined globally
        oldRaphael.was ? (g.win.Raphael = R) : (Raphael = R);
    
        return R;
    }));
    
    /** js sequence diagrams
     *  http://bramp.github.io/js-sequence-diagrams/
     *  (c) 2012-2013 Andrew Brampton (bramp.net)
     *  Simplified BSD license.
     */
    (function () {
    	
    	/*global grammar _ */
    
    	function Diagram() {
    		this.title   = undefined;
    		this.actors  = [];
    		this.signals = [];
    	}
    
    	Diagram.prototype.getActor = function(alias) {
    		var s = /^(.+) as (\S+)$/i.exec(alias.trim());
    		if (s) {
    			name  = s[1].trim();
    			alias = s[2].trim();
    		} else {
    			name = alias.trim();
    		}
    
    		name = name.replace(/\\n/gm, "\n");
    
    		var i, actors = this.actors;
    		for (i in actors) {
    			if (actors[i].alias == alias)
    				return actors[i];
    		}
    		i = actors.push( new Diagram.Actor(alias, name, actors.length) );
    		return actors[ i - 1 ];
    	};
    
    	Diagram.prototype.setTitle = function(title) {
    		this.title = title;
    	};
    
    	Diagram.prototype.addSignal = function(signal) {
    		this.signals.push( signal );
    	};
    
    	Diagram.Actor = function(alias, name, index) {
    		this.alias = alias;
    		this.name  = name;
    		this.index = index;
    	};
    
    	Diagram.Signal = function(actorA, signaltype, actorB, message) {
    		this.type       = "Signal";
    		this.actorA     = actorA;
    		this.actorB     = actorB;
    		this.linetype   = signaltype & 3;
    		this.arrowtype  = (signaltype >> 2) & 3;
    		this.message    = message;
    	};
    
    	Diagram.Signal.prototype.isSelf = function() {
    		return this.actorA.index == this.actorB.index;
    	};
    
    	Diagram.Note = function(actor, placement, message) {
    		this.type      = "Note";
    		this.actor     = actor;
    		this.placement = placement;
    		this.message   = message;
    
    		if (this.hasManyActors() && actor[0] == actor[1]) {
    			throw new Error("Note should be over two different actors");
    		}
    	};
    
    	Diagram.Note.prototype.hasManyActors = function() {
    		return _.isArray(this.actor);
    	};
    
    	Diagram.LINETYPE = {
    		SOLID  : 0,
    		DOTTED : 1
    	};
    
    	Diagram.ARROWTYPE = {
    		FILLED  : 0,
    		OPEN    : 1
    	};
    
    	Diagram.PLACEMENT = {
    		LEFTOF  : 0,
    		RIGHTOF : 1,
    		OVER    : 2
    	};
    
    	/** The following is included by jspp */
    	/* parser generated by jison 0.4.6 */
    /*
      Returns a Parser object of the following structure:
    
      Parser: {
        yy: {}
      }
    
      Parser.prototype: {
        yy: {},
        trace: function(),
        symbols_: {associative list: name ==> number},
        terminals_: {associative list: number ==> name},
        productions_: [...],
        performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$),
        table: [...],
        defaultActions: {...},
        parseError: function(str, hash),
        parse: function(input),
    
        lexer: {
            EOF: 1,
            parseError: function(str, hash),
            setInput: function(input),
            input: function(),
            unput: function(str),
            more: function(),
            less: function(n),
            pastInput: function(),
            upcomingInput: function(),
            showPosition: function(),
            test_match: function(regex_match_array, rule_index),
            next: function(),
            lex: function(),
            begin: function(condition),
            popState: function(),
            _currentRules: function(),
            topState: function(),
            pushState: function(condition),
    
            options: {
                ranges: boolean           (optional: true ==> token location info will include a .range[] member)
                flex: boolean             (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match)
                backtrack_lexer: boolean  (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code)
            },
    
            performAction: function(yy, yy_, $avoiding_name_collisions, YY_START),
            rules: [...],
            conditions: {associative list: name ==> set},
        }
      }
    
    
      token location info (@$, _$, etc.): {
        first_line: n,
        last_line: n,
        first_column: n,
        last_column: n,
        range: [start_number, end_number]       (where the numbers are indexes into the input string, regular zero-based)
      }
    
    
      the parseError function receives a 'hash' object with these members for lexer and parser errors: {
        text:        (matched text)
        token:       (the produced terminal token, if any)
        line:        (yylineno)
      }
      while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: {
        loc:         (yylloc)
        expected:    (string describing the set of expected tokens)
        recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)
      }
    */
    var grammar = (function(){
    var parser = {trace: function trace() { },
    yy: {},
    symbols_: {"error":2,"start":3,"document":4,"EOF":5,"line":6,"statement":7,"NL":8,"participant":9,"actor":10,"signal":11,"note_statement":12,"title":13,"message":14,"note":15,"placement":16,"over":17,"actor_pair":18,",":19,"left_of":20,"right_of":21,"signaltype":22,"ACTOR":23,"linetype":24,"arrowtype":25,"LINE":26,"DOTLINE":27,"ARROW":28,"OPENARROW":29,"MESSAGE":30,"$accept":0,"$end":1},
    terminals_: {2:"error",5:"EOF",8:"NL",9:"participant",13:"title",15:"note",17:"over",19:",",20:"left_of",21:"right_of",23:"ACTOR",26:"LINE",27:"DOTLINE",28:"ARROW",29:"OPENARROW",30:"MESSAGE"},
    productions_: [0,[3,2],[4,0],[4,2],[6,1],[6,1],[7,2],[7,1],[7,1],[7,2],[12,4],[12,4],[18,1],[18,3],[16,1],[16,1],[11,4],[10,1],[22,2],[22,1],[24,1],[24,1],[25,1],[25,1],[14,1]],
    performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
    /* this == yyval */
    
    var $0 = $$.length - 1;
    switch (yystate) {
    case 1: return yy; 
    break;
    case 4: 
    break;
    case 6: $$[$0]; 
    break;
    case 7: yy.addSignal($$[$0]); 
    break;
    case 8: yy.addSignal($$[$0]); 
    break;
    case 9: yy.setTitle($$[$0]);  
    break;
    case 10: this.$ = new Diagram.Note($$[$0-1], $$[$0-2], $$[$0]); 
    break;
    case 11: this.$ = new Diagram.Note($$[$0-1], Diagram.PLACEMENT.OVER, $$[$0]); 
    break;
    case 12: this.$ = $$[$0]; 
    break;
    case 13: this.$ = [$$[$0-2], $$[$0]]; 
    break;
    case 14: this.$ = Diagram.PLACEMENT.LEFTOF; 
    break;
    case 15: this.$ = Diagram.PLACEMENT.RIGHTOF; 
    break;
    case 16: this.$ = new Diagram.Signal($$[$0-3], $$[$0-2], $$[$0-1], $$[$0]); 
    break;
    case 17: this.$ = yy.getActor($$[$0]); 
    break;
    case 18: this.$ = $$[$0-1] | ($$[$0] << 2); 
    break;
    case 19: this.$ = $$[$0]; 
    break;
    case 20: this.$ = Diagram.LINETYPE.SOLID; 
    break;
    case 21: this.$ = Diagram.LINETYPE.DOTTED; 
    break;
    case 22: this.$ = Diagram.ARROWTYPE.FILLED; 
    break;
    case 23: this.$ = Diagram.ARROWTYPE.OPEN; 
    break;
    case 24: this.$ = $$[$0].substring(1).trim().replace(/\\n/gm, "\n"); 
    break;
    }
    },
    table: [{3:1,4:2,5:[2,2],8:[2,2],9:[2,2],13:[2,2],15:[2,2],23:[2,2]},{1:[3]},{5:[1,3],6:4,7:5,8:[1,6],9:[1,7],10:11,11:8,12:9,13:[1,10],15:[1,12],23:[1,13]},{1:[2,1]},{5:[2,3],8:[2,3],9:[2,3],13:[2,3],15:[2,3],23:[2,3]},{5:[2,4],8:[2,4],9:[2,4],13:[2,4],15:[2,4],23:[2,4]},{5:[2,5],8:[2,5],9:[2,5],13:[2,5],15:[2,5],23:[2,5]},{10:14,23:[1,13]},{5:[2,7],8:[2,7],9:[2,7],13:[2,7],15:[2,7],23:[2,7]},{5:[2,8],8:[2,8],9:[2,8],13:[2,8],15:[2,8],23:[2,8]},{14:15,30:[1,16]},{22:17,24:18,26:[1,19],27:[1,20]},{16:21,17:[1,22],20:[1,23],21:[1,24]},{5:[2,17],8:[2,17],9:[2,17],13:[2,17],15:[2,17],19:[2,17],23:[2,17],26:[2,17],27:[2,17],30:[2,17]},{5:[2,6],8:[2,6],9:[2,6],13:[2,6],15:[2,6],23:[2,6]},{5:[2,9],8:[2,9],9:[2,9],13:[2,9],15:[2,9],23:[2,9]},{5:[2,24],8:[2,24],9:[2,24],13:[2,24],15:[2,24],23:[2,24]},{10:25,23:[1,13]},{23:[2,19],25:26,28:[1,27],29:[1,28]},{23:[2,20],28:[2,20],29:[2,20]},{23:[2,21],28:[2,21],29:[2,21]},{10:29,23:[1,13]},{10:31,18:30,23:[1,13]},{23:[2,14]},{23:[2,15]},{14:32,30:[1,16]},{23:[2,18]},{23:[2,22]},{23:[2,23]},{14:33,30:[1,16]},{14:34,30:[1,16]},{19:[1,35],30:[2,12]},{5:[2,16],8:[2,16],9:[2,16],13:[2,16],15:[2,16],23:[2,16]},{5:[2,10],8:[2,10],9:[2,10],13:[2,10],15:[2,10],23:[2,10]},{5:[2,11],8:[2,11],9:[2,11],13:[2,11],15:[2,11],23:[2,11]},{10:36,23:[1,13]},{30:[2,13]}],
    defaultActions: {3:[2,1],23:[2,14],24:[2,15],26:[2,18],27:[2,22],28:[2,23],36:[2,13]},
    parseError: function parseError(str, hash) {
        if (hash.recoverable) {
            this.trace(str);
        } else {
            throw new Error(str);
        }
    },
    parse: function parse(input) {
        var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
        this.lexer.setInput(input);
        this.lexer.yy = this.yy;
        this.yy.lexer = this.lexer;
        this.yy.parser = this;
        if (typeof this.lexer.yylloc == 'undefined') {
            this.lexer.yylloc = {};
        }
        var yyloc = this.lexer.yylloc;
        lstack.push(yyloc);
        var ranges = this.lexer.options && this.lexer.options.ranges;
        if (typeof this.yy.parseError === 'function') {
            this.parseError = this.yy.parseError;
        } else {
            this.parseError = Object.getPrototypeOf(this).parseError;
        }
        function popStack(n) {
            stack.length = stack.length - 2 * n;
            vstack.length = vstack.length - n;
            lstack.length = lstack.length - n;
        }
        function lex() {
            var token;
            token = self.lexer.lex() || EOF;
            if (typeof token !== 'number') {
                token = self.symbols_[token] || token;
            }
            return token;
        }
        var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
        while (true) {
            state = stack[stack.length - 1];
            if (this.defaultActions[state]) {
                action = this.defaultActions[state];
            } else {
                if (symbol === null || typeof symbol == 'undefined') {
                    symbol = lex();
                }
                action = table[state] && table[state][symbol];
            }
                        if (typeof action === 'undefined' || !action.length || !action[0]) {
                    var errStr = '';
                    expected = [];
                    for (p in table[state]) {
                        if (this.terminals_[p] && p > TERROR) {
                            expected.push('\'' + this.terminals_[p] + '\'');
                        }
                    }
                    if (this.lexer.showPosition) {
                        errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + this.lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\'';
                    } else {
                        errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\'');
                    }
                    this.parseError(errStr, {
                        text: this.lexer.match,
                        token: this.terminals_[symbol] || symbol,
                        line: this.lexer.yylineno,
                        loc: yyloc,
                        expected: expected
                    });
                }
            if (action[0] instanceof Array && action.length > 1) {
                throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol);
            }
            switch (action[0]) {
            case 1:
                stack.push(symbol);
                vstack.push(this.lexer.yytext);
                lstack.push(this.lexer.yylloc);
                stack.push(action[1]);
                symbol = null;
                if (!preErrorSymbol) {
                    yyleng = this.lexer.yyleng;
                    yytext = this.lexer.yytext;
                    yylineno = this.lexer.yylineno;
                    yyloc = this.lexer.yylloc;
                    if (recovering > 0) {
                        recovering--;
                    }
                } else {
                    symbol = preErrorSymbol;
                    preErrorSymbol = null;
                }
                break;
            case 2:
                len = this.productions_[action[1]][1];
                yyval.$ = vstack[vstack.length - len];
                yyval._$ = {
                    first_line: lstack[lstack.length - (len || 1)].first_line,
                    last_line: lstack[lstack.length - 1].last_line,
                    first_column: lstack[lstack.length - (len || 1)].first_column,
                    last_column: lstack[lstack.length - 1].last_column
                };
                if (ranges) {
                    yyval._$.range = [
                        lstack[lstack.length - (len || 1)].range[0],
                        lstack[lstack.length - 1].range[1]
                    ];
                }
                r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
                if (typeof r !== 'undefined') {
                    return r;
                }
                if (len) {
                    stack = stack.slice(0, -1 * len * 2);
                    vstack = vstack.slice(0, -1 * len);
                    lstack = lstack.slice(0, -1 * len);
                }
                stack.push(this.productions_[action[1]][0]);
                vstack.push(yyval.$);
                lstack.push(yyval._$);
                newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
                stack.push(newState);
                break;
            case 3:
                return true;
            }
        }
        return true;
    }};
    
    /* generated by jison-lex 0.2.1 */
    var lexer = (function(){
    var lexer = {
    
    EOF:1,
    
    parseError:function parseError(str, hash) {
            if (this.yy.parser) {
                this.yy.parser.parseError(str, hash);
            } else {
                throw new Error(str);
            }
        },
    
    // resets the lexer, sets new input
    setInput:function (input) {
            this._input = input;
            this._more = this._backtrack = this.done = false;
            this.yylineno = this.yyleng = 0;
            this.yytext = this.matched = this.match = '';
            this.conditionStack = ['INITIAL'];
            this.yylloc = {
                first_line: 1,
                first_column: 0,
                last_line: 1,
                last_column: 0
            };
            if (this.options.ranges) {
                this.yylloc.range = [0,0];
            }
            this.offset = 0;
            return this;
        },
    
    // consumes and returns one char from the input
    input:function () {
            var ch = this._input[0];
            this.yytext += ch;
            this.yyleng++;
            this.offset++;
            this.match += ch;
            this.matched += ch;
            var lines = ch.match(/(?:\r\n?|\n).*/g);
            if (lines) {
                this.yylineno++;
                this.yylloc.last_line++;
            } else {
                this.yylloc.last_column++;
            }
            if (this.options.ranges) {
                this.yylloc.range[1]++;
            }
    
            this._input = this._input.slice(1);
            return ch;
        },
    
    // unshifts one char (or a string) into the input
    unput:function (ch) {
            var len = ch.length;
            var lines = ch.split(/(?:\r\n?|\n)/g);
    
            this._input = ch + this._input;
            this.yytext = this.yytext.substr(0, this.yytext.length - len - 1);
            //this.yyleng -= len;
            this.offset -= len;
            var oldLines = this.match.split(/(?:\r\n?|\n)/g);
            this.match = this.match.substr(0, this.match.length - 1);
            this.matched = this.matched.substr(0, this.matched.length - 1);
    
            if (lines.length - 1) {
                this.yylineno -= lines.length - 1;
            }
            var r = this.yylloc.range;
    
            this.yylloc = {
                first_line: this.yylloc.first_line,
                last_line: this.yylineno + 1,
                first_column: this.yylloc.first_column,
                last_column: lines ?
                    (lines.length === oldLines.length ? this.yylloc.first_column : 0)
                     + oldLines[oldLines.length - lines.length].length - lines[0].length :
                  this.yylloc.first_column - len
            };
    
            if (this.options.ranges) {
                this.yylloc.range = [r[0], r[0] + this.yyleng - len];
            }
            this.yyleng = this.yytext.length;
            return this;
        },
    
    // When called from action, caches matched text and appends it on next action
    more:function () {
            this._more = true;
            return this;
        },
    
    // When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
    reject:function () {
            if (this.options.backtrack_lexer) {
                this._backtrack = true;
            } else {
                return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), {
                    text: "",
                    token: null,
                    line: this.yylineno
                });
    
            }
            return this;
        },
    
    // retain first n characters of the match
    less:function (n) {
            this.unput(this.match.slice(n));
        },
    
    // displays already matched input, i.e. for error messages
    pastInput:function () {
            var past = this.matched.substr(0, this.matched.length - this.match.length);
            return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
        },
    
    // displays upcoming input, i.e. for error messages
    upcomingInput:function () {
            var next = this.match;
            if (next.length < 20) {
                next += this._input.substr(0, 20-next.length);
            }
            return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
        },
    
    // displays the character position where the lexing error occurred, i.e. for error messages
    showPosition:function () {
            var pre = this.pastInput();
            var c = new Array(pre.length + 1).join("-");
            return pre + this.upcomingInput() + "\n" + c + "^";
        },
    
    // test the lexed token: return FALSE when not a match, otherwise return token
    test_match:function (match, indexed_rule) {
            var token,
                lines,
                backup;
    
            if (this.options.backtrack_lexer) {
                // save context
                backup = {
                    yylineno: this.yylineno,
                    yylloc: {
                        first_line: this.yylloc.first_line,
                        last_line: this.last_line,
                        first_column: this.yylloc.first_column,
                        last_column: this.yylloc.last_column
                    },
                    yytext: this.yytext,
                    match: this.match,
                    matches: this.matches,
                    matched: this.matched,
                    yyleng: this.yyleng,
                    offset: this.offset,
                    _more: this._more,
                    _input: this._input,
                    yy: this.yy,
                    conditionStack: this.conditionStack.slice(0),
                    done: this.done
                };
                if (this.options.ranges) {
                    backup.yylloc.range = this.yylloc.range.slice(0);
                }
            }
    
            lines = match[0].match(/(?:\r\n?|\n).*/g);
            if (lines) {
                this.yylineno += lines.length;
            }
            this.yylloc = {
                first_line: this.yylloc.last_line,
                last_line: this.yylineno + 1,
                first_column: this.yylloc.last_column,
                last_column: lines ?
                             lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length :
                             this.yylloc.last_column + match[0].length
            };
            this.yytext += match[0];
            this.match += match[0];
            this.matches = match;
            this.yyleng = this.yytext.length;
            if (this.options.ranges) {
                this.yylloc.range = [this.offset, this.offset += this.yyleng];
            }
            this._more = false;
            this._backtrack = false;
            this._input = this._input.slice(match[0].length);
            this.matched += match[0];
            token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]);
            if (this.done && this._input) {
                this.done = false;
            }
            if (token) {
                return token;
            } else if (this._backtrack) {
                // recover context
                for (var k in backup) {
                    this[k] = backup[k];
                }
                return false; // rule action called reject() implying the next rule should be tested instead.
            }
            return false;
        },
    
    // return next match in input
    next:function () {
            if (this.done) {
                return this.EOF;
            }
            if (!this._input) {
                this.done = true;
            }
    
            var token,
                match,
                tempMatch,
                index;
            if (!this._more) {
                this.yytext = '';
                this.match = '';
            }
            var rules = this._currentRules();
            for (var i = 0; i < rules.length; i++) {
                tempMatch = this._input.match(this.rules[rules[i]]);
                if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
                    match = tempMatch;
                    index = i;
                    if (this.options.backtrack_lexer) {
                        token = this.test_match(tempMatch, rules[i]);
                        if (token !== false) {
                            return token;
                        } else if (this._backtrack) {
                            match = false;
                            continue; // rule action called reject() implying a rule MISmatch.
                        } else {
                            // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
                            return false;
                        }
                    } else if (!this.options.flex) {
                        break;
                    }
                }
            }
            if (match) {
                token = this.test_match(match, rules[index]);
                if (token !== false) {
                    return token;
                }
                // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
                return false;
            }
            if (this._input === "") {
                return this.EOF;
            } else {
                return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), {
                    text: "",
                    token: null,
                    line: this.yylineno
                });
            }
        },
    
    // return next match that has a token
    lex:function lex() {
            var r = this.next();
            if (r) {
                return r;
            } else {
                return this.lex();
            }
        },
    
    // activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
    begin:function begin(condition) {
            this.conditionStack.push(condition);
        },
    
    // pop the previously active lexer condition state off the condition stack
    popState:function popState() {
            var n = this.conditionStack.length - 1;
            if (n > 0) {
                return this.conditionStack.pop();
            } else {
                return this.conditionStack[0];
            }
        },
    
    // produce the lexer rule set which is active for the currently active lexer condition state
    _currentRules:function _currentRules() {
            if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
                return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
            } else {
                return this.conditions["INITIAL"].rules;
            }
        },
    
    // return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
    topState:function topState(n) {
            n = this.conditionStack.length - 1 - Math.abs(n || 0);
            if (n >= 0) {
                return this.conditionStack[n];
            } else {
                return "INITIAL";
            }
        },
    
    // alias for begin(condition)
    pushState:function pushState(condition) {
            this.begin(condition);
        },
    
    // return the number of states currently on the stack
    stateStackSize:function stateStackSize() {
            return this.conditionStack.length;
        },
    options: {"case-insensitive":true},
    performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
    
    	// Pre-lexer code can go here
    
    var YYSTATE=YY_START;
    switch($avoiding_name_collisions) {
    case 0:return 8;
    break;
    case 1:/* skip whitespace */
    break;
    case 2:/* skip comments */
    break;
    case 3:return 9;
    break;
    case 4:return 20;
    break;
    case 5:return 21;
    break;
    case 6:return 17;
    break;
    case 7:return 15;
    break;
    case 8:return 13;
    break;
    case 9:return 19;
    break;
    case 10:return 23;
    break;
    case 11:return 27;
    break;
    case 12:return 26;
    break;
    case 13:return 29;
    break;
    case 14:return 28;
    break;
    case 15:return 30;
    break;
    case 16:return 5;
    break;
    case 17:return 'INVALID';
    break;
    }
    },
    rules: [/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:participant\b)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:over\b)/i,/^(?:note\b)/i,/^(?:title\b)/i,/^(?:,)/i,/^(?:[^\->:\n,]+)/i,/^(?:--)/i,/^(?:-)/i,/^(?:>>)/i,/^(?:>)/i,/^(?:[^#\n]+)/i,/^(?:$)/i,/^(?:.)/i],
    conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],"inclusive":true}}
    };
    return lexer;
    })();
    parser.lexer = lexer;
    function Parser () {
      this.yy = {};
    }
    Parser.prototype = parser;parser.Parser = Parser;
    return new Parser;
    })();
    
    
    if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
    exports.parser = grammar;
    exports.Parser = grammar.Parser;
    exports.parse = function () { return grammar.parse.apply(grammar, arguments); };
    exports.main = function commonjsMain(args) {
        if (!args[1]) {
            console.log('Usage: '+args[0]+' FILE');
            process.exit(1);
        }
        var source = require('fs').readFileSync(require('path').normalize(args[1]), "utf8");
        return exports.parser.parse(source);
    };
    if (typeof module !== 'undefined' && require.main === module) {
      exports.main(process.argv.slice(1));
    }
    }
    
    	/**
    	 * jison doesn't have a good exception, so we make one
    	 */
    	function ParseError(message, hash) {
    		_.extend(this, hash);
    
    		this.name = "ParseError";
    		this.message = (message || "");
    	}
    	ParseError.prototype = new Error();
    	Diagram.ParseError = ParseError;
    
    	grammar.parseError = function(message, hash) {
    		throw new ParseError(message, hash);
    	};
    
    	Diagram.parse = function(input) {
    		grammar.yy = new Diagram();
    
    		return grammar.parse(input);
    	};
    
    	// Expose this class externally
    	this.Diagram = Diagram;
    
    }).call(this);
    define("diagram-grammar", function(){});
    
    /** js sequence diagrams
     *  http://bramp.github.io/js-sequence-diagrams/
     *  (c) 2012-2013 Andrew Brampton (bramp.net)
     *  Simplified BSD license.
     */
    (function(init) {
    	/*global Diagram, Raphael, _ */
    	if(typeof define === "function" && define.amd) {
    		define("Diagram", ['raphael'], function(Raphael) {
    			init(Raphael);
    			return Diagram;
    		});
    	}
    	else {
    		init(Raphael);
    	}
    })(function(Raphael) {
    	
    	/*global Diagram, Raphael, _ */
    
    	// Following the CSS convention
    	// Margin is the gap outside the box
    	// Padding is the gap inside the box
    	// Each object has x/y/width/height properties
    	// The x/y should be top left corner
    	// width/height is with both margin and padding
    
    	// TODO
    	// Image width is wrong, when there is a note in the right hand col
    	// Title box could look better
    	// Note box could look better
    
    	var DIAGRAM_MARGIN = 10;
    
    	var ACTOR_MARGIN   = 10; // Margin around a actor
    	var ACTOR_PADDING  = 10; // Padding inside a actor
    
    	var SIGNAL_MARGIN  = 5; // Margin around a signal
    	var SIGNAL_PADDING = 5; // Padding inside a signal
    
    	var NOTE_MARGIN   = 10; // Margin around a note
    	var NOTE_PADDING  = 5; // Padding inside a note
    	var NOTE_OVERLAP  = 15; // Overlap when using a "note over A,B"
    
    	var TITLE_MARGIN   = 0;
    	var TITLE_PADDING  = 5;
    
    	var SELF_SIGNAL_WIDTH = 20; // How far out a self signal goes
    
    	var PLACEMENT = Diagram.PLACEMENT;
    	var LINETYPE  = Diagram.LINETYPE;
    	var ARROWTYPE = Diagram.ARROWTYPE;
    
    	var LINE = {
    		'stroke': '#000',
    		'stroke-width': 2
    	};
    
    	var RECT = {
    		'fill': "#fff"
    	};
    
    	function AssertException(message) { this.message = message; }
    	AssertException.prototype.toString = function () {
    		return 'AssertException: ' + this.message;
    	};
    
    	function assert(exp, message) {
    		if (!exp) {
    			throw new AssertException(message);
    		}
    	}
    
    	if (!String.prototype.trim) {
    		String.prototype.trim=function() {
    			return this.replace(/^\s+|\s+$/g, '');
    		};
    	}
    
    /******************
     * Drawing extras
     ******************/
    
    	function getCenterX(box) {
    		return box.x + box.width / 2;
    	}
    
    	function getCenterY(box) {
    		return box.y + box.height / 2;
    	}
    
    /******************
     * Raphaël extras
     ******************/
    
    	Raphael.fn.line = function(x1, y1, x2, y2) {
    		assert(_.all([x1,x2,y1,y2], _.isFinite), "x1,x2,y1,y2 must be numeric");
    		return this.path("M{0},{1} L{2},{3}", x1, y1, x2, y2);
    	};
    
    	Raphael.fn.wobble = function(x1, y1, x2, y2) {
    		assert(_.all([x1,x2,y1,y2], _.isFinite), "x1,x2,y1,y2 must be numeric");
    
    		var wobble = Math.sqrt( (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) / 25;
    
    		// Distance along line
    		var r1 = Math.random();
    		var r2 = Math.random();
    
    		var xfactor = Math.random() > 0.5 ? wobble : -wobble;
    		var yfactor = Math.random() > 0.5 ? wobble : -wobble;
    
    		var p1 = {
    			x: (x2 - x1) * r1 + x1 + xfactor,
    			y: (y2 - y1) * r1 + y1 + yfactor
    		};
    
    		var p2 = {
    			x: (x2 - x1) * r2 + x1 - xfactor,
    			y: (y2 - y1) * r2 + y1 - yfactor
    		};
    
    		return "C" + p1.x + "," + p1.y +
    			" " + p2.x + "," + p2.y +
    			" " + x2 + "," + y2;
    	};
    
    	/**
    	 * Returns the text's bounding box
    	 */
    	Raphael.fn.text_bbox = function (text, font) {
    		var p;
    		if (font._obj) {
    			p = this.print_center(0, 0, text, font._obj, font['font-size']);
    		} else {
    			p = this.text(0, 0, text);
    			p.attr(font);
    		}
    
    		var bb = p.getBBox();
    		p.remove();
    
    		return bb;
    	};
    
    	/**
    	 * Draws a wobbly (hand drawn) rect
    	 */
    	Raphael.fn.handRect = function (x, y, w, h) {
    		assert(_.all([x, y, w, h], _.isFinite), "x, y, w, h must be numeric");
    		return this.path("M" + x + "," + y +
    			this.wobble(x, y, x + w, y) +
    			this.wobble(x + w, y, x + w, y + h) +
    			this.wobble(x + w, y + h, x, y + h) +
    			this.wobble(x, y + h, x, y))
    			.attr(RECT);
    	};
    
    	/**
    	 * Draws a wobbly (hand drawn) line
    	 */
    	Raphael.fn.handLine = function (x1, y1, x2, y2) {
    		assert(_.all([x1,x2,y1,y2], _.isFinite), "x1,x2,y1,y2 must be numeric");
    		return this.path("M" + x1 + "," + y1 + this.wobble(x1, y1, x2, y2));
    	};
    
    	/**
    	 * Prints, but aligns text in a similar way to text(...)
    	 */
    	Raphael.fn.print_center = function(x, y, string, font, size, letter_spacing) {
    		var path = this.print(x, y, string, font, size, 'baseline', letter_spacing);
    		var bb = path.getBBox();
    
    		// Translate the text so it's centered.
    		var dx = (x - bb.x) - bb.width / 2;
    		var dy = (y - bb.y) - bb.height / 2;
    
    		// Due to an issue in Raphael 2.1.0 (that seems to be fixed later)
    		// we remap the path itself, instead of using a transformation matrix
    		var m = new Raphael.matrix();
    		m.translate(dx, dy);
    		return path.attr('path', Raphael.mapPath(path.attr('path'), m));
    
    		// otherwise we would do this:
    		//return path.transform("t" + dx + "," + dy);
    	};
    
    /******************
     * BaseTheme
     ******************/
    
    	var BaseTheme = function(diagram) {
    		this.init(diagram);
    	};
    
    	_.extend(BaseTheme.prototype, {
    		init : function(diagram) {
    			this.diagram = diagram;
    			this._paper  = undefined;
    			this._font   = undefined;
    
    			this._title  = undefined; // hack - This should be somewhere better
    
    			this._actors_height  = 0;
    			this._signals_height = 0;
    
    			var a = this.arrow_types = {};
    			a[ARROWTYPE.FILLED] = 'block';
    			a[ARROWTYPE.OPEN]   = 'open';
    
    			var l = this.line_types = {};
    			l[LINETYPE.SOLID]  = '';
    			l[LINETYPE.DOTTED] = '-';
    		},
    
    		init_paper : function(container) {
    			this._paper = new Raphael(container, 320, 200);
    		},
    
    		init_font : function() {},
    
    		draw_line : function(x1, y1, x2, y2) {
    			return this._paper.line(x1, y1, x2, y2);
    		},
    
    		draw_rect : function(x, y, w, h) {
    			return this._paper.rect(x, y, w, h);
    		},
    
    		draw : function(container) {
    			var diagram = this.diagram;
    			this.init_paper(container);
    			this.init_font();
    
    			this.layout();
    
    			var title_height = this._title ? this._title.height : 0;
    
    			this._paper.setStart();
    			this._paper.setSize(diagram.width, diagram.height);
    
    			var y = DIAGRAM_MARGIN + title_height;
    
    			this.draw_title();
    			this.draw_actors(y);
    			this.draw_signals(y + this._actors_height);
    
    			this._paper.setFinish();
    		},
    
    		layout : function() {
    			// Local copies
    			var diagram = this.diagram;
    			var paper   = this._paper;
    			var font    = this._font;
    			var actors  = diagram.actors;
    			var signals = diagram.signals;
    
    			diagram.width = 0;  // min width
    			diagram.height = 0; // min width
    
    			// Setup some layout stuff
    			if (diagram.title) {
    				var title = this._title = {};
    				var bb = paper.text_bbox(diagram.title, font);
    				title.text_bb = bb;
    				title.message = diagram.title;
    
    				title.width  = bb.width  + (TITLE_PADDING + TITLE_MARGIN) * 2;
    				title.height = bb.height + (TITLE_PADDING + TITLE_MARGIN) * 2;
    				title.x = DIAGRAM_MARGIN;
    				title.y = DIAGRAM_MARGIN;
    
    				diagram.width  += title.width;
    				diagram.height += title.height;
    			}
    
    			_.each(actors, function(a) {
    				var bb = paper.text_bbox(a.name, font);
    				a.text_bb = bb;
    
    				//var bb = t.attr("text", a.name).getBBox();
    				a.x = 0; a.y = 0;
    				a.width  = bb.width  + (ACTOR_PADDING + ACTOR_MARGIN) * 2;
    				a.height = bb.height + (ACTOR_PADDING + ACTOR_MARGIN) * 2;
    
    				a.distances = [];
    				a.padding_right = 0;
    				this._actors_height = Math.max(a.height, this._actors_height);
    			}, this);
    
    			function actor_ensure_distance(a, b, d) {
    				assert(a < b, "a must be less than or equal to b");
    
    				if (a < 0) {
    					// Ensure b has left margin
    					b = actors[b];
    					b.x = Math.max(d - b.width / 2, b.x);
    				} else if (b >= actors.length) {
    					// Ensure a has right margin
    					a = actors[a];
    					a.padding_right = Math.max(d, a.padding_right);
    				} else {
    					a = actors[a];
    					a.distances[b] = Math.max(d, a.distances[b] ? a.distances[b] : 0);
    				}
    			}
    
    			_.each(signals, function(s) {
    				var a, b; // Indexes of the left and right actors involved
    
    				var bb = paper.text_bbox(s.message, font);
    
    				//var bb = t.attr("text", s.message).getBBox();
    				s.text_bb = bb;
    				s.width   = bb.width;
    				s.height  = bb.height;
    
    				var extra_width = 0;
    
    				if (s.type == "Signal") {
    
    					s.width  += (SIGNAL_MARGIN + SIGNAL_PADDING) * 2;
    					s.height += (SIGNAL_MARGIN + SIGNAL_PADDING) * 2;
    
    					if (s.isSelf()) {
    						a = s.actorA.index;
    						b = a + 1;
    						s.width += SELF_SIGNAL_WIDTH;
    					} else {
    						a = Math.min(s.actorA.index, s.actorB.index);
    						b = Math.max(s.actorA.index, s.actorB.index);
    					}
    
    				} else if (s.type == "Note") {
    					s.width  += (NOTE_MARGIN + NOTE_PADDING) * 2;
    					s.height += (NOTE_MARGIN + NOTE_PADDING) * 2;
    
    					// HACK lets include the actor's padding
    					extra_width = 2 * ACTOR_MARGIN;
    
    					if (s.placement == PLACEMENT.LEFTOF) {
    						b = s.actor.index;
    						a = b - 1;
    					} else if (s.placement == PLACEMENT.RIGHTOF) {
    						a = s.actor.index;
    						b = a + 1;
    					} else if (s.placement == PLACEMENT.OVER && s.hasManyActors()) {
    						// Over multiple actors
    						a = Math.min(s.actor[0].index, s.actor[1].index);
    						b = Math.max(s.actor[0].index, s.actor[1].index);
    
    						// We don't need our padding, and we want to overlap
    						extra_width = - (NOTE_PADDING * 2 + NOTE_OVERLAP * 2);
    
    					} else if (s.placement == PLACEMENT.OVER) {
    						// Over single actor
    						a = s.actor.index;
    						actor_ensure_distance(a - 1, a, s.width / 2);
    						actor_ensure_distance(a, a + 1, s.width / 2);
    						this._signals_height += s.height;
    
    						return; // Bail out early
    					}
    				} else {
    					throw new Error("Unhandled signal type:" + s.type);
    				}
    
    				actor_ensure_distance(a, b, s.width + extra_width);
    				this._signals_height += s.height;
    			}, this);
    
    			// Re-jig the positions
    			var actors_x = 0;
    			_.each(actors, function(a) {
    				a.x = Math.max(actors_x, a.x);
    
    				// TODO This only works if we loop in sequence, 0, 1, 2, etc
    				_.each(a.distances, function(distance, b) {
    					// lodash (and possibly others) do not like sparse arrays
    					// so sometimes they return undefined
    					if (typeof distance == "undefined")
    						return;
    
    					b = actors[b];
    					distance = Math.max(distance, a.width / 2, b.width / 2);
    					b.x = Math.max(b.x, a.x + a.width/2 + distance - b.width/2);
    				});
    
    				actors_x = a.x + a.width + a.padding_right;
    			}, this);
    
    			diagram.width = Math.max(actors_x, diagram.width);
    
    			// TODO Refactor a little
    			diagram.width  += 2 * DIAGRAM_MARGIN;
    			diagram.height += 2 * DIAGRAM_MARGIN + 2 * this._actors_height + this._signals_height;
    
    			return this;
    		},
    
    		draw_title : function() {
    			var title = this._title;
    			if (title)
    				this.draw_text_box(title, title.message, TITLE_MARGIN, TITLE_PADDING, this._font);
    		},
    
    		draw_actors : function(offsetY) {
    			var y = offsetY;
    			_.each(this.diagram.actors, function(a) {
    				// Top box
    				this.draw_actor(a, y, this._actors_height);
    
    				// Bottom box
    				this.draw_actor(a, y + this._actors_height + this._signals_height, this._actors_height);
    
    				// Veritical line
    				var aX = getCenterX(a);
    				var line = this.draw_line(
    					aX, y + this._actors_height - ACTOR_MARGIN,
    					aX, y + this._actors_height + ACTOR_MARGIN + this._signals_height);
    				line.attr(LINE);
    			}, this);
    		},
    
    		draw_actor : function (actor, offsetY, height) {
    			actor.y      = offsetY;
    			actor.height = height;
    			this.draw_text_box(actor, actor.name, ACTOR_MARGIN, ACTOR_PADDING, this._font);
    		},
    
    		draw_signals : function (offsetY) {
    			var y = offsetY;
    			_.each(this.diagram.signals, function(s) {
    				if (s.type == "Signal") {
    					if (s.isSelf()) {
    						this.draw_self_signal(s, y);
    					} else {
    						this.draw_signal(s, y);
    					}
    
    				} else if (s.type == "Note") {
    					this.draw_note(s, y);
    				}
    
    				y += s.height;
    			}, this);
    		},
    
    		draw_self_signal : function(signal, offsetY) {
    			assert(signal.isSelf(), "signal must be a self signal");
    
    			var text_bb = signal.text_bb;
    			var aX = getCenterX(signal.actorA);
    
    			var x = aX + SELF_SIGNAL_WIDTH + SIGNAL_PADDING - text_bb.x;
    			var y = offsetY + signal.height / 2;
    
    			this.draw_text(x, y, signal.message, this._font);
    
    			var attr = _.extend({}, LINE, {
    				'stroke-dasharray': this.line_types[signal.linetype]
    			});
    
    			var y1 = offsetY + SIGNAL_MARGIN;
    			var y2 = y1 + signal.height - SIGNAL_MARGIN;
    
    			// Draw three lines, the last one with a arrow
    			var line;
    			line = this.draw_line(aX, y1, aX + SELF_SIGNAL_WIDTH, y1);
    			line.attr(attr);
    
    			line = this.draw_line(aX + SELF_SIGNAL_WIDTH, y1, aX + SELF_SIGNAL_WIDTH, y2);
    			line.attr(attr);
    
    			line = this.draw_line(aX + SELF_SIGNAL_WIDTH, y2, aX, y2);
    			attr['arrow-end'] = this.arrow_types[signal.arrowtype] + '-wide-long';
    			line.attr(attr);
    		},
    
    		draw_signal : function (signal, offsetY) {
    			var aX = getCenterX( signal.actorA );
    			var bX = getCenterX( signal.actorB );
    
    			// Mid point between actors
    			var x = (bX - aX) / 2 + aX;
    			var y = offsetY + SIGNAL_MARGIN + 2*SIGNAL_PADDING;
    
    			// Draw the text in the middle of the signal
    			this.draw_text(x, y, signal.message, this._font);
    
    			// Draw the line along the bottom of the signal
    			y = offsetY + signal.height - SIGNAL_MARGIN - SIGNAL_PADDING;
    			var line = this.draw_line(aX, y, bX, y);
    			line.attr(LINE);
    			line.attr({
    				'arrow-end': this.arrow_types[signal.arrowtype] + '-wide-long',
    				'stroke-dasharray': this.line_types[signal.linetype]
    			});
    
    			//var ARROW_SIZE = 16;
    			//var dir = this.actorA.x < this.actorB.x ? 1 : -1;
    			//draw_arrowhead(bX, offsetY, ARROW_SIZE, dir);
    		},
    
    		draw_note : function (note, offsetY) {
    			note.y = offsetY;
    			var actorA = note.hasManyActors() ? note.actor[0] : note.actor;
    			var aX = getCenterX( actorA );
    			switch (note.placement) {
    				case PLACEMENT.RIGHTOF:
    					note.x = aX + ACTOR_MARGIN;
    					break;
    				case PLACEMENT.LEFTOF:
    					note.x = aX - ACTOR_MARGIN - note.width;
    					break;
    				case PLACEMENT.OVER:
    					if (note.hasManyActors()) {
    						var bX = getCenterX( note.actor[1] );
    						var overlap = NOTE_OVERLAP + NOTE_PADDING;
    						note.x = aX - overlap;
    						note.width = (bX + overlap) - note.x;
    					} else {
    						note.x = aX - note.width / 2;
    					}
    					break;
    				default:
    					throw new Error("Unhandled note placement:" + note.placement);
    			}
    
    			this.draw_text_box(note, note.message, NOTE_MARGIN, NOTE_PADDING, this._font);
    		},
    
    		/**
    		 * Draws text with a white background
    		 * x,y (int) x,y center point for this text
    		 * TODO Horz center the text when it's multi-line print
    		 */
    		draw_text : function (x, y, text, font) {
    			var paper = this._paper;
    			var f = font || {};
    			var t;
    			if (f._obj) {
    				t = paper.print_center(x, y, text, f._obj, f['font-size']);
    			} else {
    				t = paper.text(x, y, text);
    				t.attr(f);
    			}
    			// draw a rect behind it
    			var bb = t.getBBox();
    			var r = paper.rect(bb.x, bb.y, bb.width, bb.height);
    			r.attr({'fill': "#fff", 'stroke': 'none'});
    
    			t.toFront();
    		},
    
    		draw_text_box : function (box, text, margin, padding, font) {
    			var x = box.x + margin;
    			var y = box.y + margin;
    			var w = box.width  - 2 * margin;
    			var h = box.height - 2 * margin;
    
    			// Draw inner box
    			var rect = this.draw_rect(x, y, w, h);
    			rect.attr(LINE);
    
    			// Draw text (in the center)
    			x = getCenterX(box);
    			y = getCenterY(box);
    
    			this.draw_text(x, y, text, font);
    		}
    
    		/**
    		 * Draws a arrow head
    		 * direction must be -1 for left, or 1 for right
    		 */
    		//function draw_arrowhead(x, y, size, direction) {
    		//	var dx = (size/2) * direction;
    		//	var dy = (size/2);
    		//
    		//	y -= dy; x -= dx;
    		//	var p = this._paper.path("M" + x + "," + y + "v" + size + "l" + dx + ",-" + (size/2) + "Z");
    		//}
    	});
    
    /******************
     * RaphaelTheme
     ******************/
    
    	var RaphaelTheme = function(diagram) {
    		this.init(diagram);
    	};
    
    	_.extend(RaphaelTheme.prototype, BaseTheme.prototype, {
    
    		init_font : function() {
    			this._font = {
    				'font-size': 16,
    				'font-family': 'Andale Mono, monospace'
    			};
    		}
    
    	});
    
    /******************
     * HandRaphaelTheme
     ******************/
    
    	var HandRaphaelTheme = function(diagram) {
    		this.init(diagram);
    	};
    
    	// Take the standard RaphaelTheme and make all the lines wobbly
    	_.extend(HandRaphaelTheme.prototype, BaseTheme.prototype, {
    		init_font : function() {
    			this._font = {
    				'font-size': 16,
    				'font-family': 'daniel'
    			};
    
    			this._font._obj = this._paper.getFont('daniel');
    		},
    
    		draw_line : function(x1, y1, x2, y2) {
    			return this._paper.handLine(x1, y1, x2, y2);
    		},
    
    		draw_rect : function(x, y, w, h) {
    			return this._paper.handRect(x, y, w, h);
    		}
    	});
    
    	var themes = {
    		simple : RaphaelTheme,
    		hand  : HandRaphaelTheme
    	};
    
    	Diagram.prototype.drawSVG = function (container, options) {
    		var default_options = {
    			theme: 'hand'
    		};
    
    		options = _.defaults(options || {}, default_options);
    
    		if (!(options.theme in themes))
    			throw new Error("Unsupported theme: " + options.theme);
    
    		var drawing = new themes[options.theme](this);
    		drawing.draw(container);
    
    	}; // end of drawSVG
    
    });
    
    // flowchart, v1.3.4
    // Copyright (c)2014 Adriano Raiano (adrai).
    // Distributed under MIT license
    // http://adrai.github.io/flowchart.js
    !function(a,b){"object"==typeof exports?module.exports=b():"function"==typeof define&&define.amd&&define('flow-chart',[],b)}(this,function(){function a(b,c){if(!b||"function"==typeof b)return c;var d={};for(var e in c)d[e]=c[e];for(e in b)b[e]&&(d[e]="object"==typeof d[e]?a(d[e],b[e]):b[e]);return d}function b(a,b){if("function"==typeof Object.create)a.super_=b,a.prototype=Object.create(b.prototype,{constructor:{value:a,enumerable:!1,writable:!0,configurable:!0}});else{a.super_=b;var c=function(){};c.prototype=b.prototype,a.prototype=new c,a.prototype.constructor=a}}function c(a,b,c){var d,e,f="M{0},{1}";for(d=2,e=2*c.length+2;e>d;d+=2)f+=" L{"+d+"},{"+(d+1)+"}";var g=[b.x,b.y];for(d=0,e=c.length;e>d;d++)g.push(c[d].x),g.push(c[d].y);var h=a.paper.path(f,g);h.attr("stroke",a.options["element-color"]),h.attr("stroke-width",a.options["line-width"]);var i=a.options.font,j=a.options["font-family"],k=a.options["font-weight"];return i&&h.attr({font:i}),j&&h.attr({"font-family":j}),k&&h.attr({"font-weight":k}),h}function d(a,b,c,d){var e,f;"[object Array]"!==Object.prototype.toString.call(c)&&(c=[c]);var g="M{0},{1}";for(e=2,f=2*c.length+2;f>e;e+=2)g+=" L{"+e+"},{"+(e+1)+"}";var h=[b.x,b.y];for(e=0,f=c.length;f>e;e++)h.push(c[e].x),h.push(c[e].y);var i=a.paper.path(g,h);i.attr({stroke:a.options["line-color"],"stroke-width":a.options["line-width"],"arrow-end":a.options["arrow-end"]});var j=a.options.font,k=a.options["font-family"],l=a.options["font-weight"];if(j&&i.attr({font:j}),k&&i.attr({"font-family":k}),l&&i.attr({"font-weight":l}),d){var m=!1,n=a.paper.text(0,0,d),o=!1,p=c[0];b.y===p.y&&(o=!0);var q=0,r=0;m?(q=b.x>p.x?b.x-(b.x-p.x)/2:p.x-(p.x-b.x)/2,r=b.y>p.y?b.y-(b.y-p.y)/2:p.y-(p.y-b.y)/2,o?(q-=n.getBBox().width/2,r-=a.options["text-margin"]):(q+=a.options["text-margin"],r-=n.getBBox().height/2)):(q=b.x,r=b.y,o?(q+=a.options["text-margin"]/2,r-=a.options["text-margin"]):(q+=a.options["text-margin"]/2,r+=a.options["text-margin"])),n.attr({"text-anchor":"start","font-size":a.options["font-size"],fill:a.options["font-color"],x:q,y:r}),j&&n.attr({font:j}),k&&n.attr({"font-family":k}),l&&n.attr({"font-weight":l})}return i}function e(a,b,c,d,e,f,g,h){var i,j,k,l,m,n={x:null,y:null,onLine1:!1,onLine2:!1};return i=(h-f)*(c-a)-(g-e)*(d-b),0===i?n:(j=b-f,k=a-e,l=(g-e)*j-(h-f)*k,m=(c-a)*j-(d-b)*k,j=l/i,k=m/i,n.x=a+j*(c-a),n.y=b+j*(d-b),j>0&&1>j&&(n.onLine1=!0),k>0&&1>k&&(n.onLine2=!0),n)}function f(a,b){b=b||{},this.paper=new Raphael(a),this.options=q.defaults(b,p),this.symbols=[],this.lines=[],this.start=null}function g(a,b,c){this.chart=a,this.group=this.chart.paper.set(),this.symbol=c,this.connectedTo=[],this.symbolType=b.symbolType,this.flowstate=b.flowstate||"future",this.next_direction=b.next&&b.direction_next?b.direction_next:void 0,this.text=this.chart.paper.text(0,0,b.text),b.key&&(this.text.node.id=b.key+"t"),this.text.node.setAttribute("class",this.getAttr("class")+"t"),this.text.attr({"text-anchor":"start",x:this.getAttr("text-margin"),fill:this.getAttr("font-color"),"font-size":this.getAttr("font-size")});var d=this.getAttr("font"),e=this.getAttr("font-family"),f=this.getAttr("font-weight");d&&this.text.attr({font:d}),e&&this.text.attr({"font-family":e}),f&&this.text.attr({"font-weight":f}),b.link&&this.text.attr("href",b.link),b.target&&this.text.attr("target",b.target);var g=this.getAttr("maxWidth");if(g){for(var h=b.text.split(" "),i="",j=0,k=h.length;k>j;j++){var l=h[j];this.text.attr("text",i+" "+l),i+=this.text.getBBox().width>g?"\n"+l:" "+l}this.text.attr("text",i.substring(1))}if(this.group.push(this.text),c){var m=this.getAttr("text-margin");c.attr({fill:this.getAttr("fill"),stroke:this.getAttr("element-color"),"stroke-width":this.getAttr("line-width"),width:this.text.getBBox().width+2*m,height:this.text.getBBox().height+2*m}),c.node.setAttribute("class",this.getAttr("class")),b.link&&c.attr("href",b.link),b.target&&c.attr("target",b.target),b.key&&(c.node.id=b.key),this.group.push(c),c.insertBefore(this.text),this.text.attr({y:c.getBBox().height/2}),this.initialize()}}function h(a,b){var c=a.paper.rect(0,0,0,0,20);b=b||{},b.text=b.text||"Start",g.call(this,a,b,c)}function i(a,b){var c=a.paper.rect(0,0,0,0,20);b=b||{},b.text=b.text||"End",g.call(this,a,b,c)}function j(a,b){var c=a.paper.rect(0,0,0,0);b=b||{},g.call(this,a,b,c)}function k(a,b){var c=a.paper.rect(0,0,0,0);b=b||{},g.call(this,a,b,c),c.attr({width:this.text.getBBox().width+4*this.getAttr("text-margin")}),this.text.attr({x:2*this.getAttr("text-margin")});var d=a.paper.rect(0,0,0,0);d.attr({x:this.getAttr("text-margin"),stroke:this.getAttr("element-color"),"stroke-width":this.getAttr("line-width"),width:this.text.getBBox().width+2*this.getAttr("text-margin"),height:this.text.getBBox().height+2*this.getAttr("text-margin"),fill:this.getAttr("fill")}),b.key&&(d.node.id=b.key+"i");var e=this.getAttr("font"),f=this.getAttr("font-family"),h=this.getAttr("font-weight");e&&d.attr({font:e}),f&&d.attr({"font-family":f}),h&&d.attr({"font-weight":h}),b.link&&d.attr("href",b.link),b.target&&d.attr("target",b.target),this.group.push(d),d.insertBefore(this.text),this.initialize()}function l(a,b){b=b||{},g.call(this,a,b),this.textMargin=this.getAttr("text-margin"),this.text.attr({x:3*this.textMargin});var d=this.text.getBBox().width+4*this.textMargin,e=this.text.getBBox().height+2*this.textMargin,f=this.textMargin,h=e/2,i={x:f,y:h},j=[{x:f-this.textMargin,y:e},{x:f-this.textMargin+d,y:e},{x:f-this.textMargin+d+2*this.textMargin,y:0},{x:f-this.textMargin+2*this.textMargin,y:0},{x:f,y:h}],k=c(a,i,j);k.attr({stroke:this.getAttr("element-color"),"stroke-width":this.getAttr("line-width"),fill:this.getAttr("fill")}),b.link&&k.attr("href",b.link),b.target&&k.attr("target",b.target),b.key&&(k.node.id=b.key),k.node.setAttribute("class",this.getAttr("class")),this.text.attr({y:k.getBBox().height/2}),this.group.push(k),k.insertBefore(this.text),this.initialize()}function m(a,b){b=b||{},g.call(this,a,b),this.textMargin=this.getAttr("text-margin"),this.yes_direction="bottom",this.no_direction="right",b.yes&&b.direction_yes&&b.no&&!b.direction_no?"right"===b.direction_yes?(this.no_direction="bottom",this.yes_direction="right"):(this.no_direction="right",this.yes_direction="bottom"):b.yes&&!b.direction_yes&&b.no&&b.direction_no?"right"===b.direction_no?(this.yes_direction="bottom",this.no_direction="right"):(this.yes_direction="right",this.no_direction="bottom"):(this.yes_direction="bottom",this.no_direction="right"),this.yes_direction=this.yes_direction||"bottom",this.no_direction=this.no_direction||"right",this.text.attr({x:2*this.textMargin});var d=this.text.getBBox().width+3*this.textMargin;d+=d/2;var e=this.text.getBBox().height+2*this.textMargin;e+=e/2,e=Math.max(.5*d,e);var f=d/4,h=e/4;this.text.attr({x:f+this.textMargin/2});var i={x:f,y:h},j=[{x:f-d/4,y:h+e/4},{x:f-d/4+d/2,y:h+e/4+e/2},{x:f-d/4+d,y:h+e/4},{x:f-d/4+d/2,y:h+e/4-e/2},{x:f-d/4,y:h+e/4}],k=c(a,i,j);k.attr({stroke:this.getAttr("element-color"),"stroke-width":this.getAttr("line-width"),fill:this.getAttr("fill")}),b.link&&k.attr("href",b.link),b.target&&k.attr("target",b.target),b.key&&(k.node.id=b.key),k.node.setAttribute("class",this.getAttr("class")),this.text.attr({y:k.getBBox().height/2}),this.group.push(k),k.insertBefore(this.text),this.initialize()}function n(a){function b(a){var b=a.indexOf("(")+1,c=a.indexOf(")");return b>=0&&c>=0?d.symbols[a.substring(0,b-1)]:d.symbols[a]}function c(a){var b="next",c=a.indexOf("(")+1,d=a.indexOf(")");return c>=0&&d>=0&&(b=D.substring(c,d),b.indexOf(",")<0&&"yes"!==b&&"no"!==b&&(b="next, "+b)),b}a=a||"",a=a.trim();for(var d={symbols:{},start:null,drawSVG:function(a,b){function c(a){if(g[a.key])return g[a.key];switch(a.symbolType){case"start":g[a.key]=new h(e,a);break;case"end":g[a.key]=new i(e,a);break;case"operation":g[a.key]=new j(e,a);break;case"inputoutput":g[a.key]=new l(e,a);break;case"subroutine":g[a.key]=new k(e,a);break;case"condition":g[a.key]=new m(e,a);break;default:return new Error("Wrong symbol type!")}return g[a.key]}var d=this;this.diagram&&this.diagram.clean();var e=new f(a,b);this.diagram=e;var g={};!function n(a,b,f){var g=c(a);return d.start===a?e.startWith(g):b&&f&&!b.pathOk&&(b instanceof m?(f.yes===a&&b.yes(g),f.no===a&&b.no(g)):b.then(g)),g.pathOk?g:(g instanceof m?(a.yes&&n(a.yes,g,a),a.no&&n(a.no,g,a)):a.next&&n(a.next,g,a),g)}(this.start),e.render()},clean:function(){this.diagram.clean()}},e=[],g=0,n=1,o=a.length;o>n;n++)if("\n"===a[n]&&"\\"!==a[n-1]){var p=a.substring(g,n);g=n+1,e.push(p.replace(/\\\n/g,"\n"))}g<a.length&&e.push(a.substr(g));for(var q=1,r=e.length;r>q;){var s=e[q];s.indexOf(": ")<0&&s.indexOf("(")<0&&s.indexOf(")")<0&&s.indexOf("->")<0&&s.indexOf("=>")<0?(e[q-1]+="\n"+s,e.splice(q,1),r--):q++}for(;e.length>0;){var t=e.splice(0,1)[0];if(t.indexOf("=>")>=0){var u,v=t.split("=>"),w={key:v[0],symbolType:v[1],text:null,link:null,target:null,flowstate:null};if(w.symbolType.indexOf(": ")>=0&&(u=w.symbolType.split(": "),w.symbolType=u[0],w.text=u[1]),w.text&&w.text.indexOf(":>")>=0?(u=w.text.split(":>"),w.text=u[0],w.link=u[1]):w.symbolType.indexOf(":>")>=0&&(u=w.symbolType.split(":>"),w.symbolType=u[0],w.link=u[1]),w.symbolType.indexOf("\n")>=0&&(w.symbolType=w.symbolType.split("\n")[0]),w.link){var x=w.link.indexOf("[")+1,y=w.link.indexOf("]");x>=0&&y>=0&&(w.target=w.link.substring(x,y),w.link=w.link.substring(0,x-1))}if(w.text&&w.text.indexOf("|")>=0){var z=w.text.split("|");w.text=z[0],w.flowstate=z[1].trim()}d.symbols[w.key]=w}else if(t.indexOf("->")>=0)for(var A=t.split("->"),B=0,C=A.length;C>B;B++){var D=A[B],E=b(D),F=c(D),G=null;if(F.indexOf(",")>=0){var H=F.split(",");F=H[0],G=H[1].trim()}if(d.start||(d.start=E),C>B+1){var I=A[B+1];E[F]=b(I),E["direction_"+F]=G,G=null}}}return d}Array.prototype.indexOf||(Array.prototype.indexOf=function(a){if(null===this)throw new TypeError;var b=Object(this),c=b.length>>>0;if(0===c)return-1;var d=0;if(arguments.length>0&&(d=Number(arguments[1]),d!=d?d=0:0!==d&&1/0!=d&&d!=-1/0&&(d=(d>0||-1)*Math.floor(Math.abs(d)))),d>=c)return-1;for(var e=d>=0?d:Math.max(c-Math.abs(d),0);c>e;e++)if(e in b&&b[e]===a)return e;return-1}),Array.prototype.lastIndexOf||(Array.prototype.lastIndexOf=function(a){if(null===this)throw new TypeError;var b=Object(this),c=b.length>>>0;if(0===c)return-1;var d=c;arguments.length>1&&(d=Number(arguments[1]),d!=d?d=0:0!==d&&d!=1/0&&d!=-(1/0)&&(d=(d>0||-1)*Math.floor(Math.abs(d))));for(var e=d>=0?Math.min(d,c-1):c-Math.abs(d);e>=0;e--)if(e in b&&b[e]===a)return e;return-1}),String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")});var o={},p={x:0,y:0,"line-width":3,"line-length":50,"text-margin":10,"font-size":14,"font-color":"black","line-color":"black","element-color":"black",fill:"white","yes-text":"yes","no-text":"no","arrow-end":"block","class":"flowchart",symbols:{start:{},end:{},condition:{},inputoutput:{},operation:{},subroutine:{}}},q={defaults:a,inherits:b};return f.prototype.handle=function(a){this.symbols.indexOf(a)<=-1&&this.symbols.push(a);var b=this;return a instanceof m?(a.yes=function(c){return a.yes_symbol=c,a.no_symbol&&(a.pathOk=!0),b.handle(c)},a.no=function(c){return a.no_symbol=c,a.yes_symbol&&(a.pathOk=!0),b.handle(c)}):a.then=function(c){return a.next=c,a.pathOk=!0,b.handle(c)},a},f.prototype.startWith=function(a){return this.start=a,this.handle(a)},f.prototype.render=function(){var a,b=0,c=0,d=0,e=0,f=0,g=0;for(d=0,e=this.symbols.length;e>d;d++)a=this.symbols[d],a.width>b&&(b=a.width),a.height>c&&(c=a.height);for(d=0,e=this.symbols.length;e>d;d++)a=this.symbols[d],a.shiftX(this.options.x+(b-a.width)/2+this.options["line-width"]),a.shiftY(this.options.y+(c-a.height)/2+this.options["line-width"]);for(this.start.render(),d=0,e=this.symbols.length;e>d;d++)a=this.symbols[d],a.renderLines();for(f=this.maxXFromLine,d=0,e=this.symbols.length;e>d;d++){a=this.symbols[d];var h=a.getX()+a.width,i=a.getY()+a.height;h>f&&(f=h),i>g&&(g=i)}this.paper.setSize(f+this.options["line-width"],g+this.options["line-width"])},f.prototype.clean=function(){if(this.paper){var a=this.paper.canvas;a.parentNode.removeChild(a)}},g.prototype.getAttr=function(a){if(!this.chart)return void 0;var b,c=this.chart.options?this.chart.options[a]:void 0,d=this.chart.options.symbols?this.chart.options.symbols[this.symbolType][a]:void 0;return this.chart.options.flowstate&&this.chart.options.flowstate[this.flowstate]&&(b=this.chart.options.flowstate[this.flowstate][a]),b||d||c},g.prototype.initialize=function(){this.group.transform("t"+this.getAttr("line-width")+","+this.getAttr("line-width")),this.width=this.group.getBBox().width,this.height=this.group.getBBox().height},g.prototype.getCenter=function(){return{x:this.getX()+this.width/2,y:this.getY()+this.height/2}},g.prototype.getX=function(){return this.group.getBBox().x},g.prototype.getY=function(){return this.group.getBBox().y},g.prototype.shiftX=function(a){this.group.transform("t"+(this.getX()+a)+","+this.getY())},g.prototype.setX=function(a){this.group.transform("t"+a+","+this.getY())},g.prototype.shiftY=function(a){this.group.transform("t"+this.getX()+","+(this.getY()+a))},g.prototype.setY=function(a){this.group.transform("t"+this.getX()+","+a)},g.prototype.getTop=function(){var a=this.getY(),b=this.getX()+this.width/2;return{x:b,y:a}},g.prototype.getBottom=function(){var a=this.getY()+this.height,b=this.getX()+this.width/2;return{x:b,y:a}},g.prototype.getLeft=function(){var a=this.getY()+this.group.getBBox().height/2,b=this.getX();return{x:b,y:a}},g.prototype.getRight=function(){var a=this.getY()+this.group.getBBox().height/2,b=this.getX()+this.group.getBBox().width;return{x:b,y:a}},g.prototype.render=function(){if(this.next){var a=this.getAttr("line-length");if("right"===this.next_direction){var b=this.getRight();if(this.next.getLeft(),!this.next.isPositioned){this.next.setY(b.y-this.next.height/2),this.next.shiftX(this.group.getBBox().x+this.width+a);var c=this;!function e(){for(var b,d=!1,f=0,g=c.chart.symbols.length;g>f;f++){b=c.chart.symbols[f];var h=Math.abs(b.getCenter().x-c.next.getCenter().x);if(b.getCenter().y>c.next.getCenter().y&&h<=c.next.width/2){d=!0;break}}d&&(c.next.setX(b.getX()+b.width+a),e())}(),this.next.isPositioned=!0,this.next.render()}}else{var d=this.getBottom();this.next.getTop(),this.next.isPositioned||(this.next.shiftY(this.getY()+this.height+a),this.next.setX(d.x-this.next.width/2),this.next.isPositioned=!0,this.next.render())}}},g.prototype.renderLines=function(){this.next&&(this.next_direction?this.drawLineTo(this.next,"",this.next_direction):this.drawLineTo(this.next))},g.prototype.drawLineTo=function(a,b,c){this.connectedTo.indexOf(a)<0&&this.connectedTo.push(a);var f,g=this.getCenter().x,h=this.getCenter().y,i=(this.getTop(),this.getRight()),j=this.getBottom(),k=this.getLeft(),l=a.getCenter().x,m=a.getCenter().y,n=a.getTop(),o=a.getRight(),p=(a.getBottom(),a.getLeft()),q=g===l,r=h===m,s=m>h,t=h>m,u=g>l,v=l>g,w=0,x=this.getAttr("line-length"),y=this.getAttr("line-width");if(c&&"bottom"!==c||!q||!s)if(c&&"right"!==c||!r||!v)if(c&&"left"!==c||!r||!u)if(c&&"right"!==c||!q||!t)if(c&&"right"!==c||!q||!s)if(c&&"bottom"!==c||!u)if(c&&"bottom"!==c||!v)if(c&&"right"===c&&u)f=d(this.chart,i,[{x:i.x+x/2,y:i.y},{x:i.x+x/2,y:n.y-x/2},{x:n.x,y:n.y-x/2},{x:n.x,y:n.y}],b),this.rightStart=!0,a.topEnd=!0,w=i.x+x/2;else if(c&&"right"===c&&v)f=d(this.chart,i,[{x:n.x,y:i.y},{x:n.x,y:n.y}],b),this.rightStart=!0,a.topEnd=!0,w=i.x+x/2;else if(c&&"bottom"===c&&q&&t)f=d(this.chart,j,[{x:j.x,y:j.y+x/2},{x:i.x+x/2,y:j.y+x/2},{x:i.x+x/2,y:n.y-x/2},{x:n.x,y:n.y-x/2},{x:n.x,y:n.y}],b),this.bottomStart=!0,a.topEnd=!0,w=j.x+x/2;else if("left"===c&&q&&t){var z=k.x-x/2;p.x<k.x&&(z=p.x-x/2),f=d(this.chart,k,[{x:z,y:k.y},{x:z,y:n.y-x/2},{x:n.x,y:n.y-x/2},{x:n.x,y:n.y}],b),this.leftStart=!0,a.topEnd=!0,w=k.x}else"left"===c&&(f=d(this.chart,k,[{x:n.x+(k.x-n.x)/2,y:k.y},{x:n.x+(k.x-n.x)/2,y:n.y-x/2},{x:n.x,y:n.y-x/2},{x:n.x,y:n.y}],b),this.leftStart=!0,a.topEnd=!0,w=k.x);else f=d(this.chart,j,[{x:j.x,y:j.y+x/2},{x:j.x+(j.x-n.x)/2,y:j.y+x/2},{x:j.x+(j.x-n.x)/2,y:n.y-x/2},{x:n.x,y:n.y-x/2},{x:n.x,y:n.y}],b),this.bottomStart=!0,a.topEnd=!0,w=j.x+(j.x-n.x)/2;else f=this.leftEnd&&t?d(this.chart,j,[{x:j.x,y:j.y+x/2},{x:j.x+(j.x-n.x)/2,y:j.y+x/2},{x:j.x+(j.x-n.x)/2,y:n.y-x/2},{x:n.x,y:n.y-x/2},{x:n.x,y:n.y}],b):d(this.chart,j,[{x:j.x,y:n.y-x/2},{x:n.x,y:n.y-x/2},{x:n.x,y:n.y}],b),this.bottomStart=!0,a.topEnd=!0,w=j.x+(j.x-n.x)/2;else f=d(this.chart,i,[{x:i.x+x/2,y:i.y},{x:i.x+x/2,y:n.y-x/2},{x:n.x,y:n.y-x/2},{x:n.x,y:n.y}],b),this.rightStart=!0,a.topEnd=!0,w=i.x+x/2;else f=d(this.chart,i,[{x:i.x+x/2,y:i.y},{x:i.x+x/2,y:n.y-x/2},{x:n.x,y:n.y-x/2},{x:n.x,y:n.y}],b),this.rightStart=!0,a.topEnd=!0,w=i.x+x/2;else f=d(this.chart,k,o,b),this.leftStart=!0,a.rightEnd=!0,w=o.x;else f=d(this.chart,i,p,b),this.rightStart=!0,a.leftEnd=!0,w=p.x;else f=d(this.chart,j,n,b),this.bottomStart=!0,a.topEnd=!0,w=j.x;if(f){for(var A=0,B=this.chart.lines.length;B>A;A++)for(var C,D=this.chart.lines[A],E=D.attr("path"),F=f.attr("path"),G=0,H=E.length-1;H>G;G++){var I=[];I.push(["M",E[G][1],E[G][2]]),I.push(["L",E[G+1][1],E[G+1][2]]);for(var J=I[0][1],K=I[0][2],L=I[1][1],M=I[1][2],N=0,O=F.length-1;O>N;N++){var P=[];P.push(["M",F[N][1],F[N][2]]),P.push(["L",F[N+1][1],F[N+1][2]]);var Q=P[0][1],R=P[0][2],S=P[1][1],T=P[1][2],U=e(J,K,L,M,Q,R,S,T);if(U.onLine1&&U.onLine2){var V;R===T?Q>S?(V=["L",U.x+2*y,R],F.splice(N+1,0,V),V=["C",U.x+2*y,R,U.x,R-4*y,U.x-2*y,R],F.splice(N+2,0,V),f.attr("path",F)):(V=["L",U.x-2*y,R],F.splice(N+1,0,V),V=["C",U.x-2*y,R,U.x,R-4*y,U.x+2*y,R],F.splice(N+2,0,V),f.attr("path",F)):R>T?(V=["L",Q,U.y+2*y],F.splice(N+1,0,V),V=["C",Q,U.y+2*y,Q+4*y,U.y,Q,U.y-2*y],F.splice(N+2,0,V),f.attr("path",F)):(V=["L",Q,U.y-2*y],F.splice(N+1,0,V),V=["C",Q,U.y-2*y,Q+4*y,U.y,Q,U.y+2*y],F.splice(N+2,0,V),f.attr("path",F)),N+=2,C+=2}}}this.chart.lines.push(f)}(!this.chart.maxXFromLine||this.chart.maxXFromLine&&w>this.chart.maxXFromLine)&&(this.chart.maxXFromLine=w)},q.inherits(h,g),q.inherits(i,g),q.inherits(j,g),q.inherits(k,g),q.inherits(l,g),l.prototype.getLeft=function(){var a=this.getY()+this.group.getBBox().height/2,b=this.getX()+this.textMargin;return{x:b,y:a}},l.prototype.getRight=function(){var a=this.getY()+this.group.getBBox().height/2,b=this.getX()+this.group.getBBox().width-this.textMargin;return{x:b,y:a}},q.inherits(m,g),m.prototype.render=function(){this.yes_direction&&(this[this.yes_direction+"_symbol"]=this.yes_symbol),this.no_direction&&(this[this.no_direction+"_symbol"]=this.no_symbol);var a=this.getAttr("line-length");if(this.bottom_symbol){var b=this.getBottom();this.bottom_symbol.getTop(),this.bottom_symbol.isPositioned||(this.bottom_symbol.shiftY(this.getY()+this.height+a),this.bottom_symbol.setX(b.x-this.bottom_symbol.width/2),this.bottom_symbol.isPositioned=!0,this.bottom_symbol.render())}if(this.right_symbol){var c=this.getRight();if(this.right_symbol.getLeft(),!this.right_symbol.isPositioned){this.right_symbol.setY(c.y-this.right_symbol.height/2),this.right_symbol.shiftX(this.group.getBBox().x+this.width+a);var d=this;!function e(){for(var b,c=!1,f=0,g=d.chart.symbols.length;g>f;f++){b=d.chart.symbols[f];var h=Math.abs(b.getCenter().x-d.right_symbol.getCenter().x);if(b.getCenter().y>d.right_symbol.getCenter().y&&h<=d.right_symbol.width/2){c=!0;break}}c&&(d.right_symbol.setX(b.getX()+b.width+a),e())}(),this.right_symbol.isPositioned=!0,this.right_symbol.render()}}},m.prototype.renderLines=function(){this.yes_symbol&&this.drawLineTo(this.yes_symbol,this.getAttr("yes-text"),this.yes_direction),this.no_symbol&&this.drawLineTo(this.no_symbol,this.getAttr("no-text"),this.no_direction)},o.parse=n,o});
    define('extensions/umlDiagrams',[
    	// "jquery",
    	"underscore",
    	"utils",
    	"logger",
    	"classes/Extension",
    	// "ext!html/umlDiagramsSettingsBlock.html",
    	'crel',
    	'Diagram',
    	'flow-chart'
    ], function(_, utils, logger, Extension, crel, Diagram, flowChart) {
    
    	var umlDiagrams = new Extension("umlDiagrams", "UML Diagrams", true);
    	// umlDiagrams.settingsBlock = umlDiagramsSettingsBlockHTML;
    	umlDiagrams.defaultConfig = {
    		flowchartOptions: [
    			'{',
    			'   "line-width": 2,',
    			'   "font-family": "sans-serif",',
    			'   "font-weight": "normal"',
    			'}'
    		].join('\n')
    	};
    
    	// umlDiagrams.onLoadSettings = function() {
    	// 	utils.setInputValue("#textarea-umldiagram-flowchart-options", umlDiagrams.config.flowchartOptions);
    	// };
    
    	// umlDiagrams.onSaveSettings = function(newConfig, event) {
    	// 	newConfig.flowchartOptions = utils.getInputJSONValue("#textarea-umldiagram-flowchart-options", event);
    	// };
    
    	umlDiagrams.onPagedownConfigure = function(editor) {
    		var previewContentsElt = document.getElementById('preview-contents');
    		editor.hooks.chain("onPreviewRefresh", function() {
    			_.each(previewContentsElt.querySelectorAll('.prettyprint > .language-sequence'), function(elt) {
    				try {
    					var diagram = Diagram.parse(elt.textContent);
    					var preElt = elt.parentNode;
    					var containerElt = crel('div', {
    						class: 'sequence-diagram'
    					});
    					preElt.parentNode.replaceChild(containerElt, preElt);
    					diagram.drawSVG(containerElt, {
    						theme: 'simple'
    					});
    				}
    				catch(e) {
    				}
    			});
    			_.each(previewContentsElt.querySelectorAll('.prettyprint > .language-flow'), function(elt) {
    				try {
    					var chart = flowChart.parse(elt.textContent);
    					var preElt = elt.parentNode;
    					var containerElt = crel('div', {
    						class: 'flow-chart'
    					});
    					preElt.parentNode.replaceChild(containerElt, preElt);
    					chart.drawSVG(containerElt, JSON.parse(umlDiagrams.config.flowchartOptions));
    				}
    				catch(e) {
    				}
    			});
    		});
    	};
    
    	return umlDiagrams;
    });
    
    define('extensions/toc',[
        // "jquery",
        "underscore",
        "utils",
        "classes/Extension",
        // "ext!html/tocSettingsBlock.html",
    ], function(_, utils, Extension) {
    
        var toc = new Extension("toc", "Table of Contents", true);
        // toc.settingsBlock = tocSettingsBlockHTML;
        toc.defaultConfig = {
            marker: "\\[(TOC|toc)\\]",
            maxDepth: 6,
            button: true,
        };
    
        // toc.onLoadSettings = function() {
        //     utils.setInputValue("#input-toc-marker", toc.config.marker);
        //     utils.setInputValue("#input-toc-maxdepth", toc.config.maxDepth);
        //     utils.setInputChecked("#input-toc-button", toc.config.button);
        // };
    
        // toc.onSaveSettings = function(newConfig, event) {
        //     newConfig.marker = utils.getInputRegExpValue("#input-toc-marker", event);
        //     newConfig.maxDepth = utils.getInputIntValue("#input-toc-maxdepth");
        //     newConfig.button = utils.getInputChecked("#input-toc-button");
        // };
    
        /*
        toc.onCreatePreviewButton = function() {
            if(toc.config.button) {
                return buttonTocHTML;
            }
        };
        */
    
        // TOC element description
        function TocElement(tagName, anchor, text) {
            this.tagName = tagName;
            this.anchor = anchor;
            this.text = text;
            this.children = [];
        }
        TocElement.prototype.childrenToString = function() {
            if(this.children.length === 0) {
                return "";
            }
            var result = "<ul>\n";
            _.each(this.children, function(child) {
                result += child.toString();
            });
            result += "</ul>\n";
            return result;
        };
        TocElement.prototype.toString = function() {
            var result = "<li>";
            if(this.anchor && this.text) {
                result += '<a href="#' + this.anchor + '">' + this.text + '</a>';
            }
            result += this.childrenToString() + "</li>\n";
            return result;
        };
    
        // Transform flat list of TocElement into a tree
        function groupTags(array, level) {
            level = level || 1;
            var tagName = "H" + level;
            var result = [];
    
            var currentElement;
            function pushCurrentElement() {
                if(currentElement !== undefined) {
                    if(currentElement.children.length > 0) {
                        currentElement.children = groupTags(currentElement.children, level + 1);
                    }
                    result.push(currentElement);
                }
            }
    
            _.each(array, function(element) {
                if(element.tagName != tagName) {
                    if(level !== toc.config.maxDepth) {
                        if(currentElement === undefined) {
                            currentElement = new TocElement();
                        }
                        currentElement.children.push(element);
                    }
                }
                else {
                    pushCurrentElement();
                    currentElement = element;
                }
            });
            pushCurrentElement();
            return result;
        }
    
        // Build the TOC
        var previewContentsElt;
        function buildToc() {
            var anchorList = {};
            function createAnchor(element) {
                var id = element.id || utils.slugify(element.textContent) || 'title';
                var anchor = id;
                var index = 0;
                while (_.has(anchorList, anchor)) {
                    anchor = id + "-" + (++index);
                }
                anchorList[anchor] = true;
                // Update the id of the element
                element.id = anchor;
                return anchor;
            }
    
            var elementList = [];
            _.each(previewContentsElt.querySelectorAll('h1, h2, h3, h4, h5, h6'), function(elt) {
                elementList.push(new TocElement(elt.tagName, createAnchor(elt), elt.textContent));
            });
            elementList = groupTags(elementList);
            return '<div class="toc">\n<ul>\n' + elementList.join("") + '</ul>\n</div>\n';
        }
    
        toc.onPagedownConfigure = function(editor) {
            previewContentsElt = document.getElementById('preview-contents');
            var tocExp = new RegExp("^\\s*" + toc.config.marker + "\\s*$");
            // Run TOC generation when conversion is finished directly on HTML
            editor.hooks.chain("onPreviewRefresh", function() {
                var tocEltList = document.querySelectorAll('.table-of-contents, .toc');
                var htmlToc = buildToc();
                // Replace toc paragraphs
                _.each(previewContentsElt.getElementsByTagName('p'), function(elt) {
                    if(tocExp.test(elt.innerHTML)) {
                        elt.innerHTML = htmlToc;
                    }
                });
                // Add toc in the TOC button
                _.each(tocEltList, function(elt) {
                    elt.innerHTML = htmlToc;
                });
    
                $("#leanoteNavContentMd").height("auto"); // auto
                try {
                    if(!$(htmlToc).text()) {
                        $("#leanoteNavContentMd").html("&nbsp; &nbsp; Nothing...");
                    }
                } catch(e) {}
                // 这里, resize Height
                var curH = $("#leanoteNavContentMd").height();
                var pH = $("#mdEditor").height()-100;
                if(curH > pH) {
                    $("#leanoteNavContentMd").height(pH);
                }
            });
        };
    
        toc.onReady = function() {
            var isPreviewVisible = true;
            $(".preview-panel").on('hide.layout.toggle', function() {
                isPreviewVisible = false;
            }).on('shown.layout.toggle', function() {
                isPreviewVisible = true;
            });
            $('.extension-preview-buttons .table-of-contents').on('click', 'a', function(evt) {
                !isPreviewVisible && evt.preventDefault();
            });
        };
    
        return toc;
    });
    
    define('extensions/emailConverter',[
        "classes/Extension",
    ], function(Extension) {
    
        var emailConverter = new Extension("emailConverter", "Markdown Email", true);
        // emailConverter.settingsBlock = '<p>Converts email addresses in the form &lt;email@example.com&gt; into clickable links.</p>';
    
        emailConverter.onPagedownConfigure = function(editor) {
            editor.getConverter().hooks.chain("postConversion", function(text) {
                return text.replace(/<(mailto\:)?([^\s>]+@[^\s>]+\.\S+?)>/g, function(match, mailto, email) {
                    return '<a href="mailto:' + email + '">' + email + '</a>';
                });
            });
        };
    
        return emailConverter;
    });
    
    define('extensions/scrollSync',[
    	// "jquery",
    	"underscore",
    	"classes/Extension"
    	// "ext!html/scrollSyncSettingsBlock.html"
    ], function(_, Extension) {
    
    	var scrollSync = new Extension("scrollSync", "Scroll Sync", true, true);
    	// scrollSync.settingsBlock = scrollSyncSettingsBlockHTML;
    
    	var sectionList;
    	scrollSync.onSectionsCreated = function(sectionListParam) {
    		sectionList = sectionListParam;
    	};
    
    	var editorElt;
    	var previewElt;
    	var mdSectionList = [];
    	var htmlSectionList = [];
    	var lastEditorScrollTop;
    	var lastPreviewScrollTop;
    
    	// 创建sections
    	// mdSectionList 和 htmlSectionList
    	// 两者一一对应, 知道各自offsetTop
    	var buildSections = _.debounce(function() {
    		// 编辑区
    		mdSectionList = [];
    		var mdSectionOffset;
    		var scrollHeight;
    
    		_.each(editorElt.querySelectorAll(".wmd-input-section"), function(delimiterElt) {
    			if(mdSectionOffset === undefined) {
    				// Force start to 0 for the first section
    				mdSectionOffset = 0;
    				return;
    			}
    			// life
    			// 因为如果左侧是纯文本编辑, delimiterElt.firstChild就是文本
    			if (delimiterElt.firstChild && delimiterElt.firstChild.nodeName != '#text') {
    				delimiterElt = delimiterElt.firstChild;
    			}
    
    			// Consider div scroll position
    			var newSectionOffset = delimiterElt.offsetTop;
    			mdSectionList.push({
    				startOffset: mdSectionOffset,
    				endOffset: newSectionOffset,
    				height: newSectionOffset - mdSectionOffset
    			});
    			mdSectionOffset = newSectionOffset;
    		});
    
    		// Last section
    		scrollHeight = editorElt.scrollHeight;
    		mdSectionList.push({
    			startOffset: mdSectionOffset,
    			endOffset: scrollHeight,
    			height: scrollHeight - mdSectionOffset
    		});
    
    		// 预览区相对应
    		// Find corresponding sections in the preview
    		htmlSectionList = [];
    		var htmlSectionOffset;
    		_.each(previewElt.querySelectorAll(".wmd-preview-section"), function(delimiterElt) {
    			if(htmlSectionOffset === undefined) {
    				// Force start to 0 for the first section
    				htmlSectionOffset = 0;
    				return;
    			}
    			// Consider div scroll position
    			var newSectionOffset = delimiterElt.offsetTop;
    			htmlSectionList.push({
    				startOffset: htmlSectionOffset,
    				endOffset: newSectionOffset,
    				height: newSectionOffset - htmlSectionOffset
    			});
    			htmlSectionOffset = newSectionOffset;
    		});
    		// Last section
    		scrollHeight = previewElt.scrollHeight;
    		htmlSectionList.push({
    			startOffset: htmlSectionOffset,
    			endOffset: scrollHeight,
    			height: scrollHeight - htmlSectionOffset
    		});
    
    		// apply Scroll Link (-10 to have a gap > 9px)
    		lastEditorScrollTop = -10;
    		lastPreviewScrollTop = -10;
    		doScrollSync();
    	}, 500);
    
    	var isPreviewVisible = true;
    	var isScrollEditor = false;
    	var isScrollPreview = false;
    	var isEditorMoving = false;
    	var isPreviewMoving = false;
    
    	function getDestScrollTop(srcScrollTop, srcSectionList, destSectionList) {
    		// Find the section corresponding to the offset
    		var sectionIndex;
    		var srcSection = _.find(srcSectionList, function(section, index) {
    			sectionIndex = index;
    			return srcScrollTop < section.endOffset;
    		});
    		if(srcSection === undefined) {
    			// Something very bad happened
    			return;
    		}
    		var posInSection = (srcScrollTop - srcSection.startOffset) / (srcSection.height || 1);
    		var destSection = destSectionList[sectionIndex];
    		return destSection.startOffset + destSection.height * posInSection;
    	}
    
    	var timeoutId;
    	var currentEndCb;
    
    	function animate(elt, startValue, endValue, stepCb, endCb) {
    		if(currentEndCb) {
    			clearTimeout(timeoutId);
    			currentEndCb();
    		}
    		currentEndCb = endCb;
    		var diff = endValue - startValue;
    		var startTime = Date.now();
    
    		function tick() {
    			var currentTime = Date.now();
    			var progress = (currentTime - startTime) / 200;
    			if(progress < 1) {
    				var scrollTop = startValue + diff * Math.cos((1 - progress) * Math.PI / 2);
    				elt.scrollTop = scrollTop;
    				stepCb(scrollTop);
    				timeoutId = setTimeout(tick, 1);
    			}
    			else {
    				currentEndCb = undefined;
    				elt.scrollTop = endValue;
    				setTimeout(endCb, 100);
    			}
    		}
    
    		tick();
    	}
    
    	// 同步预览
    	var doScrollSync = _.throttle(function() {
    		if(!isPreviewVisible 
    			|| mdSectionList.length === 0 
    			|| mdSectionList.length !== htmlSectionList.length) {
    			return;
    		}
    
    		var editorScrollTop = editorElt.scrollTop;
    		editorScrollTop < 0 && (editorScrollTop = 0);
    		var previewScrollTop = previewElt.scrollTop;
    		var destScrollTop;
    		// Perform the animation if diff > 9px
    		if(isScrollEditor === true) {
    			if(Math.abs(editorScrollTop - lastEditorScrollTop) <= 9) {
    				return;
    			}
    			isScrollEditor = false;
    			// Animate the preview
    			lastEditorScrollTop = editorScrollTop;
    			destScrollTop = getDestScrollTop(editorScrollTop, mdSectionList, htmlSectionList);
    			destScrollTop = _.min([
    				destScrollTop,
    				previewElt.scrollHeight - previewElt.offsetHeight
    			]);
    			if(Math.abs(destScrollTop - previewScrollTop) <= 9) {
    				// Skip the animation if diff is <= 9
    				lastPreviewScrollTop = previewScrollTop;
    				return;
    			}
    			animate(previewElt, previewScrollTop, destScrollTop, function(currentScrollTop) {
    				isPreviewMoving = true;
    				lastPreviewScrollTop = currentScrollTop;
    			}, function() {
    				isPreviewMoving = false;
    			});
    		}
    		else if(isScrollPreview === true) {
    			if(Math.abs(previewScrollTop - lastPreviewScrollTop) <= 9) {
    				return;
    			}
    			isScrollPreview = false;
    			// Animate the editor
    			lastPreviewScrollTop = previewScrollTop;
    			destScrollTop = getDestScrollTop(previewScrollTop, htmlSectionList, mdSectionList);
    			destScrollTop = _.min([
    				destScrollTop,
    				editorElt.scrollHeight - editorElt.offsetHeight
    			]);
    			if(Math.abs(destScrollTop - editorScrollTop) <= 9) {
    				// Skip the animation if diff is <= 9
    				lastEditorScrollTop = editorScrollTop;
    				return;
    			}
    			animate(editorElt, editorScrollTop, destScrollTop, function(currentScrollTop) {
    				isEditorMoving = true;
    				lastEditorScrollTop = currentScrollTop;
    			}, function() {
    				isEditorMoving = false;
    			});
    		}
    	}, 100);
    
    	scrollSync.onLayoutResize = function() {
    		isScrollEditor = true;
    		buildSections();
    	};
    
    	scrollSync.onFileClosed = function() {
    		mdSectionList = [];
    	};
    
    	var scrollAdjust = false;
    	scrollSync.onReady = function() {
    		previewElt = document.querySelector(".preview-container");
    		editorElt = document.querySelector("#wmd-input");
    		$(previewElt).scroll(function() {
    			if(isPreviewMoving === false && scrollAdjust === false) {
    				isScrollPreview = true;
    				isScrollEditor = false;
    				doScrollSync();
    			}
    			scrollAdjust = false;
    		});
    		$(editorElt).scroll(function() {
    			if(isEditorMoving === false) {
    				isScrollEditor = true;
    				isScrollPreview = false;
    				doScrollSync();
    			}
    		});
    
    		$(".preview-panel").on('hide.layout.toggle', function() {
    			isPreviewVisible = false;
    		}).on('shown.layout.toggle', function() {
    			isPreviewVisible = true;
    		});
    
    		// Reimplement anchor scrolling to work without preview
    		$('.extension-preview-buttons .table-of-contents').on('click', 'a', function(evt) {
    			evt.preventDefault();
    			var id = this.hash;
    			var anchorElt = $(id);
    			if(!anchorElt.length) {
    				return;
    			}
    			var previewScrollTop = anchorElt[0].getBoundingClientRect().top - previewElt.getBoundingClientRect().top + previewElt.scrollTop;
    			previewElt.scrollTop = previewScrollTop;
    			var editorScrollTop = getDestScrollTop(previewScrollTop, htmlSectionList, mdSectionList);
    			editorElt.scrollTop = editorScrollTop;
    		});
    	};
    
    	var previewContentsElt;
    	var previousHeight;
    	scrollSync.onPagedownConfigure = function(editor) {
    		previewContentsElt = document.getElementById("preview-contents");
    		editor.getConverter().hooks.chain("postConversion", function(text) {
    			// To avoid losing scrolling position before elements are fully loaded
    			previousHeight = previewContentsElt.offsetHeight;
    			previewContentsElt.style.height = previousHeight + 'px';
    			return text;
    		});
    	};
    
    	scrollSync.onPreviewFinished = function() {
    		// Now set the correct height
    		previewContentsElt.style.removeProperty('height');
    		var newHeight = previewContentsElt.offsetHeight;
    		isScrollEditor = true;
    		if(newHeight < previousHeight) {
    			// We expect a scroll adjustment
    			scrollAdjust = true;
    		}
    		buildSections();
    	};
    
    	return scrollSync;
    });
    
    // 什么原因会停止scroll或定位不准, 有resize的时候, offset都变了, 但是scrollSync插件里面的没变, 所以需要buildSection
    // 什么时候buildSections ?
    // scrollSync.onPreviewFinished, scrollSync.onLayoutResize
    ;
    define('extensions/shortcuts',[
    	// "jquery",
    	"underscore",
    	"utils",
    	"mousetrap",
    	"classes/Extension"
    ], function(_, utils, mousetrap, Extension, shortcutsDefaultMapping) {
    	var shortcuts = new Extension("shortcuts", "Shortcuts", true, true);
    	shortcuts.defaultConfig = {
    	};
    
    	var eventMgr;
    	var pagedownEditor;
    	shortcuts.onEventMgrCreated = function(eventMgrParameter) {
    		eventMgr = eventMgrParameter;
    		eventMgr.addListener('onPagedownConfigure', function(pagedownEditorParam) {
    			pagedownEditor = pagedownEditorParam;
    		});
    	};
    
    	/*jshint unused:false */
    	function bindPagedownButton(buttonName) {
    		return function(evt) {
    			pagedownEditor.uiManager.doClick(pagedownEditor.uiManager.buttons[buttonName]);
    			evt.preventDefault();
    		};
    	}
    
    	function expand(text, replacement) {
    		utils.defer(function() {
    			require('editor').replacePreviousText(text, replacement);
    		});
    	}
    
    	/*
        'mod+b': bindPagedownButton('bold'),
        'mod+i': bindPagedownButton('italic'),
        'mod+l': bindPagedownButton('link'),
        'mod+q': bindPagedownButton('quote'),
        'mod+k': bindPagedownButton('code'),
        'mod+g': bindPagedownButton('image'),
        'mod+o': bindPagedownButton('olist'),
        'mod+u': bindPagedownButton('ulist'),
        'mod+h': bindPagedownButton('heading'),
        'mod+r': bindPagedownButton('hr'),
        'mod+z': bindPagedownButton('undo'),
        'mod+y': bindPagedownButton('redo'),
        'mod+shift+z': bindPagedownButton('redo'),
        'mod+m': function(evt) {
            $('.button-open-discussion').click();
            evt.preventDefault();
        },
        '= = > space': function() {
            expand('==> ', '⇒ ');
        },
        '< = = space': function() {
            expand('<== ', '⇐ ');
        },
        'S t a c k E d i t': function() {
            eventMgr.onMessage("You are stunned!!! Aren't you?");
        }
    	*/
    	/*jshint unused:true */
    	shortcuts.onInit = function() {
    		try {
    			// http://craig.is/killing/mice
    			// 只在输入框下有效, life
    			// http://stackoverflow.com/questions/16169645/using-mousetrap-on-a-specific-element
    			var input = $('.editor-content');
    			mousetrap.stopCallback = function(e, element, combo) {
    				return element !== input[0];
    			};
    
    			var shortcutMap;
                var shortcutMap = {
                        'mod+b': bindPagedownButton('bold'),
                        'mod+i': bindPagedownButton('italic'),
                        'mod+l': bindPagedownButton('link'),
                        'mod+q': bindPagedownButton('quote'),
                        'mod+k': bindPagedownButton('code'),
                        'mod+g': bindPagedownButton('image'),
                        'mod+o': bindPagedownButton('olist'),
                        'mod+u': bindPagedownButton('ulist'),
                        'mod+h': bindPagedownButton('heading'),
                        'mod+r': bindPagedownButton('hr'),
                        'mod+z': bindPagedownButton('undo'),
                        'mod+y': bindPagedownButton('redo'),
                        'mod+shift+z': bindPagedownButton('redo'),
                        'mod+m': function(evt) {
                            $('.button-open-discussion').click();
                            evt.preventDefault();
                        },
                        '= = > space': function() {
                            expand('==> ', '⇒ ');
                        },
                        '< = = space': function() {
                            expand('<== ', '⇐ ');
                        },
                        'S t a c k E d i t': function() {
                            eventMgr.onMessage("You are stunned!!! Aren't you?");
                        }
                    };
    			_.each(shortcutMap, function(func, shortcut) {
    				mousetrap.bind(shortcut, func);
    			});
    		}
    		catch(e) {
    			console.error(e);
    		}
    	};
    
    	shortcuts.onReady = function() {
    	};
    
    	return shortcuts;
    });
    
    define('extensions/findReplace',[
    	// "jquery",
    	"underscore",
    	"crel",
    	"utils",
    	"classes/Extension",
    	"mousetrap",
    	"rangy",
    	// "ext!html/findReplace.html",
    	// "ext!html/findReplaceSettingsBlock.html"
    ], function(_, crel, utils, Extension, mousetrap, rangy) {
    
    	var findReplaceHTML = '<button type="button" class="close button-find-replace-dismiss">×</button>\n<div class="form-inline">\n    <div class="form-group">\n        <label for="input-find-replace-search-for">Search for</label>\n        <input class="form-control" id="input-find-replace-search-for" placeholder="Search for">\n    </div>\n    <div class="form-group">\n        <label for="input-find-replace-replace-with">Replace with</label>\n        <input class="form-control" id="input-find-replace-replace-with" placeholder="Replace with">\n    </div>\n</div>\n<div class="pull-right">\n    <div class="help-block text-right">\n        <span class="found-counter">0</span> found\n    </div>\n    <div>\n        <button type="button" class="btn btn-primary search-button">Search</button>\n        <button type="button" class="btn btn-default replace-button">Replace</button>\n        <button type="button" class="btn btn-default replace-all-button">All</button>\n    </div>\n</div>\n<div class="pull-left">\n    <div class="checkbox">\n        <label>\n            <input type="checkbox" class="checkbox-case-sensitive"> Case sensitive\n        </label>\n    </div>\n    <div class="checkbox">\n        <label>\n            <input type="checkbox" class="checkbox-regexp"> Regular expression\n        </label>\n    </div>\n</div>\n';
    
    	var findReplace = new Extension("findReplace", 'Find and Replace', true, true);
    	// findReplace.settingsBlock = findReplaceSettingsBlockHTML;
    	findReplace.defaultConfig = {
    		findReplaceShortcut: 'mod+f'
    	};
    
    	// findReplace.onLoadSettings = function() {
    	// 	utils.setInputValue("#input-find-replace-shortcut", findReplace.config.findReplaceShortcut);
    	// };
    
    	// findReplace.onSaveSettings = function(newConfig, event) {
    	// 	newConfig.findReplaceShortcut = utils.getInputTextValue("#input-find-replace-shortcut", event);
    	// };
    
    	var editor;
    	findReplace.onEditorCreated = function(editorParam) {
    		editor = editorParam;
    	};
    
    	var eventMgr;
    	findReplace.onEventMgrCreated = function(eventMgrParam) {
    		eventMgr = eventMgrParam;
    	};
    
    	var rangeList = [];
    	var offsetList = [];
    	var highlightCssApplier, selectCssApplier;
    	var selectRange;
    	function resetHighlight() {
    		resetSelect();
    		rangeList.forEach(function(rangyRange) {
    			try {
    				highlightCssApplier.undoToRange(rangyRange);
    			}
    			catch(e) {
    			}
    		});
    		rangeList = [];
    	}
    
    	function resetSelect() {
    		if(selectRange) {
    			try {
    				selectRange && selectCssApplier.undoToRange(selectRange);
    			}
    			catch(e) {}
    			selectRange = undefined;
    		}
    	}
    
    	var contentElt;
    	var $findReplaceElt, $searchForInputElt, $replaceWithInputElt;
    	var foundCounterElt, $caseSensitiveElt, $regexpElt;
    
    	var previousText = '';
    	var previousCaseSensitive = false;
    	var previousUseRegexp = false;
    	var shown = false;
    	var regex;
    
    	function highlight(force) {
    		if(!shown) {
    			return;
    		}
    		var text = $searchForInputElt.val();
    		var caseSensitive = $caseSensitiveElt.prop('checked');
    		var useRegexp = $regexpElt.prop('checked');
    		if(!force && text == previousText && caseSensitive == previousCaseSensitive && useRegexp == previousUseRegexp) {
    			return;
    		}
    		previousText = text;
    		previousCaseSensitive = caseSensitive;
    		previousUseRegexp = useRegexp;
    
    		resetHighlight();
    		var lastOffset = {};
    		var lastRange;
    
    		function adjustOffset(offset) {
    			if(offset.container === lastOffset.container) {
    				// adjust the offset after rangy has modified the text node
    				return {
    					container: lastRange.endContainer.parentElement.nextSibling,
    					offsetInContainer: offset.offsetInContainer - lastOffset.offsetInContainer,
    					offset: offset.offset
    				};
    			}
    			return offset;
    		}
    
    		offsetList = [];
    		var found = 0;
    		var textLength = text.length;
    		if(textLength) {
    			try {
    				var flags = caseSensitive ? 'gm' : 'gmi';
    				text = useRegexp ? text : text.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
    				regex = new RegExp(text, flags);
    				editor.getValue().replace(regex, function(match, offset) {
    					offsetList.push({
    						start: offset,
    						end: offset + match.length
    					});
    				});
    				found = offsetList.length;
    				// Highly CPU consuming, so add a limit
    				if(offsetList.length < 200) {
    					var rangeOffsets = [];
    					offsetList.forEach(function(offset) {
    						rangeOffsets.push(offset.start);
    						rangeOffsets.push(offset.end);
    					});
    					rangeOffsets = editor.selectionMgr.findOffsets(rangeOffsets);
    					for(var i = 0; i < rangeOffsets.length; i += 2) {
    						var offsetStart = rangeOffsets[i];
    						var offsetEnd = rangeOffsets[i + 1];
    						var adjustedOffsetStart = adjustOffset(offsetStart);
    						var adjustedOffsetEnd = adjustOffset(offsetEnd);
    						var rangyRange = rangy.createRange();
    						rangyRange.setStart(adjustedOffsetStart.container, adjustedOffsetStart.offsetInContainer);
    						rangyRange.setEnd(adjustedOffsetEnd.container, adjustedOffsetEnd.offsetInContainer);
    						lastOffset = offsetEnd;
    						lastRange = rangyRange;
    						highlightCssApplier.applyToRange(rangyRange);
    						rangeList[offsetStart.offset] = rangyRange;
    					}
    					editor.selectionMgr.hasFocus && editor.selectionMgr.updateSelectionRange();
    				}
    			}
    			catch(e) {
    			}
    		}
    		foundCounterElt.innerHTML = found;
    	}
    
    	function show() {
    		eventMgr.onEditorPopover();
    		shown = true;
    		$findReplaceElt.show();
    		$searchForInputElt.focus()[0].setSelectionRange(0, $searchForInputElt.val().length);
    		editor.selectionMgr.adjustTop = 50;
    		editor.selectionMgr.adjustBottom = 220;
    		highlight(true);
    	}
    
    	function hide() {
    		shown = false;
    		$findReplaceElt.hide();
    		resetHighlight();
    		editor.selectionMgr.adjustTop = 0;
    		editor.selectionMgr.adjustBottom = 0;
    		editor.focus();
    	}
    
    	findReplace.onEditorPopover = function() {
    		hide();
    	};
    
    	function find() {
    		resetSelect();
    		var position = Math.min(editor.selectionMgr.selectionStart, editor.selectionMgr.selectionEnd);
    		var offset = _.find(offsetList, function(offset) {
    			return offset.start > position;
    		});
    		if(!offset) {
    			offset = _.first(offsetList);
    		}
    		if(!offset) {
    			return;
    		}
    		selectRange = rangeList[offset.start];
    		if(!selectRange) {
    			var range = editor.selectionMgr.createRange(offset.start, offset.end);
    			selectRange = rangy.createRange();
    			selectRange.setStart(range.startContainer, range.startOffset);
    			selectRange.setEnd(range.endContainer, range.endOffset);
    		}
    		selectCssApplier.applyToRange(selectRange);
    		selectRange.start = offset.start;
    		selectRange.end = offset.end;
    		editor.selectionMgr.setSelectionStartEnd(offset.start, offset.end);
    		editor.selectionMgr.updateCursorCoordinates(true);
    	}
    
    	function replace() {
    		if(!selectRange) {
    			return find();
    		}
    		var replacement = $replaceWithInputElt.val();
    		editor.replace(selectRange.start, selectRange.end, replacement);
    		setTimeout(function() {
    			find();
    			$replaceWithInputElt.focus();
    		}, 1);
    	}
    
    	function replaceAll() {
    		var replacement = $replaceWithInputElt.val();
    		editor.replaceAll(regex, replacement);
    	}
    
    	findReplace.onContentChanged = _.bind(highlight, null, true);
    	findReplace.onFileOpen = _.bind(highlight, null, true);
    
    	findReplace.onReady = function() {
    		highlightCssApplier = rangy.createCssClassApplier('find-replace-highlight', {
    			normalize: false
    		});
    		selectCssApplier = rangy.createCssClassApplier('find-replace-select', {
    			normalize: false
    		});
    		contentElt = document.querySelector('#wmd-input .editor-content');
    
    		var elt = crel('div', {
    			class: 'find-replace'
    		});
    		$findReplaceElt = $(elt).hide();
    		elt.innerHTML = findReplaceHTML;
    		document.querySelector('.layout-wrapper-l2').appendChild(elt);
    		$('.button-find-replace-dismiss').click(function() {
    			hide();
    		});
    		foundCounterElt = elt.querySelector('.found-counter');
    		$caseSensitiveElt = $findReplaceElt.find('.checkbox-case-sensitive').change(_.bind(highlight, null, false));
    		$regexpElt = $findReplaceElt.find('.checkbox-regexp').change(_.bind(highlight, null, false));
    		$findReplaceElt.find('.search-button').click(find);
    		$searchForInputElt = $('#input-find-replace-search-for').keyup(_.bind(highlight, null, false));
    		$findReplaceElt.find('.replace-button').click(replace);
    		$replaceWithInputElt = $('#input-find-replace-replace-with');
    		$findReplaceElt.find('.replace-all-button').click(replaceAll);
    
    		// Key bindings
    		$().add($searchForInputElt).add($replaceWithInputElt).keydown(function(evt) {
    			if(evt.which === 13) {
    				// Enter key
    				evt.preventDefault();
    				find();
    			}
    		});
    
    		mousetrap.bind(findReplace.config.findReplaceShortcut, function(e) {
    			var newSearch = editor.selectionMgr.getSelectedText();
    			if(newSearch) {
    				$searchForInputElt.val(newSearch);
    			}
    			show();
    			e.preventDefault();
    		});
    	};
    
    	return findReplace;
    });
    
    define('extensions/htmlSanitizer',[
    	// "jquery",
    	"underscore",
    	"utils",
    	"logger",
    	"classes/Extension",
    	// "ext!html/htmlSanitizerSettingsBlock.html"
    ], function(_, utils, logger, Extension) {
    
    	var htmlSanitizer = new Extension("htmlSanitizer", "HTML Sanitizer", true);
    	// htmlSanitizer.settingsBlock = htmlSanitizerSettingsBlockHTML;
    
    	var buf;
    	htmlSanitizer.onPagedownConfigure = function(editor) {
    		var converter = editor.getConverter();
    		converter.hooks.chain("postConversion", function(html) {
    			buf = [];
    			html.split('<div class="se-preview-section-delimiter"></div>').forEach(function(sectionHtml) {
    				htmlParser(sectionHtml, htmlSanitizeWriter(buf, function(uri, isImage) {
    					return !/^unsafe/.test(sanitizeUri(uri, isImage));
    				}));
    				buf.push('<div class="se-preview-section-delimiter"></div>');
    			});
    			return buf.slice(0, -1).join('');
    		});
    	};
    
    	/**
    	 * @license AngularJS v1.2.16
    	 * (c) 2010-2014 Google, Inc. http://angularjs.org
    	 * License: MIT
    	 */
    
    	var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/,
    		imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|data:image\//;
    
    	function sanitizeUri(uri, isImage) {
    		var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
    		var normalizedVal;
    		normalizedVal = utils.urlResolve(uri).href;
    		if(normalizedVal !== '' && !normalizedVal.match(regex)) {
    			return 'unsafe:' + normalizedVal;
    		}
    	}
    
    	// Regular Expressions for parsing tags and attributes
    	var START_TAG_REGEXP =
    			/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,
    		END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/,
    		ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
    		BEGIN_TAG_REGEXP = /^</,
    		BEGING_END_TAGE_REGEXP = /^<\s*\//,
    		COMMENT_REGEXP = /<!--(.*?)-->/g,
    		DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i,
    		CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
    		// Match everything outside of normal chars and " (quote character)
    		NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
    
    	function makeMap(str) {
    		var obj = {}, items = str.split(','), i;
    		for(i = 0; i < items.length; i++) {
    			obj[items[i]] = true;
    		}
    		return obj;
    	}
    
    	// Good source of info about elements and attributes
    	// http://dev.w3.org/html5/spec/Overview.html#semantics
    	// http://simon.html5.org/html-elements
    
    	// Safe Void Elements - HTML5
    	// http://dev.w3.org/html5/spec/Overview.html#void-elements
    	var voidElements = makeMap("area,br,col,hr,img,wbr");
    
    	// Elements that you can, intentionally, leave open (and which close themselves)
    	// http://dev.w3.org/html5/spec/Overview.html#optional-tags
    	var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
    		optionalEndTagInlineElements = makeMap("rp,rt"),
    		optionalEndTagElements = _.extend({},
    			optionalEndTagInlineElements,
    			optionalEndTagBlockElements);
    
    	// Safe Block Elements - HTML5
    	var blockElements = _.extend({}, optionalEndTagBlockElements, makeMap("address,article," +
    		"aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," +
    		"h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));
    
    	// Inline Elements - HTML5
    	var inlineElements = _.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," +
    		"bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," +
    		"samp,small,span,strike,strong,sub,sup,time,tt,u,var"));
    
    
    	// Special Elements (can contain anything)
    	var specialElements = makeMap("script,style");
    
    	// benweet: Add iframe
    	blockElements.iframe = true;
    
    	var validElements = _.extend({},
    		voidElements,
    		blockElements,
    		inlineElements,
    		optionalEndTagElements);
    
    	//Attributes that have href and hence need to be sanitized
    	var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap");
    	var validAttrs = _.extend({}, uriAttrs, makeMap(
    			'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
    			'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' +
    			'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' +
    			'scope,scrolling,shape,size,span,start,summary,target,title,type,' +
    			'valign,value,vspace,width'));
    
    	// benweet: Add id and allowfullscreen (YouTube iframe)
    	validAttrs.id = true;
    	validAttrs.allowfullscreen = true;
    
    	/*
    	 * HTML Parser By Misko Hevery (misko@hevery.com)
    	 * based on:  HTML Parser By John Resig (ejohn.org)
    	 * Original code by Erik Arvidsson, Mozilla Public License
    	 * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
    	 *
    	 * // Use like so:
    	 * htmlParser(htmlString, {
    	 *     start: function(tag, attrs, unary) {},
    	 *     end: function(tag) {},
    	 *     chars: function(text) {},
    	 *     comment: function(text) {}
    	 * });
    	 *
    	 */
    	/* jshint -W083 */
    	function htmlParser(html, handler) {
    		var index, chars, match, stack = [], last = html;
    		stack.last = function() {
    			return stack[ stack.length - 1 ];
    		};
    
    		function parseStartTag(tag, tagName, rest, unary) {
    			tagName = tagName && tagName.toLowerCase();
    			if(blockElements[ tagName ]) {
    				while(stack.last() && inlineElements[ stack.last() ]) {
    					parseEndTag("", stack.last());
    				}
    			}
    
    			if(optionalEndTagElements[ tagName ] && stack.last() == tagName) {
    				parseEndTag("", tagName);
    			}
    
    			unary = voidElements[ tagName ] || !!unary;
    
    			if(!unary) {
    				stack.push(tagName);
    			}
    
    			var attrs = {};
    
    			rest.replace(ATTR_REGEXP,
    				function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {
    					var value = doubleQuotedValue ||
    						singleQuotedValue ||
    						unquotedValue ||
    						'';
    
    					attrs[name] = decodeEntities(value);
    				});
    			if(handler.start) {
    				handler.start(tagName, attrs, unary);
    			}
    		}
    
    		function parseEndTag(tag, tagName) {
    			var pos = 0, i;
    			tagName = tagName && tagName.toLowerCase();
    			if(tagName) {
    				// Find the closest opened tag of the same type
    				for(pos = stack.length - 1; pos >= 0; pos--) {
    					if(stack[ pos ] == tagName) {
    						break;
    					}
    				}
    			}
    
    			if(pos >= 0) {
    				// Close all the open elements, up the stack
    				for(i = stack.length - 1; i >= pos; i--) {
    					if(handler.end) {
    						handler.end(stack[ i ]);
    					}
    				}
    
    				// Remove the open elements from the stack
    				stack.length = pos;
    			}
    		}
    
    		while(html) {
    			chars = true;
    
    			// Make sure we're not in a script or style element
    			if(!stack.last() || !specialElements[ stack.last() ]) {
    
    				// Comment
    				if(html.indexOf("<!--") === 0) {
    					// comments containing -- are not allowed unless they terminate the comment
    					index = html.indexOf("--", 4);
    
    					if(index >= 0 && html.lastIndexOf("-->", index) === index) {
    						if(handler.comment) {
    							handler.comment(html.substring(4, index));
    						}
    						html = html.substring(index + 3);
    						chars = false;
    					}
    					// DOCTYPE
    				} else if(DOCTYPE_REGEXP.test(html)) {
    					match = html.match(DOCTYPE_REGEXP);
    
    					if(match) {
    						html = html.replace(match[0], '');
    						chars = false;
    					}
    					// end tag
    				} else if(BEGING_END_TAGE_REGEXP.test(html)) {
    					match = html.match(END_TAG_REGEXP);
    
    					if(match) {
    						html = html.substring(match[0].length);
    						match[0].replace(END_TAG_REGEXP, parseEndTag);
    						chars = false;
    					}
    
    					// start tag
    				} else if(BEGIN_TAG_REGEXP.test(html)) {
    					match = html.match(START_TAG_REGEXP);
    
    					if(match) {
    						html = html.substring(match[0].length);
    						match[0].replace(START_TAG_REGEXP, parseStartTag);
    						chars = false;
    					}
    				}
    
    				if(chars) {
    					index = html.indexOf("<");
    
    					var text = index < 0 ? html : html.substring(0, index);
    					html = index < 0 ? "" : html.substring(index);
    
    					if(handler.chars) {
    						handler.chars(decodeEntities(text));
    					}
    				}
    
    			} else {
    				html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
    					function(all, text) {
    						text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1");
    
    						if(handler.chars) {
    							handler.chars(decodeEntities(text));
    						}
    
    						return "";
    					});
    
    				parseEndTag("", stack.last());
    			}
    
    			if(html == last) {
    				//throw new Error("The sanitizer was unable to parse the following block of html: " + html);
    				stack.reverse();
    				return stack.forEach(function(tag) {
    					buf.push('</');
    					buf.push(tag);
    					buf.push('>');
    				});
    			}
    			last = html;
    		}
    
    		// Clean up any remaining tags
    		parseEndTag();
    	}
    
    	var hiddenPre = document.createElement("pre");
    	var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
    
    	/**
    	 * decodes all entities into regular string
    	 * @param value
    	 * @returns {string} A string with decoded entities.
    	 */
    	function decodeEntities(value) {
    		if(!value) {
    			return '';
    		}
    
    		// Note: IE8 does not preserve spaces at the start/end of innerHTML
    		// so we must capture them and reattach them afterward
    		var parts = spaceRe.exec(value);
    		var spaceBefore = parts[1];
    		var spaceAfter = parts[3];
    		var content = parts[2];
    		if(content) {
    			hiddenPre.innerHTML = content.replace(/</g, "&lt;");
    			// innerText depends on styling as it doesn't display hidden elements.
    			// Therefore, it's better to use textContent not to cause unnecessary
    			// reflows. However, IE<9 don't support textContent so the innerText
    			// fallback is necessary.
    			content = 'textContent' in hiddenPre ?
    				hiddenPre.textContent : hiddenPre.innerText;
    		}
    		return spaceBefore + content + spaceAfter;
    	}
    
    	/**
    	 * Escapes all potentially dangerous characters, so that the
    	 * resulting string can be safely inserted into attribute or
    	 * element text.
    	 * @param value
    	 * @returns {string} escaped text
    	 */
    	function encodeEntities(value) {
    		return value.
    			replace(/&/g, '&amp;').
    			replace(NON_ALPHANUMERIC_REGEXP, function(value) {
    				return '&#' + value.charCodeAt(0) + ';';
    			}).
    			replace(/</g, '&lt;').
    			replace(/>/g, '&gt;');
    	}
    
    
    	/**
    	 * create an HTML/XML writer which writes to buffer
    	 * @param {Array} buf use buf.jain('') to get out sanitized html string
    	 * @returns {object} in the form of {
    	 *     start: function(tag, attrs, unary) {},
    	 *     end: function(tag) {},
    	 *     chars: function(text) {},
    	 *     comment: function(text) {}
    	 * }
    	 */
    	function htmlSanitizeWriter(buf, uriValidator) {
    		var ignore = false;
    		var out = _.bind(buf.push, buf);
    		return {
    			start: function(tag, attrs, unary) {
    				tag = tag && tag.toLowerCase();
    				if(!ignore && specialElements[tag]) {
    					ignore = tag;
    				}
    				if(!ignore && validElements[tag] === true) {
    					out('<');
    					out(tag);
    					_.forEach(attrs, function(value, key) {
    						var lkey = key && key.toLowerCase();
    						var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
    						if(validAttrs[lkey] === true &&
    							(uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
    							out(' ');
    							out(key);
    							out('="');
    							out(encodeEntities(value));
    							out('"');
    						}
    					});
    					out(unary ? '/>' : '>');
    				}
    			},
    			end: function(tag) {
    				tag = tag && tag.toLowerCase();
    				if(!ignore && validElements[tag] === true) {
    					out('</');
    					out(tag);
    					out('>');
    				}
    				if(tag == ignore) {
    					ignore = false;
    				}
    			},
    			chars: function(chars) {
    				if(!ignore) {
    					out(encodeEntities(chars));
    				}
    			},
    			comment: function(comment) {
    				if(!ignore) {
    					out('<!--');
    					out(encodeEntities(comment));
    					out('-->');
    				}
    			}
    		};
    	}
    
    	return htmlSanitizer;
    });
    /*
     * waitForImages 1.4.2
     * -------------------
     * Provides a callback when all images have loaded in your given selector.
     * https://github.com/alexanderdickson/waitForImages
     *
     * Copyright (c) 2013 Alex Dickson
     * Licensed under the MIT license.
     */
    (function ($) {
        // Namespace all events.
        var eventNamespace = 'waitForImages';
    
        // CSS properties which contain references to images.
        $.waitForImages = {
            hasImageProperties: ['backgroundImage', 'listStyleImage', 'borderImage', 'borderCornerImage']
        };
    
        // Custom selector to find `img` elements that have a valid `src` attribute and have not already loaded.
        $.expr[':'].uncached = function (obj) {
            // Ensure we are dealing with an `img` element with a valid `src` attribute.
            if (!$(obj).is('img[src!=""]')) {
                return false;
            }
    
            // Firefox's `complete` property will always be `true` even if the image has not been downloaded.
            // Doing it this way works in Firefox.
            var img = new Image();
            img.src = obj.src;
            return !img.complete;
        };
    
        $.fn.waitForImages = function (finishedCallback, eachCallback, waitForAll) {
    
            var allImgsLength = 0;
            var allImgsLoaded = 0;
    
            // Handle options object.
            if ($.isPlainObject(arguments[0])) {
                waitForAll = arguments[0].waitForAll;
                eachCallback = arguments[0].each;
    			// This must be last as arguments[0]
    			// is aliased with finishedCallback.
                finishedCallback = arguments[0].finished;
            }
    
            // Handle missing callbacks.
            finishedCallback = finishedCallback || $.noop;
            eachCallback = eachCallback || $.noop;
    
            // Convert waitForAll to Boolean
            waitForAll = !! waitForAll;
    
            // Ensure callbacks are functions.
            if (!$.isFunction(finishedCallback) || !$.isFunction(eachCallback)) {
                throw new TypeError('An invalid callback was supplied.');
            }
    
            return this.each(function () {
                // Build a list of all imgs, dependent on what images will be considered.
                var obj = $(this);
                var allImgs = [];
                // CSS properties which may contain an image.
                var hasImgProperties = $.waitForImages.hasImageProperties || [];
                // To match `url()` references.
                // Spec: http://www.w3.org/TR/CSS2/syndata.html#value-def-uri
                var matchUrl = /url\(\s*(['"]?)(.*?)\1\s*\)/g;
    
                if (waitForAll) {
    
                    // Get all elements (including the original), as any one of them could have a background image.
                    obj.find('*').andSelf().each(function () {
                        var element = $(this);
    
                        // If an `img` element, add it. But keep iterating in case it has a background image too.
                        if (element.is('img:uncached')) {
                            allImgs.push({
                                src: element.attr('src'),
                                element: element[0]
                            });
                        }
    
                        $.each(hasImgProperties, function (i, property) {
                            var propertyValue = element.css(property);
                            var match;
    
                            // If it doesn't contain this property, skip.
                            if (!propertyValue) {
                                return true;
                            }
    
                            // Get all url() of this element.
                            while (match = matchUrl.exec(propertyValue)) {
                                allImgs.push({
                                    src: match[2],
                                    element: element[0]
                                });
                            }
                        });
                    });
                } else {
                    // For images only, the task is simpler.
                    obj.find('img:uncached')
                        .each(function () {
                        allImgs.push({
                            src: this.src,
                            element: this
                        });
                    });
                }
    
                allImgsLength = allImgs.length;
                allImgsLoaded = 0;
    
                // If no images found, don't bother.
                if (allImgsLength === 0) {
                    finishedCallback.call(obj[0]);
                }
    
                $.each(allImgs, function (i, img) {
    
                    var image = new Image();
    
                    // Handle the image loading and error with the same callback.
                    $(image).bind('load.' + eventNamespace + ' error.' + eventNamespace, function (event) {
                        allImgsLoaded++;
    
                        // If an error occurred with loading the image, set the third argument accordingly.
                        eachCallback.call(img.element, allImgsLoaded, allImgsLength, event.type == 'load');
    
                        if (allImgsLoaded == allImgsLength) {
                            finishedCallback.call(obj[0]);
                            return false;
                        }
    
                    });
    
                    image.src = img.src;
                });
            });
        };
    }(jQuery));
    
    define("jquery-waitforimages", function(){});
    
    define('eventMgr',[
    	// "jquery",
    	"underscore",
    	"crel",
    	"mousetrap",
    	"utils",
    	"logger",
    	"classes/Extension",
    	"settings",
    	// "extensions/yamlFrontMatterParser",
    	"extensions/markdownSectionParser",
    	"extensions/partialRendering",
    	// "extensions/buttonMarkdownSyntax",
    	// "extensions/googleAnalytics",
    	// "extensions/twitter",
    	// "extensions/dialogAbout",
    	// "extensions/dialogManagePublication",
    	// "extensions/dialogManageSynchronization",
    	// "extensions/dialogManageSharing",
    	// "extensions/dialogOpenHarddrive",
    	// "extensions/documentTitle",
    	// "extensions/documentSelector",
    	// "extensions/documentPanel",
    	// "extensions/documentManager",
    	// "extensions/workingIndicator",
    	// "extensions/notifications",
    	"extensions/umlDiagrams",
    	"extensions/markdownExtra",
    	"extensions/toc",
    	"extensions/mathJax",
    	"extensions/emailConverter",
    	"extensions/scrollSync",
    	// "extensions/buttonSync",
    	// "extensions/buttonPublish",
    	// "extensions/buttonStat",
    	// "extensions/buttonHtmlCode",
    	// "extensions/buttonViewer",
    	// "extensions/welcomeTour",
    	"extensions/shortcuts",
    	// "extensions/userCustom",
    	// "extensions/comments",
    	"extensions/findReplace",
    	"extensions/htmlSanitizer",
    	// "bootstrap",
    	"jquery-waitforimages"
    ], function( _, crel, mousetrap, utils, logger, Extension, settings) {
    
    	var eventMgr = {};
    
    	// Create a list of extensions from module arguments
    	var extensionList = _.chain(arguments).map(function(argument) {
    		return argument instanceof Extension && argument;
    	}).compact().value();
    
    	// Configure extensions
    	var extensionSettings = settings.extensionSettings || {};
    	_.each(extensionList, function(extension) {
    		// Set the extension.config attribute from settings or default
    		// configuration
    		extension.config = _.extend({}, extension.defaultConfig, extensionSettings[extension.extensionId]);
    		if(window.viewerMode === true && extension.disableInViewer === true) {
    			// Skip enabling the extension if we are in the viewer and extension
    			// doesn't support it
    			extension.enabled = false;
    		}
    		else {
    			// Enable the extension if it's not optional or it has not been
    			// disabled by the user
    			extension.enabled = !extension.isOptional || extension.config.enabled === undefined || extension.config.enabled === true;
    		}
    	});
    
    	// Returns all listeners with the specified name that are implemented in the
    	// enabled extensions
    	function getExtensionListenerList(eventName) {
    		return _.chain(extensionList).map(function(extension) {
    			return extension.enabled && extension[eventName];
    		}).compact().value();
    	}
    
    	// Returns a function that calls every listeners with the specified name
    	// from all enabled extensions
    	var eventListenerListMap = {};
    
    	function createEventHook(eventName) {
    		eventListenerListMap[eventName] = getExtensionListenerList(eventName);
    		return function() {
    			logger.log(eventName, arguments);
    			var eventArguments = arguments;
    			_.each(eventListenerListMap[eventName], function(listener) {
    				// Use try/catch in case userCustom listener contains error
    				try {
    					listener.apply(null, eventArguments);
    				}
    				catch(e) {
    					console.error(_.isObject(e) ? e.stack : e);
    				}
    			});
    		};
    	}
    
    	// Declare an event Hook in the eventMgr that we can fire using eventMgr.eventName()
    	function addEventHook(eventName) {
    		eventMgr[eventName] = createEventHook(eventName);
    	}
    
    	// Used by external modules (not extensions) to listen to events
    	eventMgr.addListener = function(eventName, listener) {
    		try {
    			eventListenerListMap[eventName].push(listener);
    		}
    		catch(e) {
    			console.error('No event listener called ' + eventName);
    		}
    	};
    
    	// Call every onInit listeners (enabled extensions only)
    	createEventHook("onInit")();
    
    	// Load/Save extension config from/to settings
    	eventMgr.onLoadSettings = function() {
    		logger.log("onLoadSettings");
    		_.each(extensionList, function(extension) {
    			var isChecked = !extension.isOptional || extension.config.enabled === undefined || extension.config.enabled === true;
    			utils.setInputChecked("#input-enable-extension-" + extension.extensionId, isChecked);
    			// Special case for Markdown Extra and MathJax
    			if(extension.extensionId == 'markdownExtra') {
    				utils.setInputChecked("#input-settings-markdown-extra", isChecked);
    			}
    			else if(extension.extensionId == 'mathJax') {
    				utils.setInputChecked("#input-settings-mathjax", isChecked);
    			}
    			var onLoadSettingsListener = extension.onLoadSettings;
    			onLoadSettingsListener && onLoadSettingsListener();
    		});
    	};
    	eventMgr.onSaveSettings = function(newExtensionSettings, event) {
    		logger.log("onSaveSettings");
    		_.each(extensionList, function(extension) {
    			var newExtensionConfig = _.extend({}, extension.defaultConfig);
    			newExtensionConfig.enabled = utils.getInputChecked("#input-enable-extension-" + extension.extensionId);
    			var isChecked;
    			// Special case for Markdown Extra and MathJax
    			if(extension.extensionId == 'markdownExtra') {
    				isChecked = utils.getInputChecked("#input-settings-markdown-extra");
    				if(isChecked != extension.enabled) {
    					newExtensionConfig.enabled = isChecked;
    				}
    			}
    			else if(extension.extensionId == 'mathJax') {
    				isChecked = utils.getInputChecked("#input-settings-mathjax");
    				if(isChecked != extension.enabled) {
    					newExtensionConfig.enabled = isChecked;
    				}
    			}
    			var onSaveSettingsListener = extension.onSaveSettings;
    			onSaveSettingsListener && onSaveSettingsListener(newExtensionConfig, event);
    			newExtensionSettings[extension.extensionId] = newExtensionConfig;
    		});
    	};
    
    	addEventHook("onMessage");
    	addEventHook("onError");
    	addEventHook("onOfflineChanged");
    	addEventHook("onUserActive");
    	addEventHook("onAsyncRunning");
    	addEventHook("onPeriodicRun");
    
    	// To access modules that are loaded after extensions
    	addEventHook("onEditorCreated");
    	addEventHook("onFileMgrCreated");
    	addEventHook("onSynchronizerCreated");
    	addEventHook("onPublisherCreated");
    	addEventHook("onSharingCreated");
    	addEventHook("onEventMgrCreated");
    
    	// Operations on files
    	addEventHook("onFileCreated");
    	addEventHook("onFileDeleted");
    	addEventHook("onFileSelected");
    	addEventHook("onFileOpen");
    	addEventHook("onFileClosed");
    	addEventHook("onContentChanged");
    	addEventHook("onTitleChanged");
    
    	// Operations on folders
    	addEventHook("onFoldersChanged");
    
    	// Sync events
    	addEventHook("onSyncRunning");
    	addEventHook("onSyncSuccess");
    	addEventHook("onSyncImportSuccess");
    	addEventHook("onSyncExportSuccess");
    	addEventHook("onSyncRemoved");
    
    	// Publish events
    	addEventHook("onPublishRunning");
    	addEventHook("onPublishSuccess");
    	addEventHook("onNewPublishSuccess");
    	addEventHook("onPublishRemoved");
    
    	// Operations on Layout
    	addEventHook("onLayoutCreated");
    	addEventHook("onLayoutResize");
    	addEventHook("onExtensionButtonResize");
    
    	// Operations on editor
    	addEventHook("onPagedownConfigure");
    	addEventHook("onSectionsCreated");
    	addEventHook("onCursorCoordinates");
    	addEventHook("onEditorPopover");
    
    	// Operations on comments
    	addEventHook("onDiscussionCreated");
    	addEventHook("onDiscussionRemoved");
    	addEventHook("onCommentsChanged");
    
    	// Refresh twitter buttons
    	addEventHook("onTweet");
    
    
    	// onPreviewFinished 触发 scrollSync
    	var onPreviewFinished = createEventHook("onPreviewFinished");
    	var onAsyncPreviewListenerList = getExtensionListenerList("onAsyncPreview");
    	var previewContentsElt;
    	var $previewContentsElt;
    	eventMgr.onAsyncPreview = function() {
    		logger.log("onAsyncPreview");
    		function recursiveCall(callbackList) {
    			var callback = callbackList.length ? callbackList.shift() : function() {
    				setTimeout(function() {
    					var html = "";
    					_.each(previewContentsElt.children, function(elt) {
    						html += elt.innerHTML;
    					});
    					var htmlWithComments = utils.trim(html);
    					var htmlWithoutComments = htmlWithComments.replace(/ <span class="comment label label-danger">.*?<\/span> /g, '');
    					onPreviewFinished(htmlWithComments, htmlWithoutComments);
    				}, 10);
    			};
    			callback(function() {
    				recursiveCall(callbackList);
    			});
    		}
    
    		// recursiveCall(onAsyncPreviewListenerList);
    		// 加载图片后才会触发onPreviewFinished, 因为offsetTop()会有影响, 所以, 必须要在图片加载完成才触发
    		recursiveCall(onAsyncPreviewListenerList.concat([
    			function(callback) {
    				// We assume some images are loading asynchronously after the preview
    				$previewContentsElt.waitForImages(callback);
    			}
    		]));
    	};
    
    	var onReady = createEventHook("onReady");
    	eventMgr.onReady = function() {
    		previewContentsElt = document.getElementById('preview-contents');
    		$previewContentsElt = $(previewContentsElt);
    
    		// Create a button from an extension listener
    		var createBtn = function(listener) {
    			var buttonGrpElt = crel('div', {
    				class: 'btn-group'
    			});
    			var btnElt = listener();
    			if(_.isString(btnElt)) {
    				buttonGrpElt.innerHTML = btnElt;
    			}
    			else if(_.isElement(btnElt)) {
    				buttonGrpElt.appendChild(btnElt);
    			}
    			return buttonGrpElt;
    		};
    
    		if(window.viewerMode === false) {
    			/*
    			// Create accordion in settings dialog
    			var accordionHtml = _.chain(extensionList).sortBy(function(extension) {
    				return extension.extensionName.toLowerCase();
    			}).reduce(function(html, extension) {
    				return html + (extension.settingsBlock ? _.template(settingsExtensionsAccordionHTML, {
    					extensionId: extension.extensionId,
    					extensionName: extension.extensionName,
    					isOptional: extension.isOptional,
    					settingsBlock: extension.settingsBlock
    				}) : "");
    			}, "").value();
    			document.querySelector('.accordion-extensions').innerHTML = accordionHtml;
    			*/
    
    			// Create extension buttons
    			/*
    			logger.log("onCreateButton");
    			var onCreateButtonListenerList = getExtensionListenerList("onCreateButton");
    			var extensionButtonsFragment = document.createDocumentFragment();
    			_.each(onCreateButtonListenerList, function(listener) {
    				extensionButtonsFragment.appendChild(createBtn(listener));
    			});
    			document.querySelector('.extension-buttons').appendChild(extensionButtonsFragment);
    			*/
    		}
    
    		// Create extension preview buttons
    		logger.log("onCreatePreviewButton");
    		var onCreatePreviewButtonListenerList = getExtensionListenerList("onCreatePreviewButton");
    		var extensionPreviewButtonsFragment = document.createDocumentFragment();
    		_.each(onCreatePreviewButtonListenerList, function(listener) {
    			extensionPreviewButtonsFragment.appendChild(createBtn(listener));
    		});
    		var previewButtonsElt = document.querySelector('.extension-preview-buttons');
    		if(previewButtonsElt) {
    			previewButtonsElt.appendChild(extensionPreviewButtonsFragment);
    		}
    
    		// Shall close every popover
    		mousetrap.bind('escape', function() {
    			eventMgr.onEditorPopover();
    		});
    
    		// Call onReady listeners
    		onReady();
    	};
    
    	// For extensions that need to call other extensions
    	eventMgr.onEventMgrCreated(eventMgr);
    	return eventMgr;
    });
    
    
    /**
     * Prism: Lightweight, robust, elegant syntax highlighting
     * MIT license http://www.opensource.org/licenses/mit-license.php/
     * @author Lea Verou http://lea.verou.me
     */
    
    (function(){
    
    // Private helper vars
    var lang = /\blang(?:uage)?-(?!\*)(\w+)\b/i;
    
    var _ = self.Prism = {
    	util: {
    		type: function (o) { 
    			return Object.prototype.toString.call(o).match(/\[object (\w+)\]/)[1];
    		},
    		
    		// Deep clone a language definition (e.g. to extend it)
    		clone: function (o) {
    			var type = _.util.type(o);
    
    			switch (type) {
    				case 'Object':
    					var clone = {};
    					
    					for (var key in o) {
    						if (o.hasOwnProperty(key)) {
    							clone[key] = _.util.clone(o[key]);
    						}
    					}
    					
    					return clone;
    					
    				case 'Array':
    					return o.slice();
    			}
    			
    			return o;
    		}
    	},
    	
    	languages: {
    		extend: function (id, redef) {
    			var lang = _.util.clone(_.languages[id]);
    			
    			for (var key in redef) {
    				lang[key] = redef[key];
    			}
    			
    			return lang;
    		},
    		
    		// Insert a token before another token in a language literal
    		insertBefore: function (inside, before, insert, root) {
    			root = root || _.languages;
    			var grammar = root[inside];
    			var ret = {};
    				
    			for (var token in grammar) {
    			
    				if (grammar.hasOwnProperty(token)) {
    					
    					if (token == before) {
    					
    						for (var newToken in insert) {
    						
    							if (insert.hasOwnProperty(newToken)) {
    								ret[newToken] = insert[newToken];
    							}
    						}
    					}
    					
    					ret[token] = grammar[token];
    				}
    			}
    			
    			return root[inside] = ret;
    		},
    		
    		// Traverse a language definition with Depth First Search
    		DFS: function(o, callback) {
    			for (var i in o) {
    				callback.call(o, i, o[i]);
    				
    				if (_.util.type(o) === 'Object') {
    					_.languages.DFS(o[i], callback);
    				}
    			}
    		}
    	},
    
    	highlightAll: function(async, callback) {
    		var elements = document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code');
    
    		for (var i=0, element; element = elements[i++];) {
    			_.highlightElement(element, async === true, callback);
    		}
    	},
    		
    	highlightElement: function(element, async, callback) {
    		// Find language
    		var language, grammar, parent = element;
    		
    		while (parent && !lang.test(parent.className)) {
    			parent = parent.parentNode;
    		}
    		
    		if (parent) {
    			language = (parent.className.match(lang) || [,''])[1];
    			grammar = _.languages[language];
    		}
    
    		if (!grammar) {
    			return;
    		}
    		
    		// Set language on the element, if not present
    		element.className = element.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
    		
    		// Set language on the parent, for styling
    		parent = element.parentNode;
    		
    		if (/pre/i.test(parent.nodeName)) {
    			parent.className = parent.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language; 
    		}
    
    		var code = element.textContent;
    		
    		if(!code) {
    			return;
    		}
    		
    		code = code.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/\u00a0/g, ' ');
    		
    		var env = {
    			element: element,
    			language: language,
    			grammar: grammar,
    			code: code
    		};
    		
    		_.hooks.run('before-highlight', env);
    		
    		if (async && self.Worker) {
    			var worker = new Worker(_.filename);	
    			
    			worker.onmessage = function(evt) {
    				env.highlightedCode = Token.stringify(JSON.parse(evt.data), language);
    
    				_.hooks.run('before-insert', env);
    
    				env.element.innerHTML = env.highlightedCode;
    				
    				callback && callback.call(env.element);
    				_.hooks.run('after-highlight', env);
    			};
    			
    			worker.postMessage(JSON.stringify({
    				language: env.language,
    				code: env.code
    			}));
    		}
    		else {
    			env.highlightedCode = _.highlight(env.code, env.grammar, env.language)
    
    			_.hooks.run('before-insert', env);
    
    			env.element.innerHTML = env.highlightedCode;
    			
    			callback && callback.call(element);
    			
    			_.hooks.run('after-highlight', env);
    		}
    	},
    	
        // 这里啊, 把一堆好好的文本替换成恶心的东西
    	highlight: function (text, grammar, language) {
    		return Token.stringify(_.tokenize(text, grammar), language);
    	},
    	
    	tokenize: function(text, grammar, language) {
    		var Token = _.Token;
    		
    		var strarr = [text];
    		
    		var rest = grammar.rest;
    		
    		if (rest) {
    			for (var token in rest) {
    				grammar[token] = rest[token];
    			}
    			
    			delete grammar.rest;
    		}
    								
    		tokenloop: for (var token in grammar) {
    			if(!grammar.hasOwnProperty(token) || !grammar[token]) {
    				continue;
    			}
    			
    			var pattern = grammar[token], 
    				inside = pattern.inside,
    				lookbehind = !!pattern.lookbehind,
    				lookbehindLength = 0;
    			
    			pattern = pattern.pattern || pattern;
    			
    			for (var i=0; i<strarr.length; i++) { // Don’t cache length as it changes during the loop
    				
    				var str = strarr[i];
    				
    				if (strarr.length > text.length) {
    					// Something went terribly wrong, ABORT, ABORT!
    					break tokenloop;
    				}
    				
    				if (str instanceof Token) {
    					continue;
    				}
    				
    				pattern.lastIndex = 0;
    				
    				var match = pattern.exec(str);
    				
    				if (match) {
    					if(lookbehind) {
    						lookbehindLength = match[1].length;
    					}
    
    					var from = match.index - 1 + lookbehindLength,
    					    match = match[0].slice(lookbehindLength),
    					    len = match.length,
    					    to = from + len,
    						before = str.slice(0, from + 1),
    						after = str.slice(to + 1); 
    
    					var args = [i, 1];
    					
    					if (before) {
    						args.push(before);
    					}
    					
    					var wrapped = new Token(token, inside? _.tokenize(match, inside) : match);
    					
    					args.push(wrapped);
    					
    					if (after) {
    						args.push(after);
    					}
    					
    					Array.prototype.splice.apply(strarr, args);
    				}
    			}
    		}
    
    		return strarr;
    	},
    	
    	hooks: {
    		all: {},
    		
    		add: function (name, callback) {
    			var hooks = _.hooks.all;
    			
    			hooks[name] = hooks[name] || [];
    			
    			hooks[name].push(callback);
    		},
    		
    		run: function (name, env) {
    			var callbacks = _.hooks.all[name];
    			
    			if (!callbacks || !callbacks.length) {
    				return;
    			}
    			
    			for (var i=0, callback; callback = callbacks[i++];) {
    				callback(env);
    			}
    		}
    	}
    };
    
    var Token = _.Token = function(type, content) {
    	this.type = type;
    	this.content = content;
    };
    
    Token.stringify = function(o, language, parent) {
    	if (typeof o == 'string') {
    		return o;
    	}
    
    	if (Object.prototype.toString.call(o) == '[object Array]') {
    		return o.map(function(element) {
    			return Token.stringify(element, language, o);
    		}).join('');
    	}
    	
    	var env = {
    		type: o.type,
    		content: Token.stringify(o.content, language, parent),
    		tag: 'span',
    		classes: ['token', o.type],
    		attributes: {},
    		language: language,
    		parent: parent
    	};
    	
    	if (env.type == 'comment') {
    		env.attributes['spellcheck'] = 'true';
    	}
    	
    	_.hooks.run('wrap', env);
    	
    	var attributes = '';
    	
    	for (var name in env.attributes) {
    		attributes += name + '="' + (env.attributes[name] || '') + '"';
    	}
    
    	return '<' + env.tag + ' class="' + env.classes.join(' ') + '" ' + attributes + '>' + env.content + '</' + env.tag + '>';
    };
    
    if (!self.document) {
    	// In worker
    	self.addEventListener('message', function(evt) {
    		var message = JSON.parse(evt.data),
    		    lang = message.language,
    		    code = message.code;
    		
    		self.postMessage(JSON.stringify(_.tokenize(code, _.languages[lang])));
    		self.close();
    	}, false);
    	
    	return;
    }
    
    // Get current script and highlight
    var script = document.getElementsByTagName('script');
    
    script = script[script.length - 1];
    
    if (script) {
    	_.filename = script.src;
    	
    	if (document.addEventListener && !script.hasAttribute('data-manual')) {
    		document.addEventListener('DOMContentLoaded', _.highlightAll);
    	}
    }
    
    })();
    define("prism-core", (function (global) {
        return function () {
            var ret, fn;
            return ret || global.Prism;
        };
    }(this)));
    
    /**
     * Diff Match and Patch
     *
     * Copyright 2006 Google Inc.
     * http://code.google.com/p/google-diff-match-patch/
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *   http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    /**
     * @fileoverview Computes the difference between two texts to create a patch.
     * Applies the patch onto another text, allowing for errors.
     * @author fraser@google.com (Neil Fraser)
     */
    
    /**
     * Class containing the diff, match and patch methods.
     * @constructor
     */
    function diff_match_patch() {
    
      // Defaults.
      // Redefine these in your program to override the defaults.
    
      // Number of seconds to map a diff before giving up (0 for infinity).
      this.Diff_Timeout = 1.0;
      // Cost of an empty edit operation in terms of edit characters.
      this.Diff_EditCost = 4;
      // At what point is no match declared (0.0 = perfection, 1.0 = very loose).
      this.Match_Threshold = 0.5;
      // How far to search for a match (0 = exact location, 1000+ = broad match).
      // A match this many characters away from the expected location will add
      // 1.0 to the score (0.0 is a perfect match).
      this.Match_Distance = 1000;
      // When deleting a large block of text (over ~64 characters), how close do
      // the contents have to be to match the expected contents. (0.0 = perfection,
      // 1.0 = very loose).  Note that Match_Threshold controls how closely the
      // end points of a delete need to match.
      this.Patch_DeleteThreshold = 0.5;
      // Chunk size for context length.
      this.Patch_Margin = 4;
    
      // The number of bits in an int.
      this.Match_MaxBits = 32;
    }
    
    
    //  DIFF FUNCTIONS
    
    
    /**
     * The data structure representing a diff is an array of tuples:
     * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
     * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
     */
    var DIFF_DELETE = -1;
    var DIFF_INSERT = 1;
    var DIFF_EQUAL = 0;
    
    /** @typedef {{0: number, 1: string}} */
    diff_match_patch.Diff;
    
    
    /**
     * Find the differences between two texts.  Simplifies the problem by stripping
     * any common prefix or suffix off the texts before diffing.
     * @param {string} text1 Old string to be diffed.
     * @param {string} text2 New string to be diffed.
     * @param {boolean=} opt_checklines Optional speedup flag. If present and false,
     *     then don't run a line-level diff first to identify the changed areas.
     *     Defaults to true, which does a faster, slightly less optimal diff.
     * @param {number} opt_deadline Optional time when the diff should be complete
     *     by.  Used internally for recursive calls.  Users should set DiffTimeout
     *     instead.
     * @return {!Array.<!diff_match_patch.Diff>} Array of diff tuples.
     */
    diff_match_patch.prototype.diff_main = function(text1, text2, opt_checklines,
        opt_deadline) {
      // Set a deadline by which time the diff must be complete.
      if (typeof opt_deadline == 'undefined') {
        if (this.Diff_Timeout <= 0) {
          opt_deadline = Number.MAX_VALUE;
        } else {
          opt_deadline = (new Date).getTime() + this.Diff_Timeout * 1000;
        }
      }
      var deadline = opt_deadline;
    
      // Check for null inputs.
      if (text1 == null || text2 == null) {
        throw new Error('Null input. (diff_main)');
      }
    
      // Check for equality (speedup).
      if (text1 == text2) {
        if (text1) {
          return [[DIFF_EQUAL, text1]];
        }
        return [];
      }
    
      if (typeof opt_checklines == 'undefined') {
        opt_checklines = true;
      }
      var checklines = opt_checklines;
    
      // Trim off common prefix (speedup).
      var commonlength = this.diff_commonPrefix(text1, text2);
      var commonprefix = text1.substring(0, commonlength);
      text1 = text1.substring(commonlength);
      text2 = text2.substring(commonlength);
    
      // Trim off common suffix (speedup).
      commonlength = this.diff_commonSuffix(text1, text2);
      var commonsuffix = text1.substring(text1.length - commonlength);
      text1 = text1.substring(0, text1.length - commonlength);
      text2 = text2.substring(0, text2.length - commonlength);
    
      // Compute the diff on the middle block.
      var diffs = this.diff_compute_(text1, text2, checklines, deadline);
    
      // Restore the prefix and suffix.
      if (commonprefix) {
        diffs.unshift([DIFF_EQUAL, commonprefix]);
      }
      if (commonsuffix) {
        diffs.push([DIFF_EQUAL, commonsuffix]);
      }
      this.diff_cleanupMerge(diffs);
      return diffs;
    };
    
    
    /**
     * Find the differences between two texts.  Assumes that the texts do not
     * have any common prefix or suffix.
     * @param {string} text1 Old string to be diffed.
     * @param {string} text2 New string to be diffed.
     * @param {boolean} checklines Speedup flag.  If false, then don't run a
     *     line-level diff first to identify the changed areas.
     *     If true, then run a faster, slightly less optimal diff.
     * @param {number} deadline Time when the diff should be complete by.
     * @return {!Array.<!diff_match_patch.Diff>} Array of diff tuples.
     * @private
     */
    diff_match_patch.prototype.diff_compute_ = function(text1, text2, checklines,
        deadline) {
      var diffs;
    
      if (!text1) {
        // Just add some text (speedup).
        return [[DIFF_INSERT, text2]];
      }
    
      if (!text2) {
        // Just delete some text (speedup).
        return [[DIFF_DELETE, text1]];
      }
    
      var longtext = text1.length > text2.length ? text1 : text2;
      var shorttext = text1.length > text2.length ? text2 : text1;
      var i = longtext.indexOf(shorttext);
      if (i != -1) {
        // Shorter text is inside the longer text (speedup).
        diffs = [[DIFF_INSERT, longtext.substring(0, i)],
                 [DIFF_EQUAL, shorttext],
                 [DIFF_INSERT, longtext.substring(i + shorttext.length)]];
        // Swap insertions for deletions if diff is reversed.
        if (text1.length > text2.length) {
          diffs[0][0] = diffs[2][0] = DIFF_DELETE;
        }
        return diffs;
      }
    
      if (shorttext.length == 1) {
        // Single character string.
        // After the previous speedup, the character can't be an equality.
        return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]];
      }
    
      // Check to see if the problem can be split in two.
      var hm = this.diff_halfMatch_(text1, text2);
      if (hm) {
        // A half-match was found, sort out the return data.
        var text1_a = hm[0];
        var text1_b = hm[1];
        var text2_a = hm[2];
        var text2_b = hm[3];
        var mid_common = hm[4];
        // Send both pairs off for separate processing.
        var diffs_a = this.diff_main(text1_a, text2_a, checklines, deadline);
        var diffs_b = this.diff_main(text1_b, text2_b, checklines, deadline);
        // Merge the results.
        return diffs_a.concat([[DIFF_EQUAL, mid_common]], diffs_b);
      }
    
      if (checklines && text1.length > 100 && text2.length > 100) {
        return this.diff_lineMode_(text1, text2, deadline);
      }
    
      return this.diff_bisect_(text1, text2, deadline);
    };
    
    
    /**
     * Do a quick line-level diff on both strings, then rediff the parts for
     * greater accuracy.
     * This speedup can produce non-minimal diffs.
     * @param {string} text1 Old string to be diffed.
     * @param {string} text2 New string to be diffed.
     * @param {number} deadline Time when the diff should be complete by.
     * @return {!Array.<!diff_match_patch.Diff>} Array of diff tuples.
     * @private
     */
    diff_match_patch.prototype.diff_lineMode_ = function(text1, text2, deadline) {
      // Scan the text on a line-by-line basis first.
      var a = this.diff_linesToChars_(text1, text2);
      text1 = a.chars1;
      text2 = a.chars2;
      var linearray = a.lineArray;
    
      var diffs = this.diff_main(text1, text2, false, deadline);
    
      // Convert the diff back to original text.
      this.diff_charsToLines_(diffs, linearray);
      // Eliminate freak matches (e.g. blank lines)
      this.diff_cleanupSemantic(diffs);
    
      // Rediff any replacement blocks, this time character-by-character.
      // Add a dummy entry at the end.
      diffs.push([DIFF_EQUAL, '']);
      var pointer = 0;
      var count_delete = 0;
      var count_insert = 0;
      var text_delete = '';
      var text_insert = '';
      while (pointer < diffs.length) {
        switch (diffs[pointer][0]) {
          case DIFF_INSERT:
            count_insert++;
            text_insert += diffs[pointer][1];
            break;
          case DIFF_DELETE:
            count_delete++;
            text_delete += diffs[pointer][1];
            break;
          case DIFF_EQUAL:
            // Upon reaching an equality, check for prior redundancies.
            if (count_delete >= 1 && count_insert >= 1) {
              // Delete the offending records and add the merged ones.
              diffs.splice(pointer - count_delete - count_insert,
                           count_delete + count_insert);
              pointer = pointer - count_delete - count_insert;
              var a = this.diff_main(text_delete, text_insert, false, deadline);
              for (var j = a.length - 1; j >= 0; j--) {
                diffs.splice(pointer, 0, a[j]);
              }
              pointer = pointer + a.length;
            }
            count_insert = 0;
            count_delete = 0;
            text_delete = '';
            text_insert = '';
            break;
        }
        pointer++;
      }
      diffs.pop();  // Remove the dummy entry at the end.
    
      return diffs;
    };
    
    
    /**
     * Find the 'middle snake' of a diff, split the problem in two
     * and return the recursively constructed diff.
     * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
     * @param {string} text1 Old string to be diffed.
     * @param {string} text2 New string to be diffed.
     * @param {number} deadline Time at which to bail if not yet complete.
     * @return {!Array.<!diff_match_patch.Diff>} Array of diff tuples.
     * @private
     */
    diff_match_patch.prototype.diff_bisect_ = function(text1, text2, deadline) {
      // Cache the text lengths to prevent multiple calls.
      var text1_length = text1.length;
      var text2_length = text2.length;
      var max_d = Math.ceil((text1_length + text2_length) / 2);
      var v_offset = max_d;
      var v_length = 2 * max_d;
      var v1 = new Array(v_length);
      var v2 = new Array(v_length);
      // Setting all elements to -1 is faster in Chrome & Firefox than mixing
      // integers and undefined.
      for (var x = 0; x < v_length; x++) {
        v1[x] = -1;
        v2[x] = -1;
      }
      v1[v_offset + 1] = 0;
      v2[v_offset + 1] = 0;
      var delta = text1_length - text2_length;
      // If the total number of characters is odd, then the front path will collide
      // with the reverse path.
      var front = (delta % 2 != 0);
      // Offsets for start and end of k loop.
      // Prevents mapping of space beyond the grid.
      var k1start = 0;
      var k1end = 0;
      var k2start = 0;
      var k2end = 0;
      for (var d = 0; d < max_d; d++) {
        // Bail out if deadline is reached.
        if ((new Date()).getTime() > deadline) {
          break;
        }
    
        // Walk the front path one step.
        for (var k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
          var k1_offset = v_offset + k1;
          var x1;
          if (k1 == -d || (k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1])) {
            x1 = v1[k1_offset + 1];
          } else {
            x1 = v1[k1_offset - 1] + 1;
          }
          var y1 = x1 - k1;
          while (x1 < text1_length && y1 < text2_length &&
                 text1.charAt(x1) == text2.charAt(y1)) {
            x1++;
            y1++;
          }
          v1[k1_offset] = x1;
          if (x1 > text1_length) {
            // Ran off the right of the graph.
            k1end += 2;
          } else if (y1 > text2_length) {
            // Ran off the bottom of the graph.
            k1start += 2;
          } else if (front) {
            var k2_offset = v_offset + delta - k1;
            if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1) {
              // Mirror x2 onto top-left coordinate system.
              var x2 = text1_length - v2[k2_offset];
              if (x1 >= x2) {
                // Overlap detected.
                return this.diff_bisectSplit_(text1, text2, x1, y1, deadline);
              }
            }
          }
        }
    
        // Walk the reverse path one step.
        for (var k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
          var k2_offset = v_offset + k2;
          var x2;
          if (k2 == -d || (k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1])) {
            x2 = v2[k2_offset + 1];
          } else {
            x2 = v2[k2_offset - 1] + 1;
          }
          var y2 = x2 - k2;
          while (x2 < text1_length && y2 < text2_length &&
                 text1.charAt(text1_length - x2 - 1) ==
                 text2.charAt(text2_length - y2 - 1)) {
            x2++;
            y2++;
          }
          v2[k2_offset] = x2;
          if (x2 > text1_length) {
            // Ran off the left of the graph.
            k2end += 2;
          } else if (y2 > text2_length) {
            // Ran off the top of the graph.
            k2start += 2;
          } else if (!front) {
            var k1_offset = v_offset + delta - k2;
            if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1) {
              var x1 = v1[k1_offset];
              var y1 = v_offset + x1 - k1_offset;
              // Mirror x2 onto top-left coordinate system.
              x2 = text1_length - x2;
              if (x1 >= x2) {
                // Overlap detected.
                return this.diff_bisectSplit_(text1, text2, x1, y1, deadline);
              }
            }
          }
        }
      }
      // Diff took too long and hit the deadline or
      // number of diffs equals number of characters, no commonality at all.
      return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]];
    };
    
    
    /**
     * Given the location of the 'middle snake', split the diff in two parts
     * and recurse.
     * @param {string} text1 Old string to be diffed.
     * @param {string} text2 New string to be diffed.
     * @param {number} x Index of split point in text1.
     * @param {number} y Index of split point in text2.
     * @param {number} deadline Time at which to bail if not yet complete.
     * @return {!Array.<!diff_match_patch.Diff>} Array of diff tuples.
     * @private
     */
    diff_match_patch.prototype.diff_bisectSplit_ = function(text1, text2, x, y,
        deadline) {
      var text1a = text1.substring(0, x);
      var text2a = text2.substring(0, y);
      var text1b = text1.substring(x);
      var text2b = text2.substring(y);
    
      // Compute both diffs serially.
      var diffs = this.diff_main(text1a, text2a, false, deadline);
      var diffsb = this.diff_main(text1b, text2b, false, deadline);
    
      return diffs.concat(diffsb);
    };
    
    
    /**
     * Split two texts into an array of strings.  Reduce the texts to a string of
     * hashes where each Unicode character represents one line.
     * @param {string} text1 First string.
     * @param {string} text2 Second string.
     * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
     *     An object containing the encoded text1, the encoded text2 and
     *     the array of unique strings.
     *     The zeroth element of the array of unique strings is intentionally blank.
     * @private
     */
    diff_match_patch.prototype.diff_linesToChars_ = function(text1, text2) {
      var lineArray = [];  // e.g. lineArray[4] == 'Hello\n'
      var lineHash = {};   // e.g. lineHash['Hello\n'] == 4
    
      // '\x00' is a valid character, but various debuggers don't like it.
      // So we'll insert a junk entry to avoid generating a null character.
      lineArray[0] = '';
    
      /**
       * Split a text into an array of strings.  Reduce the texts to a string of
       * hashes where each Unicode character represents one line.
       * Modifies linearray and linehash through being a closure.
       * @param {string} text String to encode.
       * @return {string} Encoded string.
       * @private
       */
      function diff_linesToCharsMunge_(text) {
        var chars = '';
        // Walk the text, pulling out a substring for each line.
        // text.split('\n') would would temporarily double our memory footprint.
        // Modifying text would create many large strings to garbage collect.
        var lineStart = 0;
        var lineEnd = -1;
        // Keeping our own length variable is faster than looking it up.
        var lineArrayLength = lineArray.length;
        while (lineEnd < text.length - 1) {
          lineEnd = text.indexOf('\n', lineStart);
          if (lineEnd == -1) {
            lineEnd = text.length - 1;
          }
          var line = text.substring(lineStart, lineEnd + 1);
          lineStart = lineEnd + 1;
    
          if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) :
              (lineHash[line] !== undefined)) {
            chars += String.fromCharCode(lineHash[line]);
          } else {
            chars += String.fromCharCode(lineArrayLength);
            lineHash[line] = lineArrayLength;
            lineArray[lineArrayLength++] = line;
          }
        }
        return chars;
      }
    
      var chars1 = diff_linesToCharsMunge_(text1);
      var chars2 = diff_linesToCharsMunge_(text2);
      return {chars1: chars1, chars2: chars2, lineArray: lineArray};
    };
    
    
    /**
     * Rehydrate the text in a diff from a string of line hashes to real lines of
     * text.
     * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
     * @param {!Array.<string>} lineArray Array of unique strings.
     * @private
     */
    diff_match_patch.prototype.diff_charsToLines_ = function(diffs, lineArray) {
      for (var x = 0; x < diffs.length; x++) {
        var chars = diffs[x][1];
        var text = [];
        for (var y = 0; y < chars.length; y++) {
          text[y] = lineArray[chars.charCodeAt(y)];
        }
        diffs[x][1] = text.join('');
      }
    };
    
    
    /**
     * Determine the common prefix of two strings.
     * @param {string} text1 First string.
     * @param {string} text2 Second string.
     * @return {number} The number of characters common to the start of each
     *     string.
     */
    diff_match_patch.prototype.diff_commonPrefix = function(text1, text2) {
      // Quick check for common null cases.
      if (!text1 || !text2 || text1.charAt(0) != text2.charAt(0)) {
        return 0;
      }
      // Binary search.
      // Performance analysis: http://neil.fraser.name/news/2007/10/09/
      var pointermin = 0;
      var pointermax = Math.min(text1.length, text2.length);
      var pointermid = pointermax;
      var pointerstart = 0;
      while (pointermin < pointermid) {
        if (text1.substring(pointerstart, pointermid) ==
            text2.substring(pointerstart, pointermid)) {
          pointermin = pointermid;
          pointerstart = pointermin;
        } else {
          pointermax = pointermid;
        }
        pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
      }
      return pointermid;
    };
    
    
    /**
     * Determine the common suffix of two strings.
     * @param {string} text1 First string.
     * @param {string} text2 Second string.
     * @return {number} The number of characters common to the end of each string.
     */
    diff_match_patch.prototype.diff_commonSuffix = function(text1, text2) {
      // Quick check for common null cases.
      if (!text1 || !text2 ||
          text1.charAt(text1.length - 1) != text2.charAt(text2.length - 1)) {
        return 0;
      }
      // Binary search.
      // Performance analysis: http://neil.fraser.name/news/2007/10/09/
      var pointermin = 0;
      var pointermax = Math.min(text1.length, text2.length);
      var pointermid = pointermax;
      var pointerend = 0;
      while (pointermin < pointermid) {
        if (text1.substring(text1.length - pointermid, text1.length - pointerend) ==
            text2.substring(text2.length - pointermid, text2.length - pointerend)) {
          pointermin = pointermid;
          pointerend = pointermin;
        } else {
          pointermax = pointermid;
        }
        pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
      }
      return pointermid;
    };
    
    
    /**
     * Determine if the suffix of one string is the prefix of another.
     * @param {string} text1 First string.
     * @param {string} text2 Second string.
     * @return {number} The number of characters common to the end of the first
     *     string and the start of the second string.
     * @private
     */
    diff_match_patch.prototype.diff_commonOverlap_ = function(text1, text2) {
      // Cache the text lengths to prevent multiple calls.
      var text1_length = text1.length;
      var text2_length = text2.length;
      // Eliminate the null case.
      if (text1_length == 0 || text2_length == 0) {
        return 0;
      }
      // Truncate the longer string.
      if (text1_length > text2_length) {
        text1 = text1.substring(text1_length - text2_length);
      } else if (text1_length < text2_length) {
        text2 = text2.substring(0, text1_length);
      }
      var text_length = Math.min(text1_length, text2_length);
      // Quick check for the worst case.
      if (text1 == text2) {
        return text_length;
      }
    
      // Start by looking for a single character match
      // and increase length until no match is found.
      // Performance analysis: http://neil.fraser.name/news/2010/11/04/
      var best = 0;
      var length = 1;
      while (true) {
        var pattern = text1.substring(text_length - length);
        var found = text2.indexOf(pattern);
        if (found == -1) {
          return best;
        }
        length += found;
        if (found == 0 || text1.substring(text_length - length) ==
            text2.substring(0, length)) {
          best = length;
          length++;
        }
      }
    };
    
    
    /**
     * Do the two texts share a substring which is at least half the length of the
     * longer text?
     * This speedup can produce non-minimal diffs.
     * @param {string} text1 First string.
     * @param {string} text2 Second string.
     * @return {Array.<string>} Five element Array, containing the prefix of
     *     text1, the suffix of text1, the prefix of text2, the suffix of
     *     text2 and the common middle.  Or null if there was no match.
     * @private
     */
    diff_match_patch.prototype.diff_halfMatch_ = function(text1, text2) {
      if (this.Diff_Timeout <= 0) {
        // Don't risk returning a non-optimal diff if we have unlimited time.
        return null;
      }
      var longtext = text1.length > text2.length ? text1 : text2;
      var shorttext = text1.length > text2.length ? text2 : text1;
      if (longtext.length < 4 || shorttext.length * 2 < longtext.length) {
        return null;  // Pointless.
      }
      var dmp = this;  // 'this' becomes 'window' in a closure.
    
      /**
       * Does a substring of shorttext exist within longtext such that the substring
       * is at least half the length of longtext?
       * Closure, but does not reference any external variables.
       * @param {string} longtext Longer string.
       * @param {string} shorttext Shorter string.
       * @param {number} i Start index of quarter length substring within longtext.
       * @return {Array.<string>} Five element Array, containing the prefix of
       *     longtext, the suffix of longtext, the prefix of shorttext, the suffix
       *     of shorttext and the common middle.  Or null if there was no match.
       * @private
       */
      function diff_halfMatchI_(longtext, shorttext, i) {
        // Start with a 1/4 length substring at position i as a seed.
        var seed = longtext.substring(i, i + Math.floor(longtext.length / 4));
        var j = -1;
        var best_common = '';
        var best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b;
        while ((j = shorttext.indexOf(seed, j + 1)) != -1) {
          var prefixLength = dmp.diff_commonPrefix(longtext.substring(i),
                                                   shorttext.substring(j));
          var suffixLength = dmp.diff_commonSuffix(longtext.substring(0, i),
                                                   shorttext.substring(0, j));
          if (best_common.length < suffixLength + prefixLength) {
            best_common = shorttext.substring(j - suffixLength, j) +
                shorttext.substring(j, j + prefixLength);
            best_longtext_a = longtext.substring(0, i - suffixLength);
            best_longtext_b = longtext.substring(i + prefixLength);
            best_shorttext_a = shorttext.substring(0, j - suffixLength);
            best_shorttext_b = shorttext.substring(j + prefixLength);
          }
        }
        if (best_common.length * 2 >= longtext.length) {
          return [best_longtext_a, best_longtext_b,
                  best_shorttext_a, best_shorttext_b, best_common];
        } else {
          return null;
        }
      }
    
      // First check if the second quarter is the seed for a half-match.
      var hm1 = diff_halfMatchI_(longtext, shorttext,
                                 Math.ceil(longtext.length / 4));
      // Check again based on the third quarter.
      var hm2 = diff_halfMatchI_(longtext, shorttext,
                                 Math.ceil(longtext.length / 2));
      var hm;
      if (!hm1 && !hm2) {
        return null;
      } else if (!hm2) {
        hm = hm1;
      } else if (!hm1) {
        hm = hm2;
      } else {
        // Both matched.  Select the longest.
        hm = hm1[4].length > hm2[4].length ? hm1 : hm2;
      }
    
      // A half-match was found, sort out the return data.
      var text1_a, text1_b, text2_a, text2_b;
      if (text1.length > text2.length) {
        text1_a = hm[0];
        text1_b = hm[1];
        text2_a = hm[2];
        text2_b = hm[3];
      } else {
        text2_a = hm[0];
        text2_b = hm[1];
        text1_a = hm[2];
        text1_b = hm[3];
      }
      var mid_common = hm[4];
      return [text1_a, text1_b, text2_a, text2_b, mid_common];
    };
    
    
    /**
     * Reduce the number of edits by eliminating semantically trivial equalities.
     * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
     */
    diff_match_patch.prototype.diff_cleanupSemantic = function(diffs) {
      var changes = false;
      var equalities = [];  // Stack of indices where equalities are found.
      var equalitiesLength = 0;  // Keeping our own length var is faster in JS.
      /** @type {?string} */
      var lastequality = null;
      // Always equal to diffs[equalities[equalitiesLength - 1]][1]
      var pointer = 0;  // Index of current position.
      // Number of characters that changed prior to the equality.
      var length_insertions1 = 0;
      var length_deletions1 = 0;
      // Number of characters that changed after the equality.
      var length_insertions2 = 0;
      var length_deletions2 = 0;
      while (pointer < diffs.length) {
        if (diffs[pointer][0] == DIFF_EQUAL) {  // Equality found.
          equalities[equalitiesLength++] = pointer;
          length_insertions1 = length_insertions2;
          length_deletions1 = length_deletions2;
          length_insertions2 = 0;
          length_deletions2 = 0;
          lastequality = diffs[pointer][1];
        } else {  // An insertion or deletion.
          if (diffs[pointer][0] == DIFF_INSERT) {
            length_insertions2 += diffs[pointer][1].length;
          } else {
            length_deletions2 += diffs[pointer][1].length;
          }
          // Eliminate an equality that is smaller or equal to the edits on both
          // sides of it.
          if (lastequality && (lastequality.length <=
              Math.max(length_insertions1, length_deletions1)) &&
              (lastequality.length <= Math.max(length_insertions2,
                                               length_deletions2))) {
            // Duplicate record.
            diffs.splice(equalities[equalitiesLength - 1], 0,
                         [DIFF_DELETE, lastequality]);
            // Change second copy to insert.
            diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
            // Throw away the equality we just deleted.
            equalitiesLength--;
            // Throw away the previous equality (it needs to be reevaluated).
            equalitiesLength--;
            pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
            length_insertions1 = 0;  // Reset the counters.
            length_deletions1 = 0;
            length_insertions2 = 0;
            length_deletions2 = 0;
            lastequality = null;
            changes = true;
          }
        }
        pointer++;
      }
    
      // Normalize the diff.
      if (changes) {
        this.diff_cleanupMerge(diffs);
      }
      this.diff_cleanupSemanticLossless(diffs);
    
      // Find any overlaps between deletions and insertions.
      // e.g: <del>abcxxx</del><ins>xxxdef</ins>
      //   -> <del>abc</del>xxx<ins>def</ins>
      // e.g: <del>xxxabc</del><ins>defxxx</ins>
      //   -> <ins>def</ins>xxx<del>abc</del>
      // Only extract an overlap if it is as big as the edit ahead or behind it.
      pointer = 1;
      while (pointer < diffs.length) {
        if (diffs[pointer - 1][0] == DIFF_DELETE &&
            diffs[pointer][0] == DIFF_INSERT) {
          var deletion = diffs[pointer - 1][1];
          var insertion = diffs[pointer][1];
          var overlap_length1 = this.diff_commonOverlap_(deletion, insertion);
          var overlap_length2 = this.diff_commonOverlap_(insertion, deletion);
          if (overlap_length1 >= overlap_length2) {
            if (overlap_length1 >= deletion.length / 2 ||
                overlap_length1 >= insertion.length / 2) {
              // Overlap found.  Insert an equality and trim the surrounding edits.
              diffs.splice(pointer, 0,
                  [DIFF_EQUAL, insertion.substring(0, overlap_length1)]);
              diffs[pointer - 1][1] =
                  deletion.substring(0, deletion.length - overlap_length1);
              diffs[pointer + 1][1] = insertion.substring(overlap_length1);
              pointer++;
            }
          } else {
            if (overlap_length2 >= deletion.length / 2 ||
                overlap_length2 >= insertion.length / 2) {
              // Reverse overlap found.
              // Insert an equality and swap and trim the surrounding edits.
              diffs.splice(pointer, 0,
                  [DIFF_EQUAL, deletion.substring(0, overlap_length2)]);
              diffs[pointer - 1][0] = DIFF_INSERT;
              diffs[pointer - 1][1] =
                  insertion.substring(0, insertion.length - overlap_length2);
              diffs[pointer + 1][0] = DIFF_DELETE;
              diffs[pointer + 1][1] =
                  deletion.substring(overlap_length2);
              pointer++;
            }
          }
          pointer++;
        }
        pointer++;
      }
    };
    
    
    /**
     * Look for single edits surrounded on both sides by equalities
     * which can be shifted sideways to align the edit to a word boundary.
     * e.g: The c<ins>at c</ins>ame. -> The <ins>cat </ins>came.
     * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
     */
    diff_match_patch.prototype.diff_cleanupSemanticLossless = function(diffs) {
      /**
       * Given two strings, compute a score representing whether the internal
       * boundary falls on logical boundaries.
       * Scores range from 6 (best) to 0 (worst).
       * Closure, but does not reference any external variables.
       * @param {string} one First string.
       * @param {string} two Second string.
       * @return {number} The score.
       * @private
       */
      function diff_cleanupSemanticScore_(one, two) {
        if (!one || !two) {
          // Edges are the best.
          return 6;
        }
    
        // Each port of this function behaves slightly differently due to
        // subtle differences in each language's definition of things like
        // 'whitespace'.  Since this function's purpose is largely cosmetic,
        // the choice has been made to use each language's native features
        // rather than force total conformity.
        var char1 = one.charAt(one.length - 1);
        var char2 = two.charAt(0);
        var nonAlphaNumeric1 = char1.match(diff_match_patch.nonAlphaNumericRegex_);
        var nonAlphaNumeric2 = char2.match(diff_match_patch.nonAlphaNumericRegex_);
        var whitespace1 = nonAlphaNumeric1 &&
            char1.match(diff_match_patch.whitespaceRegex_);
        var whitespace2 = nonAlphaNumeric2 &&
            char2.match(diff_match_patch.whitespaceRegex_);
        var lineBreak1 = whitespace1 &&
            char1.match(diff_match_patch.linebreakRegex_);
        var lineBreak2 = whitespace2 &&
            char2.match(diff_match_patch.linebreakRegex_);
        var blankLine1 = lineBreak1 &&
            one.match(diff_match_patch.blanklineEndRegex_);
        var blankLine2 = lineBreak2 &&
            two.match(diff_match_patch.blanklineStartRegex_);
    
        if (blankLine1 || blankLine2) {
          // Five points for blank lines.
          return 5;
        } else if (lineBreak1 || lineBreak2) {
          // Four points for line breaks.
          return 4;
        } else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) {
          // Three points for end of sentences.
          return 3;
        } else if (whitespace1 || whitespace2) {
          // Two points for whitespace.
          return 2;
        } else if (nonAlphaNumeric1 || nonAlphaNumeric2) {
          // One point for non-alphanumeric.
          return 1;
        }
        return 0;
      }
    
      var pointer = 1;
      // Intentionally ignore the first and last element (don't need checking).
      while (pointer < diffs.length - 1) {
        if (diffs[pointer - 1][0] == DIFF_EQUAL &&
            diffs[pointer + 1][0] == DIFF_EQUAL) {
          // This is a single edit surrounded by equalities.
          var equality1 = diffs[pointer - 1][1];
          var edit = diffs[pointer][1];
          var equality2 = diffs[pointer + 1][1];
    
          // First, shift the edit as far left as possible.
          var commonOffset = this.diff_commonSuffix(equality1, edit);
          if (commonOffset) {
            var commonString = edit.substring(edit.length - commonOffset);
            equality1 = equality1.substring(0, equality1.length - commonOffset);
            edit = commonString + edit.substring(0, edit.length - commonOffset);
            equality2 = commonString + equality2;
          }
    
          // Second, step character by character right, looking for the best fit.
          var bestEquality1 = equality1;
          var bestEdit = edit;
          var bestEquality2 = equality2;
          var bestScore = diff_cleanupSemanticScore_(equality1, edit) +
              diff_cleanupSemanticScore_(edit, equality2);
          while (edit.charAt(0) === equality2.charAt(0)) {
            equality1 += edit.charAt(0);
            edit = edit.substring(1) + equality2.charAt(0);
            equality2 = equality2.substring(1);
            var score = diff_cleanupSemanticScore_(equality1, edit) +
                diff_cleanupSemanticScore_(edit, equality2);
            // The >= encourages trailing rather than leading whitespace on edits.
            if (score >= bestScore) {
              bestScore = score;
              bestEquality1 = equality1;
              bestEdit = edit;
              bestEquality2 = equality2;
            }
          }
    
          if (diffs[pointer - 1][1] != bestEquality1) {
            // We have an improvement, save it back to the diff.
            if (bestEquality1) {
              diffs[pointer - 1][1] = bestEquality1;
            } else {
              diffs.splice(pointer - 1, 1);
              pointer--;
            }
            diffs[pointer][1] = bestEdit;
            if (bestEquality2) {
              diffs[pointer + 1][1] = bestEquality2;
            } else {
              diffs.splice(pointer + 1, 1);
              pointer--;
            }
          }
        }
        pointer++;
      }
    };
    
    // Define some regex patterns for matching boundaries.
    diff_match_patch.nonAlphaNumericRegex_ = /[^a-zA-Z0-9]/;
    diff_match_patch.whitespaceRegex_ = /\s/;
    diff_match_patch.linebreakRegex_ = /[\r\n]/;
    diff_match_patch.blanklineEndRegex_ = /\n\r?\n$/;
    diff_match_patch.blanklineStartRegex_ = /^\r?\n\r?\n/;
    
    /**
     * Reduce the number of edits by eliminating operationally trivial equalities.
     * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
     */
    diff_match_patch.prototype.diff_cleanupEfficiency = function(diffs) {
      var changes = false;
      var equalities = [];  // Stack of indices where equalities are found.
      var equalitiesLength = 0;  // Keeping our own length var is faster in JS.
      /** @type {?string} */
      var lastequality = null;
      // Always equal to diffs[equalities[equalitiesLength - 1]][1]
      var pointer = 0;  // Index of current position.
      // Is there an insertion operation before the last equality.
      var pre_ins = false;
      // Is there a deletion operation before the last equality.
      var pre_del = false;
      // Is there an insertion operation after the last equality.
      var post_ins = false;
      // Is there a deletion operation after the last equality.
      var post_del = false;
      while (pointer < diffs.length) {
        if (diffs[pointer][0] == DIFF_EQUAL) {  // Equality found.
          if (diffs[pointer][1].length < this.Diff_EditCost &&
              (post_ins || post_del)) {
            // Candidate found.
            equalities[equalitiesLength++] = pointer;
            pre_ins = post_ins;
            pre_del = post_del;
            lastequality = diffs[pointer][1];
          } else {
            // Not a candidate, and can never become one.
            equalitiesLength = 0;
            lastequality = null;
          }
          post_ins = post_del = false;
        } else {  // An insertion or deletion.
          if (diffs[pointer][0] == DIFF_DELETE) {
            post_del = true;
          } else {
            post_ins = true;
          }
          /*
           * Five types to be split:
           * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
           * <ins>A</ins>X<ins>C</ins><del>D</del>
           * <ins>A</ins><del>B</del>X<ins>C</ins>
           * <ins>A</del>X<ins>C</ins><del>D</del>
           * <ins>A</ins><del>B</del>X<del>C</del>
           */
          if (lastequality && ((pre_ins && pre_del && post_ins && post_del) ||
                               ((lastequality.length < this.Diff_EditCost / 2) &&
                                (pre_ins + pre_del + post_ins + post_del) == 3))) {
            // Duplicate record.
            diffs.splice(equalities[equalitiesLength - 1], 0,
                         [DIFF_DELETE, lastequality]);
            // Change second copy to insert.
            diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
            equalitiesLength--;  // Throw away the equality we just deleted;
            lastequality = null;
            if (pre_ins && pre_del) {
              // No changes made which could affect previous entry, keep going.
              post_ins = post_del = true;
              equalitiesLength = 0;
            } else {
              equalitiesLength--;  // Throw away the previous equality.
              pointer = equalitiesLength > 0 ?
                  equalities[equalitiesLength - 1] : -1;
              post_ins = post_del = false;
            }
            changes = true;
          }
        }
        pointer++;
      }
    
      if (changes) {
        this.diff_cleanupMerge(diffs);
      }
    };
    
    
    /**
     * Reorder and merge like edit sections.  Merge equalities.
     * Any edit section can move as long as it doesn't cross an equality.
     * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
     */
    diff_match_patch.prototype.diff_cleanupMerge = function(diffs) {
      diffs.push([DIFF_EQUAL, '']);  // Add a dummy entry at the end.
      var pointer = 0;
      var count_delete = 0;
      var count_insert = 0;
      var text_delete = '';
      var text_insert = '';
      var commonlength;
      while (pointer < diffs.length) {
        switch (diffs[pointer][0]) {
          case DIFF_INSERT:
            count_insert++;
            text_insert += diffs[pointer][1];
            pointer++;
            break;
          case DIFF_DELETE:
            count_delete++;
            text_delete += diffs[pointer][1];
            pointer++;
            break;
          case DIFF_EQUAL:
            // Upon reaching an equality, check for prior redundancies.
            if (count_delete + count_insert > 1) {
              if (count_delete !== 0 && count_insert !== 0) {
                // Factor out any common prefixies.
                commonlength = this.diff_commonPrefix(text_insert, text_delete);
                if (commonlength !== 0) {
                  if ((pointer - count_delete - count_insert) > 0 &&
                      diffs[pointer - count_delete - count_insert - 1][0] ==
                      DIFF_EQUAL) {
                    diffs[pointer - count_delete - count_insert - 1][1] +=
                        text_insert.substring(0, commonlength);
                  } else {
                    diffs.splice(0, 0, [DIFF_EQUAL,
                                        text_insert.substring(0, commonlength)]);
                    pointer++;
                  }
                  text_insert = text_insert.substring(commonlength);
                  text_delete = text_delete.substring(commonlength);
                }
                // Factor out any common suffixies.
                commonlength = this.diff_commonSuffix(text_insert, text_delete);
                if (commonlength !== 0) {
                  diffs[pointer][1] = text_insert.substring(text_insert.length -
                      commonlength) + diffs[pointer][1];
                  text_insert = text_insert.substring(0, text_insert.length -
                      commonlength);
                  text_delete = text_delete.substring(0, text_delete.length -
                      commonlength);
                }
              }
              // Delete the offending records and add the merged ones.
              if (count_delete === 0) {
                diffs.splice(pointer - count_insert,
                    count_delete + count_insert, [DIFF_INSERT, text_insert]);
              } else if (count_insert === 0) {
                diffs.splice(pointer - count_delete,
                    count_delete + count_insert, [DIFF_DELETE, text_delete]);
              } else {
                diffs.splice(pointer - count_delete - count_insert,
                    count_delete + count_insert, [DIFF_DELETE, text_delete],
                    [DIFF_INSERT, text_insert]);
              }
              pointer = pointer - count_delete - count_insert +
                        (count_delete ? 1 : 0) + (count_insert ? 1 : 0) + 1;
            } else if (pointer !== 0 && diffs[pointer - 1][0] == DIFF_EQUAL) {
              // Merge this equality with the previous one.
              diffs[pointer - 1][1] += diffs[pointer][1];
              diffs.splice(pointer, 1);
            } else {
              pointer++;
            }
            count_insert = 0;
            count_delete = 0;
            text_delete = '';
            text_insert = '';
            break;
        }
      }
      if (diffs[diffs.length - 1][1] === '') {
        diffs.pop();  // Remove the dummy entry at the end.
      }
    
      // Second pass: look for single edits surrounded on both sides by equalities
      // which can be shifted sideways to eliminate an equality.
      // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
      var changes = false;
      pointer = 1;
      // Intentionally ignore the first and last element (don't need checking).
      while (pointer < diffs.length - 1) {
        if (diffs[pointer - 1][0] == DIFF_EQUAL &&
            diffs[pointer + 1][0] == DIFF_EQUAL) {
          // This is a single edit surrounded by equalities.
          if (diffs[pointer][1].substring(diffs[pointer][1].length -
              diffs[pointer - 1][1].length) == diffs[pointer - 1][1]) {
            // Shift the edit over the previous equality.
            diffs[pointer][1] = diffs[pointer - 1][1] +
                diffs[pointer][1].substring(0, diffs[pointer][1].length -
                                            diffs[pointer - 1][1].length);
            diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];
            diffs.splice(pointer - 1, 1);
            changes = true;
          } else if (diffs[pointer][1].substring(0, diffs[pointer + 1][1].length) ==
              diffs[pointer + 1][1]) {
            // Shift the edit over the next equality.
            diffs[pointer - 1][1] += diffs[pointer + 1][1];
            diffs[pointer][1] =
                diffs[pointer][1].substring(diffs[pointer + 1][1].length) +
                diffs[pointer + 1][1];
            diffs.splice(pointer + 1, 1);
            changes = true;
          }
        }
        pointer++;
      }
      // If shifts were made, the diff needs reordering and another shift sweep.
      if (changes) {
        this.diff_cleanupMerge(diffs);
      }
    };
    
    
    /**
     * loc is a location in text1, compute and return the equivalent location in
     * text2.
     * e.g. 'The cat' vs 'The big cat', 1->1, 5->8
     * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
     * @param {number} loc Location within text1.
     * @return {number} Location within text2.
     */
    diff_match_patch.prototype.diff_xIndex = function(diffs, loc) {
      var chars1 = 0;
      var chars2 = 0;
      var last_chars1 = 0;
      var last_chars2 = 0;
      var x;
      for (x = 0; x < diffs.length; x++) {
        if (diffs[x][0] !== DIFF_INSERT) {  // Equality or deletion.
          chars1 += diffs[x][1].length;
        }
        if (diffs[x][0] !== DIFF_DELETE) {  // Equality or insertion.
          chars2 += diffs[x][1].length;
        }
        if (chars1 > loc) {  // Overshot the location.
          break;
        }
        last_chars1 = chars1;
        last_chars2 = chars2;
      }
      // Was the location was deleted?
      if (diffs.length != x && diffs[x][0] === DIFF_DELETE) {
        return last_chars2;
      }
      // Add the remaining character length.
      return last_chars2 + (loc - last_chars1);
    };
    
    
    /**
     * Convert a diff array into a pretty HTML report.
     * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
     * @return {string} HTML representation.
     */
    diff_match_patch.prototype.diff_prettyHtml = function(diffs) {
      var html = [];
      var pattern_amp = /&/g;
      var pattern_lt = /</g;
      var pattern_gt = />/g;
      var pattern_para = /\n/g;
      for (var x = 0; x < diffs.length; x++) {
        var op = diffs[x][0];    // Operation (insert, delete, equal)
        var data = diffs[x][1];  // Text of change.
        var text = data.replace(pattern_amp, '&amp;').replace(pattern_lt, '&lt;')
            .replace(pattern_gt, '&gt;').replace(pattern_para, '&para;<br>');
        switch (op) {
          case DIFF_INSERT:
            html[x] = '<ins style="background:#e6ffe6;">' + text + '</ins>';
            break;
          case DIFF_DELETE:
            html[x] = '<del style="background:#ffe6e6;">' + text + '</del>';
            break;
          case DIFF_EQUAL:
            html[x] = '<span>' + text + '</span>';
            break;
        }
      }
      return html.join('');
    };
    
    
    /**
     * Compute and return the source text (all equalities and deletions).
     * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
     * @return {string} Source text.
     */
    diff_match_patch.prototype.diff_text1 = function(diffs) {
      var text = [];
      for (var x = 0; x < diffs.length; x++) {
        if (diffs[x][0] !== DIFF_INSERT) {
          text[x] = diffs[x][1];
        }
      }
      return text.join('');
    };
    
    
    /**
     * Compute and return the destination text (all equalities and insertions).
     * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
     * @return {string} Destination text.
     */
    diff_match_patch.prototype.diff_text2 = function(diffs) {
      var text = [];
      for (var x = 0; x < diffs.length; x++) {
        if (diffs[x][0] !== DIFF_DELETE) {
          text[x] = diffs[x][1];
        }
      }
      return text.join('');
    };
    
    
    /**
     * Compute the Levenshtein distance; the number of inserted, deleted or
     * substituted characters.
     * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
     * @return {number} Number of changes.
     */
    diff_match_patch.prototype.diff_levenshtein = function(diffs) {
      var levenshtein = 0;
      var insertions = 0;
      var deletions = 0;
      for (var x = 0; x < diffs.length; x++) {
        var op = diffs[x][0];
        var data = diffs[x][1];
        switch (op) {
          case DIFF_INSERT:
            insertions += data.length;
            break;
          case DIFF_DELETE:
            deletions += data.length;
            break;
          case DIFF_EQUAL:
            // A deletion and an insertion is one substitution.
            levenshtein += Math.max(insertions, deletions);
            insertions = 0;
            deletions = 0;
            break;
        }
      }
      levenshtein += Math.max(insertions, deletions);
      return levenshtein;
    };
    
    
    /**
     * Crush the diff into an encoded string which describes the operations
     * required to transform text1 into text2.
     * E.g. =3\t-2\t+ing  -> Keep 3 chars, delete 2 chars, insert 'ing'.
     * Operations are tab-separated.  Inserted text is escaped using %xx notation.
     * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
     * @return {string} Delta text.
     */
    diff_match_patch.prototype.diff_toDelta = function(diffs) {
      var text = [];
      for (var x = 0; x < diffs.length; x++) {
        switch (diffs[x][0]) {
          case DIFF_INSERT:
            text[x] = '+' + encodeURI(diffs[x][1]);
            break;
          case DIFF_DELETE:
            text[x] = '-' + diffs[x][1].length;
            break;
          case DIFF_EQUAL:
            text[x] = '=' + diffs[x][1].length;
            break;
        }
      }
      return text.join('\t').replace(/%20/g, ' ');
    };
    
    
    /**
     * Given the original text1, and an encoded string which describes the
     * operations required to transform text1 into text2, compute the full diff.
     * @param {string} text1 Source string for the diff.
     * @param {string} delta Delta text.
     * @return {!Array.<!diff_match_patch.Diff>} Array of diff tuples.
     * @throws {!Error} If invalid input.
     */
    diff_match_patch.prototype.diff_fromDelta = function(text1, delta) {
      var diffs = [];
      var diffsLength = 0;  // Keeping our own length var is faster in JS.
      var pointer = 0;  // Cursor in text1
      var tokens = delta.split(/\t/g);
      for (var x = 0; x < tokens.length; x++) {
        // Each token begins with a one character parameter which specifies the
        // operation of this token (delete, insert, equality).
        var param = tokens[x].substring(1);
        switch (tokens[x].charAt(0)) {
          case '+':
            try {
              diffs[diffsLength++] = [DIFF_INSERT, decodeURI(param)];
            } catch (ex) {
              // Malformed URI sequence.
              throw new Error('Illegal escape in diff_fromDelta: ' + param);
            }
            break;
          case '-':
            // Fall through.
          case '=':
            var n = parseInt(param, 10);
            if (isNaN(n) || n < 0) {
              throw new Error('Invalid number in diff_fromDelta: ' + param);
            }
            var text = text1.substring(pointer, pointer += n);
            if (tokens[x].charAt(0) == '=') {
              diffs[diffsLength++] = [DIFF_EQUAL, text];
            } else {
              diffs[diffsLength++] = [DIFF_DELETE, text];
            }
            break;
          default:
            // Blank tokens are ok (from a trailing \t).
            // Anything else is an error.
            if (tokens[x]) {
              throw new Error('Invalid diff operation in diff_fromDelta: ' +
                              tokens[x]);
            }
        }
      }
      if (pointer != text1.length) {
        throw new Error('Delta length (' + pointer +
            ') does not equal source text length (' + text1.length + ').');
      }
      return diffs;
    };
    
    
    //  MATCH FUNCTIONS
    
    
    /**
     * Locate the best instance of 'pattern' in 'text' near 'loc'.
     * @param {string} text The text to search.
     * @param {string} pattern The pattern to search for.
     * @param {number} loc The location to search around.
     * @return {number} Best match index or -1.
     */
    diff_match_patch.prototype.match_main = function(text, pattern, loc) {
      // Check for null inputs.
      if (text == null || pattern == null || loc == null) {
        throw new Error('Null input. (match_main)');
      }
    
      loc = Math.max(0, Math.min(loc, text.length));
      if (text == pattern) {
        // Shortcut (potentially not guaranteed by the algorithm)
        return 0;
      } else if (!text.length) {
        // Nothing to match.
        return -1;
      } else if (text.substring(loc, loc + pattern.length) == pattern) {
        // Perfect match at the perfect spot!  (Includes case of null pattern)
        return loc;
      } else {
        // Do a fuzzy compare.
        return this.match_bitap_(text, pattern, loc);
      }
    };
    
    
    /**
     * Locate the best instance of 'pattern' in 'text' near 'loc' using the
     * Bitap algorithm.
     * @param {string} text The text to search.
     * @param {string} pattern The pattern to search for.
     * @param {number} loc The location to search around.
     * @return {number} Best match index or -1.
     * @private
     */
    diff_match_patch.prototype.match_bitap_ = function(text, pattern, loc) {
      if (pattern.length > this.Match_MaxBits) {
        throw new Error('Pattern too long for this browser.');
      }
    
      // Initialise the alphabet.
      var s = this.match_alphabet_(pattern);
    
      var dmp = this;  // 'this' becomes 'window' in a closure.
    
      /**
       * Compute and return the score for a match with e errors and x location.
       * Accesses loc and pattern through being a closure.
       * @param {number} e Number of errors in match.
       * @param {number} x Location of match.
       * @return {number} Overall score for match (0.0 = good, 1.0 = bad).
       * @private
       */
      function match_bitapScore_(e, x) {
        var accuracy = e / pattern.length;
        var proximity = Math.abs(loc - x);
        if (!dmp.Match_Distance) {
          // Dodge divide by zero error.
          return proximity ? 1.0 : accuracy;
        }
        return accuracy + (proximity / dmp.Match_Distance);
      }
    
      // Highest score beyond which we give up.
      var score_threshold = this.Match_Threshold;
      // Is there a nearby exact match? (speedup)
      var best_loc = text.indexOf(pattern, loc);
      if (best_loc != -1) {
        score_threshold = Math.min(match_bitapScore_(0, best_loc), score_threshold);
        // What about in the other direction? (speedup)
        best_loc = text.lastIndexOf(pattern, loc + pattern.length);
        if (best_loc != -1) {
          score_threshold =
              Math.min(match_bitapScore_(0, best_loc), score_threshold);
        }
      }
    
      // Initialise the bit arrays.
      var matchmask = 1 << (pattern.length - 1);
      best_loc = -1;
    
      var bin_min, bin_mid;
      var bin_max = pattern.length + text.length;
      var last_rd;
      for (var d = 0; d < pattern.length; d++) {
        // Scan for the best match; each iteration allows for one more error.
        // Run a binary search to determine how far from 'loc' we can stray at this
        // error level.
        bin_min = 0;
        bin_mid = bin_max;
        while (bin_min < bin_mid) {
          if (match_bitapScore_(d, loc + bin_mid) <= score_threshold) {
            bin_min = bin_mid;
          } else {
            bin_max = bin_mid;
          }
          bin_mid = Math.floor((bin_max - bin_min) / 2 + bin_min);
        }
        // Use the result from this iteration as the maximum for the next.
        bin_max = bin_mid;
        var start = Math.max(1, loc - bin_mid + 1);
        var finish = Math.min(loc + bin_mid, text.length) + pattern.length;
    
        var rd = Array(finish + 2);
        rd[finish + 1] = (1 << d) - 1;
        for (var j = finish; j >= start; j--) {
          // The alphabet (s) is a sparse hash, so the following line generates
          // warnings.
          var charMatch = s[text.charAt(j - 1)];
          if (d === 0) {  // First pass: exact match.
            rd[j] = ((rd[j + 1] << 1) | 1) & charMatch;
          } else {  // Subsequent passes: fuzzy match.
            rd[j] = (((rd[j + 1] << 1) | 1) & charMatch) |
                    (((last_rd[j + 1] | last_rd[j]) << 1) | 1) |
                    last_rd[j + 1];
          }
          if (rd[j] & matchmask) {
            var score = match_bitapScore_(d, j - 1);
            // This match will almost certainly be better than any existing match.
            // But check anyway.
            if (score <= score_threshold) {
              // Told you so.
              score_threshold = score;
              best_loc = j - 1;
              if (best_loc > loc) {
                // When passing loc, don't exceed our current distance from loc.
                start = Math.max(1, 2 * loc - best_loc);
              } else {
                // Already passed loc, downhill from here on in.
                break;
              }
            }
          }
        }
        // No hope for a (better) match at greater error levels.
        if (match_bitapScore_(d + 1, loc) > score_threshold) {
          break;
        }
        last_rd = rd;
      }
      return best_loc;
    };
    
    
    /**
     * Initialise the alphabet for the Bitap algorithm.
     * @param {string} pattern The text to encode.
     * @return {!Object} Hash of character locations.
     * @private
     */
    diff_match_patch.prototype.match_alphabet_ = function(pattern) {
      var s = {};
      for (var i = 0; i < pattern.length; i++) {
        s[pattern.charAt(i)] = 0;
      }
      for (var i = 0; i < pattern.length; i++) {
        s[pattern.charAt(i)] |= 1 << (pattern.length - i - 1);
      }
      return s;
    };
    
    
    //  PATCH FUNCTIONS
    
    
    /**
     * Increase the context until it is unique,
     * but don't let the pattern expand beyond Match_MaxBits.
     * @param {!diff_match_patch.patch_obj} patch The patch to grow.
     * @param {string} text Source text.
     * @private
     */
    diff_match_patch.prototype.patch_addContext_ = function(patch, text) {
      if (text.length == 0) {
        return;
      }
      var pattern = text.substring(patch.start2, patch.start2 + patch.length1);
      var padding = 0;
    
      // Look for the first and last matches of pattern in text.  If two different
      // matches are found, increase the pattern length.
      while (text.indexOf(pattern) != text.lastIndexOf(pattern) &&
             pattern.length < this.Match_MaxBits - this.Patch_Margin -
             this.Patch_Margin) {
        padding += this.Patch_Margin;
        pattern = text.substring(patch.start2 - padding,
                                 patch.start2 + patch.length1 + padding);
      }
      // Add one chunk for good luck.
      padding += this.Patch_Margin;
    
      // Add the prefix.
      var prefix = text.substring(patch.start2 - padding, patch.start2);
      if (prefix) {
        patch.diffs.unshift([DIFF_EQUAL, prefix]);
      }
      // Add the suffix.
      var suffix = text.substring(patch.start2 + patch.length1,
                                  patch.start2 + patch.length1 + padding);
      if (suffix) {
        patch.diffs.push([DIFF_EQUAL, suffix]);
      }
    
      // Roll back the start points.
      patch.start1 -= prefix.length;
      patch.start2 -= prefix.length;
      // Extend the lengths.
      patch.length1 += prefix.length + suffix.length;
      patch.length2 += prefix.length + suffix.length;
    };
    
    
    /**
     * Compute a list of patches to turn text1 into text2.
     * Use diffs if provided, otherwise compute it ourselves.
     * There are four ways to call this function, depending on what data is
     * available to the caller:
     * Method 1:
     * a = text1, b = text2
     * Method 2:
     * a = diffs
     * Method 3 (optimal):
     * a = text1, b = diffs
     * Method 4 (deprecated, use method 3):
     * a = text1, b = text2, c = diffs
     *
     * @param {string|!Array.<!diff_match_patch.Diff>} a text1 (methods 1,3,4) or
     * Array of diff tuples for text1 to text2 (method 2).
     * @param {string|!Array.<!diff_match_patch.Diff>} opt_b text2 (methods 1,4) or
     * Array of diff tuples for text1 to text2 (method 3) or undefined (method 2).
     * @param {string|!Array.<!diff_match_patch.Diff>} opt_c Array of diff tuples
     * for text1 to text2 (method 4) or undefined (methods 1,2,3).
     * @return {!Array.<!diff_match_patch.patch_obj>} Array of Patch objects.
     */
    diff_match_patch.prototype.patch_make = function(a, opt_b, opt_c) {
      var text1, diffs;
      if (typeof a == 'string' && typeof opt_b == 'string' &&
          typeof opt_c == 'undefined') {
        // Method 1: text1, text2
        // Compute diffs from text1 and text2.
        text1 = /** @type {string} */(a);
        diffs = this.diff_main(text1, /** @type {string} */(opt_b), true);
        if (diffs.length > 2) {
          this.diff_cleanupSemantic(diffs);
          this.diff_cleanupEfficiency(diffs);
        }
      } else if (a && typeof a == 'object' && typeof opt_b == 'undefined' &&
          typeof opt_c == 'undefined') {
        // Method 2: diffs
        // Compute text1 from diffs.
        diffs = /** @type {!Array.<!diff_match_patch.Diff>} */(a);
        text1 = this.diff_text1(diffs);
      } else if (typeof a == 'string' && opt_b && typeof opt_b == 'object' &&
          typeof opt_c == 'undefined') {
        // Method 3: text1, diffs
        text1 = /** @type {string} */(a);
        diffs = /** @type {!Array.<!diff_match_patch.Diff>} */(opt_b);
      } else if (typeof a == 'string' && typeof opt_b == 'string' &&
          opt_c && typeof opt_c == 'object') {
        // Method 4: text1, text2, diffs
        // text2 is not used.
        text1 = /** @type {string} */(a);
        diffs = /** @type {!Array.<!diff_match_patch.Diff>} */(opt_c);
      } else {
        throw new Error('Unknown call format to patch_make.');
      }
    
      if (diffs.length === 0) {
        return [];  // Get rid of the null case.
      }
      var patches = [];
      var patch = new diff_match_patch.patch_obj();
      var patchDiffLength = 0;  // Keeping our own length var is faster in JS.
      var char_count1 = 0;  // Number of characters into the text1 string.
      var char_count2 = 0;  // Number of characters into the text2 string.
      // Start with text1 (prepatch_text) and apply the diffs until we arrive at
      // text2 (postpatch_text).  We recreate the patches one by one to determine
      // context info.
      var prepatch_text = text1;
      var postpatch_text = text1;
      for (var x = 0; x < diffs.length; x++) {
        var diff_type = diffs[x][0];
        var diff_text = diffs[x][1];
    
        if (!patchDiffLength && diff_type !== DIFF_EQUAL) {
          // A new patch starts here.
          patch.start1 = char_count1;
          patch.start2 = char_count2;
        }
    
        switch (diff_type) {
          case DIFF_INSERT:
            patch.diffs[patchDiffLength++] = diffs[x];
            patch.length2 += diff_text.length;
            postpatch_text = postpatch_text.substring(0, char_count2) + diff_text +
                             postpatch_text.substring(char_count2);
            break;
          case DIFF_DELETE:
            patch.length1 += diff_text.length;
            patch.diffs[patchDiffLength++] = diffs[x];
            postpatch_text = postpatch_text.substring(0, char_count2) +
                             postpatch_text.substring(char_count2 +
                                 diff_text.length);
            break;
          case DIFF_EQUAL:
            if (diff_text.length <= 2 * this.Patch_Margin &&
                patchDiffLength && diffs.length != x + 1) {
              // Small equality inside a patch.
              patch.diffs[patchDiffLength++] = diffs[x];
              patch.length1 += diff_text.length;
              patch.length2 += diff_text.length;
            } else if (diff_text.length >= 2 * this.Patch_Margin) {
              // Time for a new patch.
              if (patchDiffLength) {
                this.patch_addContext_(patch, prepatch_text);
                patches.push(patch);
                patch = new diff_match_patch.patch_obj();
                patchDiffLength = 0;
                // Unlike Unidiff, our patch lists have a rolling context.
                // http://code.google.com/p/google-diff-match-patch/wiki/Unidiff
                // Update prepatch text & pos to reflect the application of the
                // just completed patch.
                prepatch_text = postpatch_text;
                char_count1 = char_count2;
              }
            }
            break;
        }
    
        // Update the current character count.
        if (diff_type !== DIFF_INSERT) {
          char_count1 += diff_text.length;
        }
        if (diff_type !== DIFF_DELETE) {
          char_count2 += diff_text.length;
        }
      }
      // Pick up the leftover patch if not empty.
      if (patchDiffLength) {
        this.patch_addContext_(patch, prepatch_text);
        patches.push(patch);
      }
    
      return patches;
    };
    
    
    /**
     * Given an array of patches, return another array that is identical.
     * @param {!Array.<!diff_match_patch.patch_obj>} patches Array of Patch objects.
     * @return {!Array.<!diff_match_patch.patch_obj>} Array of Patch objects.
     */
    diff_match_patch.prototype.patch_deepCopy = function(patches) {
      // Making deep copies is hard in JavaScript.
      var patchesCopy = [];
      for (var x = 0; x < patches.length; x++) {
        var patch = patches[x];
        var patchCopy = new diff_match_patch.patch_obj();
        patchCopy.diffs = [];
        for (var y = 0; y < patch.diffs.length; y++) {
          patchCopy.diffs[y] = patch.diffs[y].slice();
        }
        patchCopy.start1 = patch.start1;
        patchCopy.start2 = patch.start2;
        patchCopy.length1 = patch.length1;
        patchCopy.length2 = patch.length2;
        patchesCopy[x] = patchCopy;
      }
      return patchesCopy;
    };
    
    
    /**
     * Merge a set of patches onto the text.  Return a patched text, as well
     * as a list of true/false values indicating which patches were applied.
     * @param {!Array.<!diff_match_patch.patch_obj>} patches Array of Patch objects.
     * @param {string} text Old text.
     * @return {!Array.<string|!Array.<boolean>>} Two element Array, containing the
     *      new text and an array of boolean values.
     */
    diff_match_patch.prototype.patch_apply = function(patches, text) {
      if (patches.length == 0) {
        return [text, []];
      }
    
      // Deep copy the patches so that no changes are made to originals.
      patches = this.patch_deepCopy(patches);
    
      var nullPadding = this.patch_addPadding(patches);
      text = nullPadding + text + nullPadding;
    
      this.patch_splitMax(patches);
      // delta keeps track of the offset between the expected and actual location
      // of the previous patch.  If there are patches expected at positions 10 and
      // 20, but the first patch was found at 12, delta is 2 and the second patch
      // has an effective expected position of 22.
      var delta = 0;
      var results = [];
      for (var x = 0; x < patches.length; x++) {
        var expected_loc = patches[x].start2 + delta;
        var text1 = this.diff_text1(patches[x].diffs);
        var start_loc;
        var end_loc = -1;
        if (text1.length > this.Match_MaxBits) {
          // patch_splitMax will only provide an oversized pattern in the case of
          // a monster delete.
          start_loc = this.match_main(text, text1.substring(0, this.Match_MaxBits),
                                      expected_loc);
          if (start_loc != -1) {
            end_loc = this.match_main(text,
                text1.substring(text1.length - this.Match_MaxBits),
                expected_loc + text1.length - this.Match_MaxBits);
            if (end_loc == -1 || start_loc >= end_loc) {
              // Can't find valid trailing context.  Drop this patch.
              start_loc = -1;
            }
          }
        } else {
          start_loc = this.match_main(text, text1, expected_loc);
        }
        if (start_loc == -1) {
          // No match found.  :(
          results[x] = false;
          // Subtract the delta for this failed patch from subsequent patches.
          delta -= patches[x].length2 - patches[x].length1;
        } else {
          // Found a match.  :)
          results[x] = true;
          delta = start_loc - expected_loc;
          var text2;
          if (end_loc == -1) {
            text2 = text.substring(start_loc, start_loc + text1.length);
          } else {
            text2 = text.substring(start_loc, end_loc + this.Match_MaxBits);
          }
          if (text1 == text2) {
            // Perfect match, just shove the replacement text in.
            text = text.substring(0, start_loc) +
                   this.diff_text2(patches[x].diffs) +
                   text.substring(start_loc + text1.length);
          } else {
            // Imperfect match.  Run a diff to get a framework of equivalent
            // indices.
            var diffs = this.diff_main(text1, text2, false);
            if (text1.length > this.Match_MaxBits &&
                this.diff_levenshtein(diffs) / text1.length >
                this.Patch_DeleteThreshold) {
              // The end points match, but the content is unacceptably bad.
              results[x] = false;
            } else {
              this.diff_cleanupSemanticLossless(diffs);
              var index1 = 0;
              var index2;
              for (var y = 0; y < patches[x].diffs.length; y++) {
                var mod = patches[x].diffs[y];
                if (mod[0] !== DIFF_EQUAL) {
                  index2 = this.diff_xIndex(diffs, index1);
                }
                if (mod[0] === DIFF_INSERT) {  // Insertion
                  text = text.substring(0, start_loc + index2) + mod[1] +
                         text.substring(start_loc + index2);
                } else if (mod[0] === DIFF_DELETE) {  // Deletion
                  text = text.substring(0, start_loc + index2) +
                         text.substring(start_loc + this.diff_xIndex(diffs,
                             index1 + mod[1].length));
                }
                if (mod[0] !== DIFF_DELETE) {
                  index1 += mod[1].length;
                }
              }
            }
          }
        }
      }
      // Strip the padding off.
      text = text.substring(nullPadding.length, text.length - nullPadding.length);
      return [text, results];
    };
    
    
    /**
     * Add some padding on text start and end so that edges can match something.
     * Intended to be called only from within patch_apply.
     * @param {!Array.<!diff_match_patch.patch_obj>} patches Array of Patch objects.
     * @return {string} The padding string added to each side.
     */
    diff_match_patch.prototype.patch_addPadding = function(patches) {
      var paddingLength = this.Patch_Margin;
      var nullPadding = '';
      for (var x = 1; x <= paddingLength; x++) {
        nullPadding += String.fromCharCode(x);
      }
    
      // Bump all the patches forward.
      for (var x = 0; x < patches.length; x++) {
        patches[x].start1 += paddingLength;
        patches[x].start2 += paddingLength;
      }
    
      // Add some padding on start of first diff.
      var patch = patches[0];
      var diffs = patch.diffs;
      if (diffs.length == 0 || diffs[0][0] != DIFF_EQUAL) {
        // Add nullPadding equality.
        diffs.unshift([DIFF_EQUAL, nullPadding]);
        patch.start1 -= paddingLength;  // Should be 0.
        patch.start2 -= paddingLength;  // Should be 0.
        patch.length1 += paddingLength;
        patch.length2 += paddingLength;
      } else if (paddingLength > diffs[0][1].length) {
        // Grow first equality.
        var extraLength = paddingLength - diffs[0][1].length;
        diffs[0][1] = nullPadding.substring(diffs[0][1].length) + diffs[0][1];
        patch.start1 -= extraLength;
        patch.start2 -= extraLength;
        patch.length1 += extraLength;
        patch.length2 += extraLength;
      }
    
      // Add some padding on end of last diff.
      patch = patches[patches.length - 1];
      diffs = patch.diffs;
      if (diffs.length == 0 || diffs[diffs.length - 1][0] != DIFF_EQUAL) {
        // Add nullPadding equality.
        diffs.push([DIFF_EQUAL, nullPadding]);
        patch.length1 += paddingLength;
        patch.length2 += paddingLength;
      } else if (paddingLength > diffs[diffs.length - 1][1].length) {
        // Grow last equality.
        var extraLength = paddingLength - diffs[diffs.length - 1][1].length;
        diffs[diffs.length - 1][1] += nullPadding.substring(0, extraLength);
        patch.length1 += extraLength;
        patch.length2 += extraLength;
      }
    
      return nullPadding;
    };
    
    
    /**
     * Look through the patches and break up any which are longer than the maximum
     * limit of the match algorithm.
     * Intended to be called only from within patch_apply.
     * @param {!Array.<!diff_match_patch.patch_obj>} patches Array of Patch objects.
     */
    diff_match_patch.prototype.patch_splitMax = function(patches) {
      var patch_size = this.Match_MaxBits;
      for (var x = 0; x < patches.length; x++) {
        if (patches[x].length1 <= patch_size) {
          continue;
        }
        var bigpatch = patches[x];
        // Remove the big old patch.
        patches.splice(x--, 1);
        var start1 = bigpatch.start1;
        var start2 = bigpatch.start2;
        var precontext = '';
        while (bigpatch.diffs.length !== 0) {
          // Create one of several smaller patches.
          var patch = new diff_match_patch.patch_obj();
          var empty = true;
          patch.start1 = start1 - precontext.length;
          patch.start2 = start2 - precontext.length;
          if (precontext !== '') {
            patch.length1 = patch.length2 = precontext.length;
            patch.diffs.push([DIFF_EQUAL, precontext]);
          }
          while (bigpatch.diffs.length !== 0 &&
                 patch.length1 < patch_size - this.Patch_Margin) {
            var diff_type = bigpatch.diffs[0][0];
            var diff_text = bigpatch.diffs[0][1];
            if (diff_type === DIFF_INSERT) {
              // Insertions are harmless.
              patch.length2 += diff_text.length;
              start2 += diff_text.length;
              patch.diffs.push(bigpatch.diffs.shift());
              empty = false;
            } else if (diff_type === DIFF_DELETE && patch.diffs.length == 1 &&
                       patch.diffs[0][0] == DIFF_EQUAL &&
                       diff_text.length > 2 * patch_size) {
              // This is a large deletion.  Let it pass in one chunk.
              patch.length1 += diff_text.length;
              start1 += diff_text.length;
              empty = false;
              patch.diffs.push([diff_type, diff_text]);
              bigpatch.diffs.shift();
            } else {
              // Deletion or equality.  Only take as much as we can stomach.
              diff_text = diff_text.substring(0,
                  patch_size - patch.length1 - this.Patch_Margin);
              patch.length1 += diff_text.length;
              start1 += diff_text.length;
              if (diff_type === DIFF_EQUAL) {
                patch.length2 += diff_text.length;
                start2 += diff_text.length;
              } else {
                empty = false;
              }
              patch.diffs.push([diff_type, diff_text]);
              if (diff_text == bigpatch.diffs[0][1]) {
                bigpatch.diffs.shift();
              } else {
                bigpatch.diffs[0][1] =
                    bigpatch.diffs[0][1].substring(diff_text.length);
              }
            }
          }
          // Compute the head context for the next patch.
          precontext = this.diff_text2(patch.diffs);
          precontext =
              precontext.substring(precontext.length - this.Patch_Margin);
          // Append the end context for this patch.
          var postcontext = this.diff_text1(bigpatch.diffs)
                                .substring(0, this.Patch_Margin);
          if (postcontext !== '') {
            patch.length1 += postcontext.length;
            patch.length2 += postcontext.length;
            if (patch.diffs.length !== 0 &&
                patch.diffs[patch.diffs.length - 1][0] === DIFF_EQUAL) {
              patch.diffs[patch.diffs.length - 1][1] += postcontext;
            } else {
              patch.diffs.push([DIFF_EQUAL, postcontext]);
            }
          }
          if (!empty) {
            patches.splice(++x, 0, patch);
          }
        }
      }
    };
    
    
    /**
     * Take a list of patches and return a textual representation.
     * @param {!Array.<!diff_match_patch.patch_obj>} patches Array of Patch objects.
     * @return {string} Text representation of patches.
     */
    diff_match_patch.prototype.patch_toText = function(patches) {
      var text = [];
      for (var x = 0; x < patches.length; x++) {
        text[x] = patches[x];
      }
      return text.join('');
    };
    
    
    /**
     * Parse a textual representation of patches and return a list of Patch objects.
     * @param {string} textline Text representation of patches.
     * @return {!Array.<!diff_match_patch.patch_obj>} Array of Patch objects.
     * @throws {!Error} If invalid input.
     */
    diff_match_patch.prototype.patch_fromText = function(textline) {
      var patches = [];
      if (!textline) {
        return patches;
      }
      var text = textline.split('\n');
      var textPointer = 0;
      var patchHeader = /^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/;
      while (textPointer < text.length) {
        var m = text[textPointer].match(patchHeader);
        if (!m) {
          throw new Error('Invalid patch string: ' + text[textPointer]);
        }
        var patch = new diff_match_patch.patch_obj();
        patches.push(patch);
        patch.start1 = parseInt(m[1], 10);
        if (m[2] === '') {
          patch.start1--;
          patch.length1 = 1;
        } else if (m[2] == '0') {
          patch.length1 = 0;
        } else {
          patch.start1--;
          patch.length1 = parseInt(m[2], 10);
        }
    
        patch.start2 = parseInt(m[3], 10);
        if (m[4] === '') {
          patch.start2--;
          patch.length2 = 1;
        } else if (m[4] == '0') {
          patch.length2 = 0;
        } else {
          patch.start2--;
          patch.length2 = parseInt(m[4], 10);
        }
        textPointer++;
    
        while (textPointer < text.length) {
          var sign = text[textPointer].charAt(0);
          try {
            var line = decodeURI(text[textPointer].substring(1));
          } catch (ex) {
            // Malformed URI sequence.
            throw new Error('Illegal escape in patch_fromText: ' + line);
          }
          if (sign == '-') {
            // Deletion.
            patch.diffs.push([DIFF_DELETE, line]);
          } else if (sign == '+') {
            // Insertion.
            patch.diffs.push([DIFF_INSERT, line]);
          } else if (sign == ' ') {
            // Minor equality.
            patch.diffs.push([DIFF_EQUAL, line]);
          } else if (sign == '@') {
            // Start of next patch.
            break;
          } else if (sign === '') {
            // Blank line?  Whatever.
          } else {
            // WTF?
            throw new Error('Invalid patch mode "' + sign + '" in: ' + line);
          }
          textPointer++;
        }
      }
      return patches;
    };
    
    
    /**
     * Class representing one patch operation.
     * @constructor
     */
    diff_match_patch.patch_obj = function() {
      /** @type {!Array.<!diff_match_patch.Diff>} */
      this.diffs = [];
      /** @type {?number} */
      this.start1 = null;
      /** @type {?number} */
      this.start2 = null;
      /** @type {number} */
      this.length1 = 0;
      /** @type {number} */
      this.length2 = 0;
    };
    
    
    /**
     * Emmulate GNU diff's format.
     * Header: @@ -382,8 +481,9 @@
     * Indicies are printed as 1-based, not 0-based.
     * @return {string} The GNU diff string.
     */
    diff_match_patch.patch_obj.prototype.toString = function() {
      var coords1, coords2;
      if (this.length1 === 0) {
        coords1 = this.start1 + ',0';
      } else if (this.length1 == 1) {
        coords1 = this.start1 + 1;
      } else {
        coords1 = (this.start1 + 1) + ',' + this.length1;
      }
      if (this.length2 === 0) {
        coords2 = this.start2 + ',0';
      } else if (this.length2 == 1) {
        coords2 = this.start2 + 1;
      } else {
        coords2 = (this.start2 + 1) + ',' + this.length2;
      }
      var text = ['@@ -' + coords1 + ' +' + coords2 + ' @@\n'];
      var op;
      // Escape the body of the patch with %xx notation.
      for (var x = 0; x < this.diffs.length; x++) {
        switch (this.diffs[x][0]) {
          case DIFF_INSERT:
            op = '+';
            break;
          case DIFF_DELETE:
            op = '-';
            break;
          case DIFF_EQUAL:
            op = ' ';
            break;
        }
        text[x + 1] = op + encodeURI(this.diffs[x][1]) + '\n';
      }
      return text.join('').replace(/%20/g, ' ');
    };
    
    
    // Export these global variables so that they survive Google's JS compiler.
    // In a browser, 'this' will be 'window'.
    // Users of node.js should 'require' the uncompressed version since Google's
    // JS compiler may break the following exports for non-browser environments.
    this['diff_match_patch'] = diff_match_patch;
    this['DIFF_DELETE'] = DIFF_DELETE;
    this['DIFF_INSERT'] = DIFF_INSERT;
    this['DIFF_EQUAL'] = DIFF_EQUAL;
    
    define("diff_match_patch_uncompressed", (function (global) {
        return function () {
            var ret, fn;
            return ret || global.diff_match_patch;
        };
    }(this)));
    
    /*
     * Copyright 2012 The Polymer Authors. All rights reserved.
     * Use of this source code is governed by a BSD-style
     * license that can be found in the LICENSE file.
     */
    
    if (typeof WeakMap === 'undefined') {
      (function() {
        var defineProperty = Object.defineProperty;
        var counter = Date.now() % 1e9;
    
        var WeakMap = function() {
          this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__');
        };
    
        WeakMap.prototype = {
          set: function(key, value) {
            var entry = key[this.name];
            if (entry && entry[0] === key)
              entry[1] = value;
            else
              defineProperty(key, this.name, {value: [key, value], writable: true});
          },
          get: function(key) {
            var entry;
            return (entry = key[this.name]) && entry[0] === key ?
                entry[1] : undefined;
          },
          delete: function(key) {
            this.set(key, undefined);
          }
        };
    
        window.WeakMap = WeakMap;
      })();
    }
    ;
    define("WeakMap", function(){});
    
    /*
     * Copyright 2012 The Polymer Authors. All rights reserved.
     * Use of this source code is goverened by a BSD-style
     * license that can be found in the LICENSE file.
     */
    
    (function(global) {
    
      var registrationsTable = new WeakMap();
    
      // We use setImmediate or postMessage for our future callback.
      var setImmediate = window.msSetImmediate;
    
      // Use post message to emulate setImmediate.
      if (!setImmediate) {
        var setImmediateQueue = [];
        var sentinel = String(Math.random());
        window.addEventListener('message', function(e) {
          if (e.data === sentinel) {
            var queue = setImmediateQueue;
            setImmediateQueue = [];
            queue.forEach(function(func) {
              func();
            });
          }
        });
        setImmediate = function(func) {
          setImmediateQueue.push(func);
          window.postMessage(sentinel, '*');
        };
      }
    
      // This is used to ensure that we never schedule 2 callas to setImmediate
      var isScheduled = false;
    
      // Keep track of observers that needs to be notified next time.
      var scheduledObservers = [];
    
      /**
       * Schedules |dispatchCallback| to be called in the future.
       * @param {MutationObserver} observer
       */
      function scheduleCallback(observer) {
        scheduledObservers.push(observer);
        if (!isScheduled) {
          isScheduled = true;
          setImmediate(dispatchCallbacks);
        }
      }
    
      function wrapIfNeeded(node) {
        return window.ShadowDOMPolyfill &&
            window.ShadowDOMPolyfill.wrapIfNeeded(node) ||
            node;
      }
    
      function dispatchCallbacks() {
        // http://dom.spec.whatwg.org/#mutation-observers
    
        isScheduled = false; // Used to allow a new setImmediate call above.
    
        var observers = scheduledObservers;
        scheduledObservers = [];
        // Sort observers based on their creation UID (incremental).
        observers.sort(function(o1, o2) {
          return o1.uid_ - o2.uid_;
        });
    
        var anyNonEmpty = false;
        observers.forEach(function(observer) {
    
          // 2.1, 2.2
          var queue = observer.takeRecords();
          // 2.3. Remove all transient registered observers whose observer is mo.
          removeTransientObserversFor(observer);
    
          // 2.4
          if (queue.length) {
            observer.callback_(queue, observer);
            anyNonEmpty = true;
          }
        });
    
        // 3.
        if (anyNonEmpty)
          dispatchCallbacks();
      }
    
      function removeTransientObserversFor(observer) {
        observer.nodes_.forEach(function(node) {
          var registrations = registrationsTable.get(node);
          if (!registrations)
            return;
          registrations.forEach(function(registration) {
            if (registration.observer === observer)
              registration.removeTransientObservers();
          });
        });
      }
    
      /**
       * This function is used for the "For each registered observer observer (with
       * observer's options as options) in target's list of registered observers,
       * run these substeps:" and the "For each ancestor ancestor of target, and for
       * each registered observer observer (with options options) in ancestor's list
       * of registered observers, run these substeps:" part of the algorithms. The
       * |options.subtree| is checked to ensure that the callback is called
       * correctly.
       *
       * @param {Node} target
       * @param {function(MutationObserverInit):MutationRecord} callback
       */
      function forEachAncestorAndObserverEnqueueRecord(target, callback) {
        for (var node = target; node; node = node.parentNode) {
          var registrations = registrationsTable.get(node);
    
          if (registrations) {
            for (var j = 0; j < registrations.length; j++) {
              var registration = registrations[j];
              var options = registration.options;
    
              // Only target ignores subtree.
              if (node !== target && !options.subtree)
                continue;
    
              var record = callback(options);
              if (record)
                registration.enqueue(record);
            }
          }
        }
      }
    
      var uidCounter = 0;
    
      /**
       * The class that maps to the DOM MutationObserver interface.
       * @param {Function} callback.
       * @constructor
       */
      function JsMutationObserver(callback) {
        this.callback_ = callback;
        this.nodes_ = [];
        this.records_ = [];
        this.uid_ = ++uidCounter;
      }
    
      JsMutationObserver.prototype = {
        observe: function(target, options) {
          target = wrapIfNeeded(target);
    
          // 1.1
          if (!options.childList && !options.attributes && !options.characterData ||
    
              // 1.2
              options.attributeOldValue && !options.attributes ||
    
              // 1.3
              options.attributeFilter && options.attributeFilter.length &&
                  !options.attributes ||
    
              // 1.4
              options.characterDataOldValue && !options.characterData) {
    
            throw new SyntaxError();
          }
    
          var registrations = registrationsTable.get(target);
          if (!registrations)
            registrationsTable.set(target, registrations = []);
    
          // 2
          // If target's list of registered observers already includes a registered
          // observer associated with the context object, replace that registered
          // observer's options with options.
          var registration;
          for (var i = 0; i < registrations.length; i++) {
            if (registrations[i].observer === this) {
              registration = registrations[i];
              registration.removeListeners();
              registration.options = options;
              break;
            }
          }
    
          // 3.
          // Otherwise, add a new registered observer to target's list of registered
          // observers with the context object as the observer and options as the
          // options, and add target to context object's list of nodes on which it
          // is registered.
          if (!registration) {
            registration = new Registration(this, target, options);
            registrations.push(registration);
            this.nodes_.push(target);
          }
    
          registration.addListeners();
        },
    
        disconnect: function() {
          this.nodes_.forEach(function(node) {
            var registrations = registrationsTable.get(node);
            for (var i = 0; i < registrations.length; i++) {
              var registration = registrations[i];
              if (registration.observer === this) {
                registration.removeListeners();
                registrations.splice(i, 1);
                // Each node can only have one registered observer associated with
                // this observer.
                break;
              }
            }
          }, this);
          this.records_ = [];
        },
    
        takeRecords: function() {
          var copyOfRecords = this.records_;
          this.records_ = [];
          return copyOfRecords;
        }
      };
    
      /**
       * @param {string} type
       * @param {Node} target
       * @constructor
       */
      function MutationRecord(type, target) {
        this.type = type;
        this.target = target;
        this.addedNodes = [];
        this.removedNodes = [];
        this.previousSibling = null;
        this.nextSibling = null;
        this.attributeName = null;
        this.attributeNamespace = null;
        this.oldValue = null;
      }
    
      function copyMutationRecord(original) {
        var record = new MutationRecord(original.type, original.target);
        record.addedNodes = original.addedNodes.slice();
        record.removedNodes = original.removedNodes.slice();
        record.previousSibling = original.previousSibling;
        record.nextSibling = original.nextSibling;
        record.attributeName = original.attributeName;
        record.attributeNamespace = original.attributeNamespace;
        record.oldValue = original.oldValue;
        return record;
      };
    
      // We keep track of the two (possibly one) records used in a single mutation.
      var currentRecord, recordWithOldValue;
    
      /**
       * Creates a record without |oldValue| and caches it as |currentRecord| for
       * later use.
       * @param {string} oldValue
       * @return {MutationRecord}
       */
      function getRecord(type, target) {
        return currentRecord = new MutationRecord(type, target);
      }
    
      /**
       * Gets or creates a record with |oldValue| based in the |currentRecord|
       * @param {string} oldValue
       * @return {MutationRecord}
       */
      function getRecordWithOldValue(oldValue) {
        if (recordWithOldValue)
          return recordWithOldValue;
        recordWithOldValue = copyMutationRecord(currentRecord);
        recordWithOldValue.oldValue = oldValue;
        return recordWithOldValue;
      }
    
      function clearRecords() {
        currentRecord = recordWithOldValue = undefined;
      }
    
      /**
       * @param {MutationRecord} record
       * @return {boolean} Whether the record represents a record from the current
       * mutation event.
       */
      function recordRepresentsCurrentMutation(record) {
        return record === recordWithOldValue || record === currentRecord;
      }
    
      /**
       * Selects which record, if any, to replace the last record in the queue.
       * This returns |null| if no record should be replaced.
       *
       * @param {MutationRecord} lastRecord
       * @param {MutationRecord} newRecord
       * @param {MutationRecord}
       */
      function selectRecord(lastRecord, newRecord) {
        if (lastRecord === newRecord)
          return lastRecord;
    
        // Check if the the record we are adding represents the same record. If
        // so, we keep the one with the oldValue in it.
        if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord))
          return recordWithOldValue;
    
        return null;
      }
    
      /**
       * Class used to represent a registered observer.
       * @param {MutationObserver} observer
       * @param {Node} target
       * @param {MutationObserverInit} options
       * @constructor
       */
      function Registration(observer, target, options) {
        this.observer = observer;
        this.target = target;
        this.options = options;
        this.transientObservedNodes = [];
      }
    
      Registration.prototype = {
        enqueue: function(record) {
          var records = this.observer.records_;
          var length = records.length;
    
          // There are cases where we replace the last record with the new record.
          // For example if the record represents the same mutation we need to use
          // the one with the oldValue. If we get same record (this can happen as we
          // walk up the tree) we ignore the new record.
          if (records.length > 0) {
            var lastRecord = records[length - 1];
            var recordToReplaceLast = selectRecord(lastRecord, record);
            if (recordToReplaceLast) {
              records[length - 1] = recordToReplaceLast;
              return;
            }
          } else {
            scheduleCallback(this.observer);
          }
    
          records[length] = record;
        },
    
        addListeners: function() {
          this.addListeners_(this.target);
        },
    
        addListeners_: function(node) {
          var options = this.options;
          if (options.attributes)
            node.addEventListener('DOMAttrModified', this, true);
    
          if (options.characterData)
            node.addEventListener('DOMCharacterDataModified', this, true);
    
          if (options.childList)
            node.addEventListener('DOMNodeInserted', this, true);
    
          if (options.childList || options.subtree)
            node.addEventListener('DOMNodeRemoved', this, true);
        },
    
        removeListeners: function() {
          this.removeListeners_(this.target);
        },
    
        removeListeners_: function(node) {
          var options = this.options;
          if (options.attributes)
            node.removeEventListener('DOMAttrModified', this, true);
    
          if (options.characterData)
            node.removeEventListener('DOMCharacterDataModified', this, true);
    
          if (options.childList)
            node.removeEventListener('DOMNodeInserted', this, true);
    
          if (options.childList || options.subtree)
            node.removeEventListener('DOMNodeRemoved', this, true);
        },
    
        /**
         * Adds a transient observer on node. The transient observer gets removed
         * next time we deliver the change records.
         * @param {Node} node
         */
        addTransientObserver: function(node) {
          // Don't add transient observers on the target itself. We already have all
          // the required listeners set up on the target.
          if (node === this.target)
            return;
    
          this.addListeners_(node);
          this.transientObservedNodes.push(node);
          var registrations = registrationsTable.get(node);
          if (!registrations)
            registrationsTable.set(node, registrations = []);
    
          // We know that registrations does not contain this because we already
          // checked if node === this.target.
          registrations.push(this);
        },
    
        removeTransientObservers: function() {
          var transientObservedNodes = this.transientObservedNodes;
          this.transientObservedNodes = [];
    
          transientObservedNodes.forEach(function(node) {
            // Transient observers are never added to the target.
            this.removeListeners_(node);
    
            var registrations = registrationsTable.get(node);
            for (var i = 0; i < registrations.length; i++) {
              if (registrations[i] === this) {
                registrations.splice(i, 1);
                // Each node can only have one registered observer associated with
                // this observer.
                break;
              }
            }
          }, this);
        },
    
        handleEvent: function(e) {
          // Stop propagation since we are managing the propagation manually.
          // This means that other mutation events on the page will not work
          // correctly but that is by design.
          e.stopImmediatePropagation();
    
          switch (e.type) {
            case 'DOMAttrModified':
              // http://dom.spec.whatwg.org/#concept-mo-queue-attributes
    
              var name = e.attrName;
              var namespace = e.relatedNode.namespaceURI;
              var target = e.target;
    
              // 1.
              var record = new getRecord('attributes', target);
              record.attributeName = name;
              record.attributeNamespace = namespace;
    
              // 2.
              var oldValue =
                  e.attrChange === MutationEvent.ADDITION ? null : e.prevValue;
    
              forEachAncestorAndObserverEnqueueRecord(target, function(options) {
                // 3.1, 4.2
                if (!options.attributes)
                  return;
    
                // 3.2, 4.3
                if (options.attributeFilter && options.attributeFilter.length &&
                    options.attributeFilter.indexOf(name) === -1 &&
                    options.attributeFilter.indexOf(namespace) === -1) {
                  return;
                }
                // 3.3, 4.4
                if (options.attributeOldValue)
                  return getRecordWithOldValue(oldValue);
    
                // 3.4, 4.5
                return record;
              });
    
              break;
    
            case 'DOMCharacterDataModified':
              // http://dom.spec.whatwg.org/#concept-mo-queue-characterdata
              var target = e.target;
    
              // 1.
              var record = getRecord('characterData', target);
    
              // 2.
              var oldValue = e.prevValue;
    
    
              forEachAncestorAndObserverEnqueueRecord(target, function(options) {
                // 3.1, 4.2
                if (!options.characterData)
                  return;
    
                // 3.2, 4.3
                if (options.characterDataOldValue)
                  return getRecordWithOldValue(oldValue);
    
                // 3.3, 4.4
                return record;
              });
    
              break;
    
            case 'DOMNodeRemoved':
              this.addTransientObserver(e.target);
              // Fall through.
            case 'DOMNodeInserted':
              // http://dom.spec.whatwg.org/#concept-mo-queue-childlist
              var target = e.relatedNode;
              var changedNode = e.target;
              var addedNodes, removedNodes;
              if (e.type === 'DOMNodeInserted') {
                addedNodes = [changedNode];
                removedNodes = [];
              } else {
    
                addedNodes = [];
                removedNodes = [changedNode];
              }
              var previousSibling = changedNode.previousSibling;
              var nextSibling = changedNode.nextSibling;
    
              // 1.
              var record = getRecord('childList', target);
              record.addedNodes = addedNodes;
              record.removedNodes = removedNodes;
              record.previousSibling = previousSibling;
              record.nextSibling = nextSibling;
    
              forEachAncestorAndObserverEnqueueRecord(target, function(options) {
                // 2.1, 3.2
                if (!options.childList)
                  return;
    
                // 2.2, 3.3
                return record;
              });
    
          }
    
          clearRecords();
        }
      };
    
      global.JsMutationObserver = JsMutationObserver;
    
      if (!global.MutationObserver)
        global.MutationObserver = JsMutationObserver;
    
    
    })(this);
    
    define("MutationObservers", function(){});
    
    Prism.languages.markup = {
    	'comment': /&lt;!--[\w\W]*?-->/g,
    	'prolog': /&lt;\?.+?\?>/,
    	'doctype': /&lt;!DOCTYPE.+?>/,
    	'cdata': /&lt;!\[CDATA\[[\w\W]*?]]>/i,
    	'tag': {
    		pattern: /&lt;\/?[\w:-]+\s*(?:\s+[\w:-]+(?:=(?:("|')(\\?[\w\W])*?\1|[^\s'">=]+))?\s*)*\/?>/gi,
    		inside: {
    			'tag': {
    				pattern: /^&lt;\/?[\w:-]+/i,
    				inside: {
    					'punctuation': /^&lt;\/?/,
    					'namespace': /^[\w-]+?:/
    				}
    			},
    			'attr-value': {
    				pattern: /=(?:('|")[\w\W]*?(\1)|[^\s>]+)/gi,
    				inside: {
    					'punctuation': /=|>|"/g
    				}
    			},
    			'punctuation': /\/?>/g,
    			'attr-name': {
    				pattern: /[\w:-]+/g,
    				inside: {
    					'namespace': /^[\w-]+?:/
    				}
    			}
    			
    		}
    	},
    	'entity': /&amp;#?[\da-z]{1,8};/gi
    };
    
    // Plugin to make entity title show the real entity, idea by Roman Komarov
    Prism.hooks.add('wrap', function(env) {
    
    	if (env.type === 'entity') {
    		env.attributes['title'] = env.content.replace(/&amp;/, '&');
    	}
    });
    
    define("bower-libs/prism/components/prism-markup", function(){});
    
    Prism.languages.latex = {
        // A tex command e.g. \foo
    	'keyword': /\\(?:[^a-zA-Z]|[a-zA-Z]+)/g,
        // Curly and square braces
    	'lparen': /[[({]/g,
        // Curly and square braces
    	'rparen': /[\])}]/g,
        // A comment. Tex comments start with % and go to 
        // the end of the line
    	'comment': /%.*/g,
    };
    
    define("libs/prism-latex", function(){});
    
    Prism.languages.md = (function() {
    
    	var charInsideUrl = "(&amp;|[-A-Z0-9+@#/%?=~_|[\\]()!:,.;])",
    		charEndingUrl = "(&amp;|[-A-Z0-9+@#/%=~_|[\\])])";
    	var urlPattern = new RegExp("(https?|ftp)(://" + charInsideUrl + "*" + charEndingUrl + ")(?=$|\\W)", "gi");
    	var emailPattern = /(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)/gi;
    
    	var latex = Prism.languages.latex;
    
    	var lf = /\n/gm;
    
    	var md = {};
    	md['pre gfm'] = {
    		pattern: /^`{3}.*\n(?:[\s\S]*?)\n`{3} *$/gm,
    		inside: {
    			"md md-pre": /`{3}/,
    			lf: lf
    		}
    	};
    	md['h1 alt'] = {
    		pattern: /^(.+)[ \t]*\n=+[ \t]*$/gm,
    		inside: {
    		}
    	};
    	md['h2 alt'] = {
    		pattern: /^(.+)[ \t]*\n-+[ \t]*$/gm,
    		inside: {
    		}
    	};
    	for(var i = 6; i >= 1; i--) {
    		md["h" + i] = {
    			pattern: new RegExp("^#{" + i + "}.+$", "gm"),
    			inside: {
    				"md md-hash": new RegExp("^#{" + i + "}")
    			}
    		};
    	}
    	md.li = {
    		pattern: /^[ \t]*([*+\-]|\d+\.)[ \t].+(?:\n|[ \t].*\n)*/gm,
    		inside: {
    			"md md-li": /^[ \t]*([*+\-]|\d+\.)[ \t]/m,
    			'pre gfm': {
    				pattern: /^((?: {4}|\t)+)`{3}.*\n(?:[\s\S]*?)\n\1`{3} *$/gm,
    				inside: {
    					"md md-pre": /`{3}/,
    					lf: lf
    				}
    			},
    			lf: lf
    		}
    	};
    	md.pre = {
    		pattern: /(^|(?:^|(?:^|\n)(?![ \t]*([*+\-]|\d+\.)[ \t]).*\n)\s*?\n)(\s*(?: {4}|\t).*(?:\n|$))+/g,
    		lookbehind: true,
    		inside: {
    			lf: lf
    		}
    	};
    	md.table = {
    		pattern: new RegExp(
    			[
    				'^'                         ,
    				'[ ]{0,3}'                  , // Allowed whitespace
    				'[|]'                       , // Initial pipe
    				'(.+)\\n'                   , // $1: Header Row
    
    				'[ ]{0,3}'                  , // Allowed whitespace
    				'[|]([ ]*[-:]+[-| :]*)\\n'  , // $2: Separator
    
    				'('                         , // $3: Table Body
    				'(?:[ ]*[|].*\\n?)*'      , // Table rows
    				')',
    				'(?:\\n|$)'                   // Stop at final newline
    			].join(''),
    			'gm'
    		),
    		inside: {
    			lf: lf
    		}
    	};
    	md['table alt'] = {
    		pattern: new RegExp(
    			[
    				'^'                         ,
    				'[ ]{0,3}'                  , // Allowed whitespace
    				'(\\S.*[|].*)\\n'           , // $1: Header Row
    
    				'[ ]{0,3}'                  , // Allowed whitespace
    				'([-:]+[ ]*[|][-| :]*)\\n'  , // $2: Separator
    
    				'('                         , // $3: Table Body
    				'(?:.*[|].*\\n?)*'        , // Table rows
    				')'                         ,
    				'(?:\\n|$)'                   // Stop at final newline
    			].join(''),
    			'gm'
    		),
    		inside: {
    			lf: lf
    		}
    	};
    
    	md.hr = {
    		pattern: /^([*\-_] *){3,}$/gm
    	};
    	md.blockquote = {
    		pattern: /^ {0,3}> *[^\n]+$/gm,
    		inside: {
    			"md md-gt": /^ {0,3}> */,
    			"li": md.li
    		}
    	};
    	md['math block'] = {
    		pattern: /(\$\$|\\\\\[|\\\\\\\\\()[\s\S]*?(\$\$|\\\\\]|\\\\\\\\\))/g,
    		inside: {
    			"md md-bracket-start": /^(\$\$|\\\\\[|\\\\\\\\\()/,
    			"md md-bracket-end": /(\$\$|\\\\\]|\\\\\\\\\))/,
    			lf: lf,
    			rest: latex
    		}
    	};
    	md['latex block'] = {
    		pattern: /\\?\\begin\{([a-z]*\*?)\}[\s\S]*?\\?\\end\{\1\}/g,
    		inside: {
    			"keyword": /\\?\\(begin|end)/,
    			lf: lf,
    			rest: latex
    		}
    	};
    	md.fndef = {
    		pattern: /^ {0,3}\[\^.*?\]:[ \t]+.*$/gm,
    		inside: {
    			"ref-id": {
    				pattern: /\[\^.*?\]/,
    				inside: {
    					"md md-bracket-start": /\[/,
    					"md md-bracket-end": /\]/
    				}
    			}
    		}
    	};
    	md.linkdef = {
    		pattern: /^ {0,3}\[.*?\]:[ \t]+.*$/gm,
    		inside: {
    			"link-id": {
    				pattern: /\[.*?\]/,
    				inside: {
    					"md md-bracket-start": /\[/,
    					"md md-bracket-end": /\]/
    				}
    			},
    			url: urlPattern,
    			linktitle: /['\"\(][^\'\"\)]*['\"\)]/
    		}
    	};
    	md.p = {
    		pattern: /.+/g,
    		inside: {
    			'md md-toc': /^\s*\[(toc|TOC)\]\s*$/g
    		}
    	};
    	md.lf = /^\n$/gm;
    	md.img = {
    		pattern: /!\[[^\]]*\]\([^\)]+\)/g,
    		inside: {
    			"md md-bang": /^!/,
    			"md md-bracket-start": /\[/,
    			"md md-alt": /[^\[]+(?=\])/,
    			"md md-bracket-end": /\](?=\()/,
    			"md img-parens": {
    				pattern: /\([^\)]+\)/,
    				inside: {
    					"md md-paren-start": /^\(/,
    					"md md-title": /(['‘][^'’]*['’]|["“][^"”]*["”])(?=\)$)/,
    					"md md-src": /[^\('" \t]+(?=[\)'" \t])/,
    					"md md-paren-end": /\)$/
    				}
    			}
    		}
    	};
    	md.link = {
    		pattern: /\[(?:(\\.)|[^\[\]])*\]\([^\(\)\s]+(\(\S*?\))??[^\(\)\s]*?(\s(['‘][^'’]*['’]|["“][^"”]*["”]))?\)/gm,
    		inside: {
    			"md md-bracket-start": {
    				pattern: /(^|[^\\])\[/,
    				lookbehind: true
    			},
    			"md md-underlined-text": {
    				pattern: /(?:(\\.)|[^\[\]])+(?=\])/
    			},
    			"md md-bracket-end": /\]\s?\(/,
    			"md md-paren-end": /\)$/,
    			"md md-href": /.*/
    		}
    	};
    	md.fn = {
    		pattern: /\[\^(.*?)\]/g,
    		inside: {
    			"ref": {
    				pattern: /^\[[^\[\]]+\] ?/,
    				inside: {
    					"md md-bracket-start": /\[/,
    					"md md-ref": /^[^\[\]]+/,
    					"md md-bracket-end": /\]/
    				}
    			}
    		}
    	};
    	md.imgref = {
    		pattern: /!\[(.*?)\] ?\[(.*?)\]/g,
    		inside: {
    			"md md-bang": /^!/,
    			"ref-end": {
    				pattern: /\[[^\[\]]+\]$/,
    				inside: {
    					"md md-bracket-start": /\[/,
    					"md md-href": /[^\[\]]+(?=]$)/,
    					"md md-bracket-end": /\]/
    				}
    			},
    			"ref-start": {
    				pattern: /^\[[^\[\]]+\] ?/,
    				inside: {
    					"md md-bracket-start": /\[/,
    					"md md-alt": /^[^\[\]]+/,
    					"md md-bracket-end": /\]/
    				}
    			}
    		}
    	};
    	md.linkref = {
    		pattern: /\[(.*?)\] ?\[(.*?)\]/g,
    		inside: {
    			"ref-end": {
    				pattern: /\[[^\[\]]+\]$/,
    				inside: {
    					"md md-bracket-start": /\[/,
    					"md md-href": /[^\[\]]+(?=]$)/,
    					"md md-bracket-end": /\]/
    				}
    			},
    			"ref-start": {
    				pattern: /^\[[^\[\]]+\] ?/,
    				inside: {
    					"md md-bracket-start": /\[/,
    					"md md-underlined-text": /^[^\[\]]+/,
    					"md md-bracket-end": /\]/
    				}
    			}
    		}
    	};
    	md.code = {
    		pattern: /(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/g,
    		lookbehind: true,
    		inside: {
    			"md md-code": /`/
    		}
    	};
    	md.math = {
    		pattern: /\$.*?\$/g,
    		inside: {
    			"md md-bracket-start": /^\$/,
    			"md md-bracket-end": /\$$/,
    			rest: latex
    		}
    	};
    	md.strong = {
    		pattern: /([_\*])\1((?!\1{2}).)*\1{2}/g,
    		inside: {
    			"md md-strong": /([_\*])\1/g
    		}
    	};
    	md.em = {
    		pattern: /(^|[^\\])(\*|_)(\S[^\2]*?)??[^\s\\]+?\2/g,
    		lookbehind: true,
    		inside: {
    			"md md-em md-start": /^(\*|_)/,
    			"md md-em md-close": /(\*|_)$/
    		}
    	};
    	md.strike = {
    		pattern: /(^|\n|\W)(~~)(?=\S)([^\r]*?\S)\2/gm,
    		lookbehind: true,
    		inside: {
    			"md md-s": /(~~)/,
    			"md-strike-text": /[^~]+/
    		}
    	};
    	var rest = {
    		code: md.code,
    		math: md.math,
    		fn: md.fn,
    		img: md.img,
    		link: md.link,
    		imgref: md.imgref,
    		linkref: md.linkref,
    		url: urlPattern,
    		email: emailPattern,
    		strong: md.strong,
    		em: md.em,
    		strike: md.strike,
    		conflict: /⧸⧸/g,
    		comment: Prism.languages.markup.comment,
    		tag: Prism.languages.markup.tag,
    		entity: Prism.languages.markup.entity
    	};
    
    	for(var c = 6; c >= 1; c--) {
    		md["h" + c].inside.rest = rest;
    	}
    	md["h1 alt"].inside.rest = rest;
    	md["h2 alt"].inside.rest = rest;
    	md.table.inside.rest = rest;
    	md["table alt"].inside.rest = rest;
    	md.p.inside.rest = rest;
    	md.blockquote.inside.rest = rest;
    	md.li.inside.rest = rest;
    	md.fndef.inside.rest = rest;
    
    	rest = {
    		code: md.code,
    		fn: md.fn,
    		link: md.link,
    		linkref: md.linkref,
    		conflict: /⧸⧸/g,
    	};
    	md.strong.inside.rest = rest;
    	md.em.inside.rest = rest;
    	md.strike.inside.rest = rest;
    
    	var inside = {
    		code: md.code,
    		strong: md.strong,
    		em: md.em,
    		strike: md.strike,
    		conflict: /⧸⧸/g,
    		comment: Prism.languages.markup.comment,
    		tag: Prism.languages.markup.tag,
    		entity: Prism.languages.markup.entity
    	};
    	md.link.inside["md md-underlined-text"].inside = inside;
    	md.linkref.inside["ref-start"].inside["md md-underlined-text"].inside = inside;
    
    	return md;
    })();
    
    define("libs/prism-markdown", function(){});
    
    /* jshint -W084, -W099 */
    // Credit to http://dabblet.com/
    define('editor',[
    	'underscore',
    	'utils',
    	'settings',
    	'eventMgr',
    	'prism-core',
    	'diff_match_patch_uncompressed',
    	// 'jsondiffpatch',
    	'crel',
    	'rangy',
    	'MutationObservers',
    	'libs/prism-markdown'
    ], function( _, utils, settings, eventMgr, Prism, diff_match_patch, crel, rangy) {
    
    	var editor = {};
    	var scrollTop = 0;
    	var inputElt;
    	var $inputElt;
    	var contentElt;
    	var $contentElt;
    	var marginElt;
    	var $marginElt;
    	var previewElt;
    	var pagedownEditor;
    	var trailingLfNode;
    
    	// 这里, 加载预览
    	var refreshPreviewLater = (function() {
    		var elapsedTime = 0;
    		var timeoutId;
    		var refreshPreview = function() {
    			var startTime = Date.now();
    			pagedownEditor.refreshPreview();
    			elapsedTime = Date.now() - startTime;
    		};
    		if(settings.lazyRendering === true) {
    			return _.debounce(refreshPreview, 500);
    		}
    		return function() {
    			clearTimeout(timeoutId);
    			timeoutId = setTimeout(refreshPreview, elapsedTime < 2000 ? elapsedTime : 2000);
    		};
    	})();
    	eventMgr.addListener('onPagedownConfigure', function(pagedownEditorParam) {
    		pagedownEditor = pagedownEditorParam;
    	});
    
    	// 这里, onSectionsCreated, highlightSections, 预览
    	// 但输入中文也有这个事件, 但isComposing=1
    	var isComposing = 0;
    	eventMgr.addListener('onSectionsCreated', function(newSectionList) {
    		// console.trace('onSectionsCreated ==> ' + isComposing);
    		// isComposing = 0;
    		// if(!isComposing) {
    			// 总执行这个
    			updateSectionList(newSectionList);
    			highlightSections();
    		// }
    		if(fileChanged === true) {
    			// Refresh preview synchronously
    			pagedownEditor.refreshPreview();
    		}
    		else {
    			refreshPreviewLater();
    		}
    	});
    
    	var fileChanged = true;
    	var fileDesc;
    	eventMgr.addListener('onFileSelected', function(selectedFileDesc) {
    		fileChanged = true;
    		fileDesc = selectedFileDesc;
    	});
    
    	// Used to detect editor changes
    	function Watcher() {
    		this.isWatching = false;
    		var contentObserver;
    		this.startWatching = function() {
    			this.isWatching = true;
    			contentObserver = contentObserver || new MutationObserver(checkContentChange);
    			contentObserver.observe(contentElt, {
    				childList: true,
    				subtree: true,
    				characterData: true
    			});
    		};
    		this.stopWatching = function() {
    			contentObserver.disconnect();
    			this.isWatching = false;
    		};
    		this.noWatch = function(cb) {
    			if(this.isWatching === true) {
    				this.stopWatching();
    				cb();
    				this.startWatching();
    			}
    			else {
    				cb();
    			}
    		};
    	}
    
    	var watcher = new Watcher();
    	editor.watcher = watcher;
    
    	var diffMatchPatch = new diff_match_patch();
    	/*
    	var jsonDiffPatch = jsondiffpatch.create({
    		objectHash: function(obj) {
    			return JSON.stringify(obj);
    		},
    		arrays: {
    			detectMove: false
    		},
    		textDiff: {
    			minLength: 9999999
    		}
    	});
    	*/
    
    	function SelectionMgr() {
    		var self = this;
    		var lastSelectionStart = 0, lastSelectionEnd = 0;
    		this.selectionStart = 0;
    		this.selectionEnd = 0;
    		this.cursorY = 0;
    		this.adjustTop = 0;
    		this.adjustBottom = 0;
    		this.findOffsets = function(offsetList) {
    			var result = [];
    			if(!offsetList.length) {
    				return result;
    			}
    			var offset = offsetList.shift();
    			var walker = document.createTreeWalker(contentElt, 4, null, false);
    			var text = '';
    			var walkerOffset = 0;
    			while(walker.nextNode()) {
    				text = walker.currentNode.nodeValue || '';
    				var newWalkerOffset = walkerOffset + text.length;
    				while(newWalkerOffset > offset) {
    					result.push({
    						container: walker.currentNode,
    						offsetInContainer: offset - walkerOffset,
    						offset: offset
    					});
    					if(!offsetList.length) {
    						return result;
    					}
    					offset = offsetList.shift();
    				}
    				walkerOffset = newWalkerOffset;
    			}
    			do {
    				result.push({
    					container: walker.currentNode,
    					offsetInContainer: text.length,
    					offset: offset
    				});
    				offset = offsetList.shift();
    			}
    			while(offset);
    			return result;
    		};
    		this.createRange = function(start, end) {
    			start = start < 0 ? 0 : start;
    			end = end < 0 ? 0 : end;
    			var range = document.createRange();
    			var offsetList = [], startIndex, endIndex;
    			if(_.isNumber(start)) {
    				offsetList.push(start);
    				startIndex = offsetList.length - 1;
    			}
    			if(_.isNumber(end)) {
    				offsetList.push(end);
    				endIndex = offsetList.length - 1;
    			}
    			offsetList = this.findOffsets(offsetList);
    			var startOffset = _.isObject(start) ? start : offsetList[startIndex];
    
    			range.setStart(startOffset.container, startOffset.offsetInContainer);
    
    			var endOffset = startOffset;
    			if(end && end != start) {
    				endOffset = _.isObject(end) ? end : offsetList[endIndex];
    			}
    			range.setEnd(endOffset.container, endOffset.offsetInContainer);
    			return range;
    		};
    
    		// scroll 自动滚动
    		var adjustScroll;
    		var debouncedUpdateCursorCoordinates = utils.debounce(function() {
    			$inputElt.toggleClass('has-selection', this.selectionStart !== this.selectionEnd);
    			// console.log('auto scroll');
    
    			try {
    				var coordinates = this.getCoordinates(this.selectionEnd, this.selectionEndContainer, this.selectionEndOffset);
    			}
    			catch(e) {
    				console.error(e);
    				return;
    			}
    
    			if(this.cursorY !== coordinates.y) {
    				this.cursorY = coordinates.y;
    				eventMgr.onCursorCoordinates(coordinates.x, coordinates.y);
    			}
    			if(adjustScroll) {
    				var adjustTop, adjustBottom;
    				adjustTop = adjustBottom = inputElt.offsetHeight / 2 * settings.cursorFocusRatio;
    				adjustTop = this.adjustTop || adjustTop;
    				adjustBottom = this.adjustBottom || adjustTop;
    				if(adjustTop && adjustBottom) {
    					var cursorMinY = inputElt.scrollTop + adjustTop;
    					var cursorMaxY = inputElt.scrollTop + inputElt.offsetHeight - adjustBottom;
    					if(selectionMgr.cursorY < cursorMinY) {
    						inputElt.scrollTop += selectionMgr.cursorY - cursorMinY;
    					}
    					else if(selectionMgr.cursorY > cursorMaxY) {
    						inputElt.scrollTop += selectionMgr.cursorY - cursorMaxY;
    					}
    				}
    			}
    			adjustScroll = false;
    		}, this);
    		this.updateCursorCoordinates = function(adjustScrollParam) {
    			adjustScroll = adjustScroll || adjustScrollParam;
    			debouncedUpdateCursorCoordinates();
    		};
    		this.updateSelectionRange = function() {
    			var min = Math.min(this.selectionStart, this.selectionEnd);
    			var max = Math.max(this.selectionStart, this.selectionEnd);
    			var range = this.createRange(min, max);
    			var selection = rangy.getSelection();
    			selection.removeAllRanges();
    			selection.addRange(range, this.selectionStart > this.selectionEnd);
    		};
    		var saveLastSelection = _.debounce(function() {
    			lastSelectionStart = self.selectionStart;
    			lastSelectionEnd = self.selectionEnd;
    		}, 50);
    		this.setSelectionStartEnd = function(start, end) {
    			if(start === undefined) {
    				start = this.selectionStart;
    			}
    			if(start < 0) {
    				start = 0;
    			}
    			if(end === undefined) {
    				end = this.selectionEnd;
    			}
    			if(end < 0) {
    				end = 0;
    			}
    			this.selectionStart = start;
    			this.selectionEnd = end;
    			fileDesc.editorStart = start;
    			fileDesc.editorEnd = end;
    			saveLastSelection();
    		};
    		this.saveSelectionState = (function() {
    			function save() {
    				if(fileChanged === false) {
    					var selectionStart = self.selectionStart;
    					var selectionEnd = self.selectionEnd;
    					var selection = rangy.getSelection();
    					if(selection.rangeCount > 0) {
    						var selectionRange = selection.getRangeAt(0);
    						var node = selectionRange.startContainer;
    						if((contentElt.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_CONTAINED_BY) || contentElt === node) {
    							var offset = selectionRange.startOffset;
    							if(node.hasChildNodes() && offset > 0) {
    								node = node.childNodes[offset - 1];
    								offset = node.textContent.length;
    							}
    							var container = node;
    							while(node != contentElt) {
    								while(node = node.previousSibling) {
    									if(node.textContent) {
    										offset += node.textContent.length;
    									}
    								}
    								node = container = container.parentNode;
    							}
    
    							if(selection.isBackwards()) {
    								selectionStart = offset + selectionRange.toString().length;
    								selectionEnd = offset;
    							}
    							else {
    								selectionStart = offset;
    								selectionEnd = offset + selectionRange.toString().length;
    							}
    
    							if(selectionStart === selectionEnd && selectionRange.startContainer.textContent == '\n' && selectionRange.startOffset == 1) {
    								// In IE if end of line is selected, offset is wrong
    								// Also, in Firefox cursor can be after the trailingLfNode
    								selectionStart = --selectionEnd;
    								self.setSelectionStartEnd(selectionStart, selectionEnd);
    								self.updateSelectionRange();
    							}
    						}
    					}
    					self.setSelectionStartEnd(selectionStart, selectionEnd);
    				}
    				undoMgr.saveSelectionState();
    			}
    
    			var nextTickAdjustScroll = false;
    			var debouncedSave = utils.debounce(function() {
    				save();
    				self.updateCursorCoordinates(nextTickAdjustScroll);
    				// In some cases we have to wait a little bit more to see the selection change (Cmd+A on Chrome/OSX)
    				longerDebouncedSave();
    			});
    			var longerDebouncedSave = utils.debounce(function() {
    				save();
    				if(lastSelectionStart === self.selectionStart && lastSelectionEnd === self.selectionEnd) {
    					nextTickAdjustScroll = false;
    				}
    				self.updateCursorCoordinates(nextTickAdjustScroll);
    				nextTickAdjustScroll = false;
    			}, 10);
    
    			return function(debounced, adjustScroll, forceAdjustScroll) {
    				if(forceAdjustScroll) {
    					lastSelectionStart = undefined;
    					lastSelectionEnd = undefined;
    				}
    				if(debounced) {
    					nextTickAdjustScroll = nextTickAdjustScroll || adjustScroll;
    					return debouncedSave();
    				}
    				else {
    					save();
    				}
    			};
    		})();
    		this.getSelectedText = function() {
    			var min = Math.min(this.selectionStart, this.selectionEnd);
    			var max = Math.max(this.selectionStart, this.selectionEnd);
    			return textContent.substring(min, max);
    		};
    		this.getCoordinates = function(inputOffset, container, offsetInContainer) {
    			if(!container) {
    				var offset = this.findOffsets([inputOffset])[0];
    				container = offset.container;
    				offsetInContainer = offset.offsetInContainer;
    			}
    			var x = 0;
    			var y = 0;
    			if(container.textContent == '\n') {
    				y = container.parentNode.offsetTop + container.parentNode.offsetHeight / 2;
    			}
    			else {
    				var selectedChar = textContent[inputOffset];
    				var startOffset = {
    					container: container,
    					offsetInContainer: offsetInContainer,
    					offset: inputOffset
    				};
    				var endOffset = {
    					container: container,
    					offsetInContainer: offsetInContainer,
    					offset: inputOffset
    				};
    				if(inputOffset > 0 && (selectedChar === undefined || selectedChar == '\n')) {
    					if(startOffset.offset === 0) {
    						// Need to calculate offset-1
    						startOffset = inputOffset - 1;
    					}
    					else {
    						startOffset.offsetInContainer -= 1;
    					}
    				}
    				else {
    					if(endOffset.offset === container.textContent.length) {
    						// Need to calculate offset+1
    						endOffset = inputOffset + 1;
    					}
    					else {
    						endOffset.offsetInContainer += 1;
    					}
    				}
    				var selectionRange = this.createRange(startOffset, endOffset);
    				var selectionRect = selectionRange.getBoundingClientRect();
    				y = selectionRect.top + selectionRect.height / 2 - inputElt.getBoundingClientRect().top + inputElt.scrollTop;
    			}
    			return {
    				x: x,
    				y: y
    			};
    		};
    		this.getClosestWordOffset = function(offset) {
    			var offsetStart = 0;
    			var offsetEnd = 0;
    			var nextOffset = 0;
    			textContent.split(/\s/).some(function(word) {
    				if(word) {
    					offsetStart = nextOffset;
    					offsetEnd = nextOffset + word.length;
    					if(offsetEnd > offset) {
    						return true;
    					}
    				}
    				nextOffset += word.length + 1;
    			});
    			return {
    				start: offsetStart,
    				end: offsetEnd
    			};
    		};
    	}
    
    	var selectionMgr = new SelectionMgr();
    	editor.selectionMgr = selectionMgr;
    	$(document).on('selectionchange', '.editor-content', _.bind(selectionMgr.saveSelectionState, selectionMgr, true, false));
    
    	function adjustCursorPosition(force) {
    		if(inputElt === undefined) {
    			return;
    		}
    		selectionMgr.saveSelectionState(true, true, force);
    	}
    
    	editor.adjustCursorPosition = adjustCursorPosition;
    
    	var textContent;
    
    	function setValue(value) {
    		var startOffset = diffMatchPatch.diff_commonPrefix(textContent, value);
    		if(startOffset === textContent.length) {
    			startOffset--;
    		}
    		var endOffset = Math.min(
    			diffMatchPatch.diff_commonSuffix(textContent, value),
    				textContent.length - startOffset,
    				value.length - startOffset
    		);
    		var replacement = value.substring(startOffset, value.length - endOffset);
    		var range = selectionMgr.createRange(startOffset, textContent.length - endOffset);
    		range.deleteContents();
    		range.insertNode(document.createTextNode(replacement));
    		return {
    			start: startOffset,
    			end: value.length - endOffset
    		};
    	}
    
    	// 设置值
    	editor.setValue = setValue;
    
    	// life
    	// 为page.js的拖动编辑与预览宽度用
    	editor.onResize = function(){ 
    		eventMgr.onLayoutResize(); 
    	};
    
    	var contentQueue = false;
    	editor.setContent = function(content) {
    		if(!fileDesc) {
    			fileDesc = {content: content};
    		}
    		// 很可能还没init
    		if(contentElt) {
    			fileDesc = {content: content};
    			// log(content);
    			// log(contentElt);
    			contentElt.textContent = content;
    			// setTimeout(function(){ 
    				eventMgr.onFileSelected(fileDesc);
    			// });
    			// Force this since the content could be the same
    			checkContentChange();
    			contentQueue = false;
    		} else {
    			contentQueue = content;
    		}
    	};
    
    	window.we = editor;
    
    	function replace(selectionStart, selectionEnd, replacement) {
    		undoMgr.currentMode = undoMgr.currentMode || 'replace';
    		var range = selectionMgr.createRange(
    			Math.min(selectionStart, selectionEnd),
    			Math.max(selectionStart, selectionEnd)
    		);
    		if('' + range != replacement) {
    			range.deleteContents();
    			range.insertNode(document.createTextNode(replacement));
    		}
    		var endOffset = selectionStart + replacement.length;
    		selectionMgr.setSelectionStartEnd(endOffset, endOffset);
    		selectionMgr.updateSelectionRange();
    		selectionMgr.updateCursorCoordinates(true);
    	}
    
    	editor.replace = replace;
    
    	function replaceAll(search, replacement) {
    		undoMgr.currentMode = undoMgr.currentMode || 'replace';
    		var value = textContent.replace(search, replacement);
    		if(value != textContent) {
    			var offset = editor.setValue(value);
    			selectionMgr.setSelectionStartEnd(offset.end, offset.end);
    			selectionMgr.updateSelectionRange();
    			selectionMgr.updateCursorCoordinates(true);
    		}
    	}
    
    	editor.replaceAll = replaceAll;
    
    	function replacePreviousText(text, replacement) {
    		var offset = selectionMgr.selectionStart;
    		if(offset !== selectionMgr.selectionEnd) {
    			return false;
    		}
    		var range = selectionMgr.createRange(offset - text.length, offset);
    		if('' + range != text) {
    			return false;
    		}
    		range.deleteContents();
    		range.insertNode(document.createTextNode(replacement));
    		offset = offset - text.length + replacement.length;
    		selectionMgr.setSelectionStartEnd(offset, offset);
    		selectionMgr.updateSelectionRange();
    		selectionMgr.updateCursorCoordinates(true);
    		return true;
    	}
    
    	editor.replacePreviousText = replacePreviousText;
    
    	function setValueNoWatch(value) {
    		setValue(value);
    		textContent = value;
    	}
    
    	editor.setValueNoWatch = setValueNoWatch;
    
    	function getValue() {
    		return textContent;
    	}
    
    	editor.getValue = getValue;
    	editor.getContent = getValue;
    	
    	function focus() {
    		$contentElt.focus();
    		selectionMgr.updateSelectionRange();
    		inputElt.scrollTop = scrollTop;
    	}
    
    	editor.focus = focus;
    
    	// 历史记录
    	function UndoMgr() {
    		var undoStack = [];
    		var redoStack = [];
    		var lastTime;
    		var lastMode;
    		var currentState;
    		var selectionStartBefore;
    		var selectionEndBefore;
    		this.setCommandMode = function() {
    			this.currentMode = 'command';
    		};
    		this.setMode = function() {
    		}; // For compatibility with PageDown
    		this.onButtonStateChange = function() {
    		}; // To be overridden by PageDown
    		this.saveState = utils.debounce(function() {
    			redoStack = [];
    			var currentTime = Date.now();
    			if(this.currentMode == 'comment' ||
    				this.currentMode == 'replace' ||
    				lastMode == 'newlines' ||
    				this.currentMode != lastMode ||
    				currentTime - lastTime > 1000) {
    				undoStack.push(currentState);
    				// Limit the size of the stack
    				while(undoStack.length > 100) {
    					undoStack.shift();
    				}
    			}
    			else {
    				// Restore selectionBefore that has potentially been modified by saveSelectionState
    				selectionStartBefore = currentState.selectionStartBefore;
    				selectionEndBefore = currentState.selectionEndBefore;
    			}
    			currentState = {
    				selectionStartBefore: selectionStartBefore,
    				selectionEndBefore: selectionEndBefore,
    				selectionStartAfter: selectionMgr.selectionStart,
    				selectionEndAfter: selectionMgr.selectionEnd,
    				content: textContent,
    				discussionListJSON: fileDesc.discussionListJSON
    			};
    			lastTime = currentTime;
    			lastMode = this.currentMode;
    			this.currentMode = undefined;
    			this.onButtonStateChange();
    		}, this);
    		this.saveSelectionState = _.debounce(function() {
    			// Should happen just after saveState
    			if(this.currentMode === undefined) {
    				selectionStartBefore = selectionMgr.selectionStart;
    				selectionEndBefore = selectionMgr.selectionEnd;
    			}
    		}, 50);
    		this.canUndo = function() {
    			return undoStack.length;
    		};
    		this.canRedo = function() {
    			return redoStack.length;
    		};
    		function restoreState(state, selectionStart, selectionEnd) {
    			// Update editor
    			watcher.noWatch(function() {
    				if(textContent != state.content) {
    					setValueNoWatch(state.content);
    					fileDesc.content = state.content;
    					eventMgr.onContentChanged(fileDesc, state.content);
    				}
    				selectionMgr.setSelectionStartEnd(selectionStart, selectionEnd);
    				selectionMgr.updateSelectionRange();
    				selectionMgr.updateCursorCoordinates(true);
    				/*
    				var discussionListJSON = fileDesc.discussionListJSON;
    				if(discussionListJSON != state.discussionListJSON) {
    					var oldDiscussionList = fileDesc.discussionList;
    					fileDesc.discussionListJSON = state.discussionListJSON;
    					var newDiscussionList = fileDesc.discussionList;
    					var diff = jsonDiffPatch.diff(oldDiscussionList, newDiscussionList);
    					var commentsChanged = false;
    					_.each(diff, function(discussionDiff, discussionIndex) {
    						if(!_.isArray(discussionDiff)) {
    							commentsChanged = true;
    						}
    						else if(discussionDiff.length === 1) {
    							eventMgr.onDiscussionCreated(fileDesc, newDiscussionList[discussionIndex]);
    						}
    						else {
    							eventMgr.onDiscussionRemoved(fileDesc, oldDiscussionList[discussionIndex]);
    						}
    					});
    					commentsChanged && eventMgr.onCommentsChanged(fileDesc);
    				}
    				*/
    			});
    
    			selectionStartBefore = selectionStart;
    			selectionEndBefore = selectionEnd;
    			currentState = state;
    			this.currentMode = undefined;
    			lastMode = undefined;
    			this.onButtonStateChange();
    			adjustCursorPosition();
    		}
    
    		this.undo = function() {
    			var state = undoStack.pop();
    			if(!state) {
    				return;
    			}
    			redoStack.push(currentState);
    			restoreState.call(this, state, currentState.selectionStartBefore, currentState.selectionEndBefore);
    		};
    		this.redo = function() {
    			var state = redoStack.pop();
    			if(!state) {
    				return;
    			}
    			undoStack.push(currentState);
    			restoreState.call(this, state, state.selectionStartAfter, state.selectionEndAfter);
    		};
    		this.init = function() {
    			var content = fileDesc.content;
    			undoStack = [];
    			redoStack = [];
    			lastTime = 0;
    			currentState = {
    				selectionStartAfter: fileDesc.selectionStart,
    				selectionEndAfter: fileDesc.selectionEnd,
    				content: content,
    				discussionListJSON: fileDesc.discussionListJSON
    			};
    			this.currentMode = undefined;
    			lastMode = undefined;
    			contentElt.textContent = content;
    			// Force this since the content could be the same
    			checkContentChange();
    		};
    	}
    
    	var undoMgr = new UndoMgr();
    	editor.undoMgr = undoMgr;
    
    	function onComment() {
    		if(watcher.isWatching === true) {
    			undoMgr.currentMode = undoMgr.currentMode || 'comment';
    			undoMgr.saveState();
    		}
    	}
    
    	eventMgr.addListener('onDiscussionCreated', onComment);
    	eventMgr.addListener('onDiscussionRemoved', onComment);
    	eventMgr.addListener('onCommentsChanged', onComment);
    
    	var triggerSpellCheck = _.debounce(function() {
    		var selection = window.getSelection();
    		if(!selectionMgr.hasFocus || isComposing || selectionMgr.selectionStart !== selectionMgr.selectionEnd || !selection.modify) {
    			return;
    		}
    		// Hack for Chrome to trigger the spell checker
    		if(selectionMgr.selectionStart) {
    			selection.modify("move", "backward", "character");
    			selection.modify("move", "forward", "character");
    		}
    		else {
    			selection.modify("move", "forward", "character");
    			selection.modify("move", "backward", "character");
    		}
    	}, 10);
    
    	function checkContentChange() {
    		var newTextContent = inputElt.textContent;
    		if(contentElt.lastChild === trailingLfNode && trailingLfNode.textContent.slice(-1) == '\n') {
    			newTextContent = newTextContent.slice(0, -1);
    		}
    		newTextContent = newTextContent.replace(/\r\n?/g, '\n'); // Mac/DOS to Unix
    
    		if(fileChanged === false) {
    			if(newTextContent == textContent) {
    				// User has removed the empty section
    				if(contentElt.children.length === 0) {
    					contentElt.innerHTML = '';
    					sectionList.forEach(function(section) {
    						contentElt.appendChild(section.elt);
    					});
    					addTrailingLfNode();
    				}
    				return;
    			}
    			undoMgr.currentMode = undoMgr.currentMode || 'typing';
    			var discussionList = _.values(fileDesc.discussionList);
    			fileDesc.newDiscussion && discussionList.push(fileDesc.newDiscussion);
    			// var updateDiscussionList = adjustCommentOffsets(textContent, newTextContent, discussionList);
    			textContent = newTextContent;
    			// if(updateDiscussionList === true) {
    			// 	fileDesc.discussionList = fileDesc.discussionList; // Write discussionList in localStorage
    			// }
    			fileDesc.content = textContent;
    			selectionMgr.saveSelectionState();
    			eventMgr.onContentChanged(fileDesc, textContent);
    
    			// updateDiscussionList && eventMgr.onCommentsChanged(fileDesc);
    
    			undoMgr.saveState();
    			triggerSpellCheck();
    		}
    		else {
    			textContent = newTextContent;
    			fileDesc.content = textContent;
    			selectionMgr.setSelectionStartEnd(fileDesc.editorStart, fileDesc.editorEnd);
    			selectionMgr.updateSelectionRange();
    			selectionMgr.updateCursorCoordinates();
    			undoMgr.saveSelectionState();
    			eventMgr.onFileOpen(fileDesc, textContent);
    			previewElt.scrollTop = fileDesc.previewScrollTop;
    			scrollTop = fileDesc.editorScrollTop;
    			inputElt.scrollTop = scrollTop;
    			fileChanged = false;
    		}
    	}
    
    	/*
    	function adjustCommentOffsets(oldTextContent, newTextContent, discussionList) {
    		if(!discussionList.length) {
    			return;
    		}
    		var changes = diffMatchPatch.diff_main(oldTextContent, newTextContent);
    		var changed = false;
    		var startOffset = 0;
    		changes.forEach(function(change) {
    			var changeType = change[0];
    			var changeText = change[1];
    			if(changeType === 0) {
    				startOffset += changeText.length;
    				return;
    			}
    			var endOffset = startOffset;
    			var diffOffset = changeText.length;
    			if(changeType === -1) {
    				endOffset += diffOffset;
    				diffOffset = -diffOffset;
    			}
    			discussionList.forEach(function(discussion) {
    				// selectionEnd
    				if(discussion.selectionEnd > endOffset) {
    					discussion.selectionEnd += diffOffset;
    					discussion.discussionIndex && (changed = true);
    				}
    				else if(discussion.selectionEnd > startOffset) {
    					discussion.selectionEnd = startOffset;
    					discussion.discussionIndex && (changed = true);
    				}
    				// selectionStart
    				if(discussion.selectionStart >= endOffset) {
    					discussion.selectionStart += diffOffset;
    					discussion.discussionIndex && (changed = true);
    				}
    				else if(discussion.selectionStart > startOffset) {
    					discussion.selectionStart = startOffset;
    					discussion.discussionIndex && (changed = true);
    				}
    			});
    			if(changeType === 1) {
    				startOffset += changeText.length;
    			}
    		});
    		return changed;
    	}
    
    	editor.adjustCommentOffsets = adjustCommentOffsets;
    	*
    	*/
    
    	// 入口
    	editor.init = function() {
    		inputElt = document.getElementById('wmd-input');
    		$inputElt = $(inputElt);
    		contentElt = inputElt.querySelector('.editor-content');
    		$contentElt = $(contentElt);
    		marginElt = inputElt.querySelector('.editor-margin');
    		$marginElt = $(marginElt);
    		previewElt = document.querySelector('.preview-container');
    
    		$inputElt.addClass(settings.editorFontClass);
    
    		watcher.startWatching();
    
    		$(inputElt).scroll(function() {
    			scrollTop = inputElt.scrollTop;
    			if(fileChanged === false) {
    				fileDesc.editorScrollTop = scrollTop;
    			}
    		});
    		$(previewElt).scroll(function() {
    			if(fileChanged === false) {
    				fileDesc.previewScrollTop = previewElt.scrollTop;
    			}
    		});
    
    		// See https://gist.github.com/shimondoodkin/1081133
    		if(/AppleWebKit\/([\d.]+)/.exec(navigator.userAgent)) {
    			var $editableFix = $('<input style="width:1px;height:1px;border:none;margin:0;padding:0;" tabIndex="-1">').appendTo('html');
    			$contentElt.blur(function() {
    				$editableFix[0].setSelectionRange(0, 0);
    				$editableFix.blur();
    			});
    		}
    
    		inputElt.focus = focus;
    		inputElt.adjustCursorPosition = adjustCursorPosition;
    
    		Object.defineProperty(inputElt, 'value', {
    			get: function() {
    				return textContent;
    			},
    			set: setValue
    		});
    
    		Object.defineProperty(inputElt, 'selectionStart', {
    			get: function() {
    				return Math.min(selectionMgr.selectionStart, selectionMgr.selectionEnd);
    			},
    			set: function(value) {
    				selectionMgr.setSelectionStartEnd(value);
    				selectionMgr.updateSelectionRange();
    				selectionMgr.updateCursorCoordinates();
    			},
    
    			enumerable: true,
    			configurable: true
    		});
    
    		Object.defineProperty(inputElt, 'selectionEnd', {
    			get: function() {
    				return Math.max(selectionMgr.selectionStart, selectionMgr.selectionEnd);
    			},
    			set: function(value) {
    				selectionMgr.setSelectionStartEnd(undefined, value);
    				selectionMgr.updateSelectionRange();
    				selectionMgr.updateCursorCoordinates();
    			},
    
    			enumerable: true,
    			configurable: true
    		});
    
    		var clearNewline = false;
    		$contentElt
    			.on('keydown', function(evt) {
    				if(
    					evt.which === 17 || // Ctrl
    					evt.which === 91 || // Cmd
    					evt.which === 18 || // Alt
    					evt.which === 16 // Shift
    					) {
    					return;
    				}
    				selectionMgr.saveSelectionState();
    				adjustCursorPosition();
    
    				var cmdOrCtrl = evt.metaKey || evt.ctrlKey;
    
    				switch(evt.which) {
    					case 9: // Tab
    						if(!cmdOrCtrl) {
    							action('indent', {
    								inverse: evt.shiftKey
    							});
    							evt.preventDefault();
    						}
    						break;
    					case 13:
    						// console.log('newline');
    						action('newline');
    						evt.preventDefault();
    						break;
    				}
    				if(evt.which !== 13) {
    					clearNewline = false;
    				}
    			})
    			// 当浏览器有非直接的文字输入时, compositionstart事件会以同步模式触发.
    			.on('compositionstart', function() {
    				// console.trace('compositionstart !!!!!');
    				isComposing++;
    			})
    			// 当浏览器是直接的文字输入时, compositionend会以同步模式触发.
    			// 中文输入完成后, 比如按空格时触发
    			// 为什么要异步-- ?
    			.on('compositionend', function(e) {
    				// console.log('compositionend !!')
    				// console.log(e);
    				// setTimeout(function() {
    					isComposing--;
    				// }, 0);
    			})
    			.on('mouseup', _.bind(selectionMgr.saveSelectionState, selectionMgr, true, false))
    			.on('paste', function(evt) {
    				undoMgr.currentMode = 'paste';
    				evt.preventDefault();
    				var data, clipboardData = (evt.originalEvent || evt).clipboardData;
    				if(clipboardData) {
    					data = clipboardData.getData('text/plain');
    				}
    				else {
    					clipboardData = window.clipboardData;
    					data = clipboardData && clipboardData.getData('Text');
    				}
    				if(!data) {
    					return;
    				}
    				replace(selectionMgr.selectionStart, selectionMgr.selectionEnd, data);
    				adjustCursorPosition();
    			})
    			.on('cut', function() {
    				undoMgr.currentMode = 'cut';
    				adjustCursorPosition();
    			})
    			.on('focus', function() {
    				selectionMgr.hasFocus = true;
    			})
    			.on('blur', function() {
    				selectionMgr.hasFocus = false;
    			});
    
    		var action = function(action, options) {
    			var textContent = getValue();
    			var min = Math.min(selectionMgr.selectionStart, selectionMgr.selectionEnd);
    			var max = Math.max(selectionMgr.selectionStart, selectionMgr.selectionEnd);
    			var state = {
    				selectionStart: min,
    				selectionEnd: max,
    				before: textContent.slice(0, min),
    				after: textContent.slice(max),
    				selection: textContent.slice(min, max)
    			};
    
    			actions[action](state, options || {});
    			setValue(state.before + state.selection + state.after);
    			selectionMgr.setSelectionStartEnd(state.selectionStart, state.selectionEnd);
    			selectionMgr.updateSelectionRange();
    		};
    
    		var indentRegex = /^ {0,3}>[ ]*|^[ \t]*(?:[*+\-]|(\d+)\.)[ \t]|^\s+/;
    		var actions = {
    			indent: function(state, options) {
    				function strSplice(str, i, remove, add) {
    					remove = +remove || 0;
    					add = add || '';
    					return str.slice(0, i) + add + str.slice(i + remove);
    				}
    
    				var lf = state.before.lastIndexOf('\n') + 1;
    				if(options.inverse) {
    					if(/\s/.test(state.before.charAt(lf))) {
    						state.before = strSplice(state.before, lf, 1);
    
    						state.selectionStart--;
    						state.selectionEnd--;
    					}
    					state.selection = state.selection.replace(/^[ \t]/gm, '');
    				} else {
    					var previousLine = state.before.slice(lf);
    					if(state.selection || previousLine.match(indentRegex)) {
    						state.before = strSplice(state.before, lf, 0, '\t');
    						state.selection = state.selection.replace(/\r?\n(?=[\s\S])/g, '\n\t');
    						state.selectionStart++;
    						state.selectionEnd++;
    					} else {
    						state.before += '\t';
    						state.selectionStart++;
    						state.selectionEnd++;
    						return;
    					}
    				}
    
    				state.selectionEnd = state.selectionStart + state.selection.length;
    			},
    
    			newline: function(state) {
    				var lf = state.before.lastIndexOf('\n') + 1;
    				if(clearNewline) {
    					state.before = state.before.substring(0, lf);
    					state.selection = '';
    					state.selectionStart = lf;
    					state.selectionEnd = lf;
    					clearNewline = false;
    					return;
    				}
    				clearNewline = false;
    				var previousLine = state.before.slice(lf);
    				var indentMatch = previousLine.match(indentRegex);
    				var indent = (indentMatch || [''])[0];
    				if(indentMatch && indentMatch[1]) {
    					var number = parseInt(indentMatch[1], 10);
    					indent = indent.replace(/\d+/, number + 1);
    				}
    				if(indent.length) {
    					clearNewline = true;
    				}
    
    				undoMgr.currentMode = 'newlines';
    
    				state.before += '\n' + indent;
    				state.selection = '';
    				state.selectionStart += indent.length + 1;
    				state.selectionEnd = state.selectionStart;
    			}
    		};
    
    		// 在init之前就有设置值的命令
    		if(contentQueue !== false) {
    			editor.setContent(contentQueue);
    		}
    	};
    
    	var sectionList = [];
    	var sectionsToRemove = [];
    	var modifiedSections = [];
    	var insertBeforeSection;
    
    	function updateSectionList(newSectionList) {
    
    		modifiedSections = [];
    		sectionsToRemove = [];
    		insertBeforeSection = undefined;
    
    		// Render everything if file changed
    		if(fileChanged === true) {
    			sectionsToRemove = sectionList;
    			sectionList = newSectionList;
    			modifiedSections = newSectionList;
    			return;
    		}
    
    		// Find modified section starting from top
    		var leftIndex = sectionList.length;
    		_.some(sectionList, function(section, index) {
    			var newSection = newSectionList[index];
    			if(index >= newSectionList.length ||
    				// Check modified
    				section.textWithFrontMatter != newSection.textWithFrontMatter ||
    				// Check that section has not been detached or moved
    				section.elt.parentNode !== contentElt ||
    				// Check also the content since nodes can be injected in sections via copy/paste
    				section.elt.textContent != newSection.textWithFrontMatter) {
    				leftIndex = index;
    				return true;
    			}
    		});
    
    		// Find modified section starting from bottom
    		var rightIndex = -sectionList.length;
    		_.some(sectionList.slice().reverse(), function(section, index) {
    			var newSection = newSectionList[newSectionList.length - index - 1];
    			if(index >= newSectionList.length ||
    				// Check modified
    				section.textWithFrontMatter != newSection.textWithFrontMatter ||
    				// Check that section has not been detached or moved
    				section.elt.parentNode !== contentElt ||
    				// Check also the content since nodes can be injected in sections via copy/paste
    				section.elt.textContent != newSection.textWithFrontMatter) {
    				rightIndex = -index;
    				return true;
    			}
    		});
    
    		if(leftIndex - rightIndex > sectionList.length) {
    			// Prevent overlap
    			rightIndex = leftIndex - sectionList.length;
    		}
    
    		// Create an array composed of left unmodified, modified, right
    		// unmodified sections
    		var leftSections = sectionList.slice(0, leftIndex);
    		modifiedSections = newSectionList.slice(leftIndex, newSectionList.length + rightIndex);
    		var rightSections = sectionList.slice(sectionList.length + rightIndex, sectionList.length);
    		insertBeforeSection = _.first(rightSections);
    		sectionsToRemove = sectionList.slice(leftIndex, sectionList.length + rightIndex);
    		sectionList = leftSections.concat(modifiedSections).concat(rightSections);
    	}
    
    	// 高亮预览
    	function highlightSections() {
    		var newSectionEltList = document.createDocumentFragment();
    		modifiedSections.forEach(function(section) {
    			// 高亮之
    			highlight(section);
    			newSectionEltList.appendChild(section.elt);
    		});
    		watcher.noWatch(function() {
    			if(fileChanged === true) {
    				contentElt.innerHTML = '';
    				contentElt.appendChild(newSectionEltList);
    			}
    			else {
    				// Remove outdated sections
    				sectionsToRemove.forEach(function(section) {
    					// section may be already removed
    					section.elt.parentNode === contentElt && contentElt.removeChild(section.elt);
    					// To detect sections that come back with built-in undo
    					section.elt.generated = false;
    				});
    
    				if(insertBeforeSection !== undefined) {
    					contentElt.insertBefore(newSectionEltList, insertBeforeSection.elt);
    				}
    				else {
    					contentElt.appendChild(newSectionEltList);
    				}
    
    				// Remove unauthorized nodes (text nodes outside of sections or duplicated sections via copy/paste)
    				var childNode = contentElt.firstChild;
    				while(childNode) {
    					var nextNode = childNode.nextSibling;
    					if(!childNode.generated) {
    						contentElt.removeChild(childNode);
    					}
    					childNode = nextNode;
    				}
    			}
    			addTrailingLfNode();
    			selectionMgr.updateSelectionRange();
    			// console.trace('.updateCursorCoordinates')
    			selectionMgr.updateCursorCoordinates();
    		});
    	}
    
    	function addTrailingLfNode() {
    		trailingLfNode = crel('span', {
    			class: 'token lf'
    		});
    		trailingLfNode.textContent = '\n';
    		contentElt.appendChild(trailingLfNode);
    	}
    
    	var escape = (function() {
    		var entityMap = {
    			"&": "&amp;",
    			"<": "&lt;",
    			"\u00a0": ' '
    		};
    		return function(str) {
    			return str.replace(/[&<\u00a0]/g, function(s) {
    				return entityMap[s];
    			});
    		};
    	})();
    
    	// 高亮用户所输入的
    	// 实现编辑器下预览
    	function highlight(section) {
    		var text = escape(section.text);
    
    		// MDPureText 不用Prism
    		if(!window.LEAMDPureText) {
    			// log("pre")
    			// log(text);
    			// # lif
    			// 如果想以纯文本显示, 请注释之
    			text = Prism.highlight(text, Prism.languages.md);
    			// log('after');
    			// <span class="token h1" ><span class="token md md-hash" >#</span> lif</span>
    			// log(text);
    		}
    
    		// 以下必须需要, 因为scrollSync需要wmd-input-section
    		var frontMatter = section.textWithFrontMatter.substring(0, section.textWithFrontMatter.length - section.text.length);
    		if(frontMatter.length) {
    			// Front matter highlighting
    			frontMatter = escape(frontMatter);
    			frontMatter = frontMatter.replace(/\n/g, '<span class="token lf">\n</span>');
    			text = '<span class="token md">' + frontMatter + '</span>' + text;
    		}
    		var sectionElt = crel('span', {
    			id: 'wmd-input-section-' + section.id,
    			class: 'wmd-input-section'
    		});
    		sectionElt.generated = true;
    		sectionElt.innerHTML = text;
    		section.elt = sectionElt;
    	}
    
    	eventMgr.onEditorCreated(editor);
    	return editor;
    });
    
    // needs Markdown.Converter.js at the moment
    
    (function () {
    
        var util = {},
            position = {},
            ui = {},
            doc = window.document,
            re = window.RegExp,
            nav = window.navigator,
            SETTINGS = { lineLength: 72 },
    
        // Used to work around some browser bugs where we can't use feature testing.
            uaSniffed = {
                isIE: /msie/.test(nav.userAgent.toLowerCase()),
                isIE_5or6: /msie 6/.test(nav.userAgent.toLowerCase()) || /msie 5/.test(nav.userAgent.toLowerCase()),
                isOpera: /opera/.test(nav.userAgent.toLowerCase())
            };
    
        var defaultsStrings = {
            bold: getMsg("Strong") + ' <strong> Ctrl/Cmd+B',
            boldexample: getMsg("strong text"),
    
            italic: getMsg("Emphasis") + ' <em> Ctrl/Cmd+I',
            italicexample: getMsg("emphasized text"),
    
            link: getMsg("Hyperlink") + ' <a> Ctrl/Cmd+L',
            linkdescription: getMsg("enter link description here"),
            linkdialog: "<p><b>Insert Hyperlink</b></p><p>http://example.com/ \"optional title\"</p>",
    
            quote: getMsg("Blockquote") + ' <blockquote> Ctrl/Cmd+Q',
            quoteexample: getMsg("Blockquote"),
    
            code: getMsg("Code Sample") + ' <pre><code> Ctrl/Cmd+K',
            codeexample: getMsg("enter code here"),
    
            image: getMsg("Image") + '<img> Ctrl/Cmd+G',
            imagedescription: getMsg("enter image description here"),
            imagedialog: "<p><b>Insert Image</b></p><p>http://example.com/images/diagram.jpg \"optional title\"<br><br>Need <a href='http://www.google.com/search?q=free+image+hosting' target='_blank'>free image hosting?</a></p>",
    
            olist: getMsg("Numbered List") +' <ol> Ctrl/Cmd+O',
            ulist: getMsg("Bulleted List") +' <ul> Ctrl/Cmd+U',
            litem: getMsg("List item"),
    
            heading: getMsg("Heading") + ' <h1>/<h2> Ctrl/Cmd+H',
            headingexample: getMsg("Heading"),
    
            hr: getMsg("Horizontal Rule") + ' <hr> Ctrl/Cmd+R',
    
            undo: getMsg("Undo") + ' - Ctrl/Cmd+Z',
            redo: getMsg("Redo") + ' - Ctrl/Cmd+Y',
    
            help: "Markdown Editing Help"
        };
    
        // -------------------------------------------------------------------
        //  YOUR CHANGES GO HERE
        //
        // I've tried to localize the things you are likely to change to
        // this area.
        // -------------------------------------------------------------------
    
        // The default text that appears in the dialog input box when entering
        // links.
        var imageDefaultText = "http://";
        var linkDefaultText = "http://";
    
        // -------------------------------------------------------------------
        //  END OF YOUR CHANGES
        // -------------------------------------------------------------------
    
        // options, if given, can have the following properties:
        //   options.helpButton = { handler: yourEventHandler }
        //   options.strings = { italicexample: "slanted text" }
        // `yourEventHandler` is the click handler for the help button.
        // If `options.helpButton` isn't given, not help button is created.
        // `options.strings` can have any or all of the same properties as
        // `defaultStrings` above, so you can just override some string displayed
        // to the user on a case-by-case basis, or translate all strings to
        // a different language.
        //
        // For backwards compatibility reasons, the `options` argument can also
        // be just the `helpButton` object, and `strings.help` can also be set via
        // `helpButton.title`. This should be considered legacy.
        //
        // The constructed editor object has the methods:
        // - getConverter() returns the markdown converter object that was passed to the constructor
        // - run() actually starts the editor; should be called after all necessary plugins are registered. Calling this more than once is a no-op.
        // - refreshPreview() forces the preview to be updated. This method is only available after run() was called.
        Markdown.Editor = function (markdownConverter, idPostfix, options) {
    
            options = options || {};
    
            if (typeof options.handler === "function") { //backwards compatible behavior
                options = { helpButton: options };
            }
            options.strings = options.strings || {};
            if (options.helpButton) {
                options.strings.help = options.strings.help || options.helpButton.title;
            }
            var getString = function (identifier) { return options.strings[identifier] || defaultsStrings[identifier]; }
    
            idPostfix = idPostfix || "";
    
            var hooks = this.hooks = new Markdown.HookCollection();
            hooks.addNoop("onPreviewRefresh");       // called with no arguments after the preview has been refreshed
            hooks.addNoop("postBlockquoteCreation"); // called with the user's selection *after* the blockquote was created; should return the actual to-be-inserted text
            hooks.addFalse("insertImageDialog");     /* called with one parameter: a callback to be called with the URL of the image. If the application creates
                                                      * its own image insertion dialog, this hook should return true, and the callback should be called with the chosen
                                                      * image url (or null if the user cancelled). If this hook returns false, the default dialog will be used.
                                                      */
            hooks.addFalse("insertLinkDialog");
    
            this.getConverter = function () { return markdownConverter; }
    
            var that = this,
                panels;
    
            var undoManager;
            this.run = function () {
                if (panels)
                    return; // already initialized
    
                panels = new PanelCollection(idPostfix);
                var commandManager = new CommandManager(hooks, getString);
                var previewManager = new PreviewManager(markdownConverter, panels, function () { hooks.onPreviewRefresh(); });
                var uiManager;
    
                if(options.undoManager) {
                    undoManager = options.undoManager;
                    undoManager.onButtonStateChange = function() {
                        uiManager.setUndoRedoButtonStates();
                    };
                    if (uiManager) // not available on the first call
                        uiManager.setUndoRedoButtonStates();
                }
                else if (!/\?noundo/.test(doc.location.href)) {
                    undoManager = new UndoManager(function () {
                        previewManager.refresh();
                        if (uiManager) // not available on the first call
                            uiManager.setUndoRedoButtonStates();
                    }, panels);
                    this.textOperation = function (f) {
                        undoManager.setCommandMode();
                        f();
                        that.refreshPreview();
                    }
                }
    
                uiManager = new UIManager(idPostfix, panels, undoManager, previewManager, commandManager, options.helpButton, getString);
                uiManager.setUndoRedoButtonStates();
    
                var forceRefresh = that.refreshPreview = function () { previewManager.refresh(true); };
    
                //Not necessary
                //forceRefresh();
                that.undoManager = undoManager;
                that.uiManager = uiManager;
            };
    
        }
    
        // before: contains all the text in the input box BEFORE the selection.
        // after: contains all the text in the input box AFTER the selection.
        function Chunks() { }
    
        // startRegex: a regular expression to find the start tag
        // endRegex: a regular expresssion to find the end tag
        Chunks.prototype.findTags = function (startRegex, endRegex) {
    
            var chunkObj = this;
            var regex;
    
            if (startRegex) {
    
                regex = util.extendRegExp(startRegex, "", "$");
    
                this.before = this.before.replace(regex,
                    function (match) {
                        chunkObj.startTag = chunkObj.startTag + match;
                        return "";
                    });
    
                regex = util.extendRegExp(startRegex, "^", "");
    
                this.selection = this.selection.replace(regex,
                    function (match) {
                        chunkObj.startTag = chunkObj.startTag + match;
                        return "";
                    });
            }
    
            if (endRegex) {
    
                regex = util.extendRegExp(endRegex, "", "$");
    
                this.selection = this.selection.replace(regex,
                    function (match) {
                        chunkObj.endTag = match + chunkObj.endTag;
                        return "";
                    });
    
                regex = util.extendRegExp(endRegex, "^", "");
    
                this.after = this.after.replace(regex,
                    function (match) {
                        chunkObj.endTag = match + chunkObj.endTag;
                        return "";
                    });
            }
        };
    
        // If remove is false, the whitespace is transferred
        // to the before/after regions.
        //
        // If remove is true, the whitespace disappears.
        Chunks.prototype.trimWhitespace = function (remove) {
            var beforeReplacer, afterReplacer, that = this;
            if (remove) {
                beforeReplacer = afterReplacer = "";
            } else {
                beforeReplacer = function (s) { that.before += s; return ""; }
                afterReplacer = function (s) { that.after = s + that.after; return ""; }
            }
    
            this.selection = this.selection.replace(/^(\s*)/, beforeReplacer).replace(/(\s*)$/, afterReplacer);
        };
    
    
        Chunks.prototype.skipLines = function (nLinesBefore, nLinesAfter, findExtraNewlines) {
    
            if (nLinesBefore === undefined) {
                nLinesBefore = 1;
            }
    
            if (nLinesAfter === undefined) {
                nLinesAfter = 1;
            }
    
            nLinesBefore++;
            nLinesAfter++;
    
            var regexText;
            var replacementText;
    
            // chrome bug ... documented at: http://meta.stackoverflow.com/questions/63307/blockquote-glitch-in-editor-in-chrome-6-and-7/65985#65985
            if (navigator.userAgent.match(/Chrome/)) {
                "X".match(/()./);
            }
    
            this.selection = this.selection.replace(/(^\n*)/, "");
    
            this.startTag = this.startTag + re.$1;
    
            this.selection = this.selection.replace(/(\n*$)/, "");
            this.endTag = this.endTag + re.$1;
            this.startTag = this.startTag.replace(/(^\n*)/, "");
            this.before = this.before + re.$1;
            this.endTag = this.endTag.replace(/(\n*$)/, "");
            this.after = this.after + re.$1;
    
            if (this.before) {
    
                regexText = replacementText = "";
    
                while (nLinesBefore--) {
                    regexText += "\\n?";
                    replacementText += "\n";
                }
    
                if (findExtraNewlines) {
                    regexText = "\\n*";
                }
                this.before = this.before.replace(new re(regexText + "$", ""), replacementText);
            }
    
            if (this.after) {
    
                regexText = replacementText = "";
    
                while (nLinesAfter--) {
                    regexText += "\\n?";
                    replacementText += "\n";
                }
                if (findExtraNewlines) {
                    regexText = "\\n*";
                }
    
                this.after = this.after.replace(new re(regexText, ""), replacementText);
            }
        };
    
        // end of Chunks
    
        // A collection of the important regions on the page.
        // Cached so we don't have to keep traversing the DOM.
        // Also holds ieCachedRange and ieCachedScrollTop, where necessary; working around
        // this issue:
        // Internet explorer has problems with CSS sprite buttons that use HTML
        // lists.  When you click on the background image "button", IE will
        // select the non-existent link text and discard the selection in the
        // textarea.  The solution to this is to cache the textarea selection
        // on the button's mousedown event and set a flag.  In the part of the
        // code where we need to grab the selection, we check for the flag
        // and, if it's set, use the cached area instead of querying the
        // textarea.
        //
        // This ONLY affects Internet Explorer (tested on versions 6, 7
        // and 8) and ONLY on button clicks.  Keyboard shortcuts work
        // normally since the focus never leaves the textarea.
        function PanelCollection(postfix) {
            this.buttonBar = doc.getElementById("wmd-button-bar" + postfix);
            this.preview = doc.getElementById("wmd-preview" + postfix);
            this.input = doc.getElementById("wmd-input" + postfix);
        };
    
        // Returns true if the DOM element is visible, false if it's hidden.
        // Checks if display is anything other than none.
        util.isVisible = function (elem) {
    
            if (window.getComputedStyle) {
                // Most browsers
                return window.getComputedStyle(elem, null).getPropertyValue("display") !== "none";
            }
            else if (elem.currentStyle) {
                // IE
                return elem.currentStyle["display"] !== "none";
            }
        };
    
    
        // Adds a listener callback to a DOM element which is fired on a specified
        // event.
        util.addEvent = function (elem, event, listener) {
            if (elem.attachEvent) {
                // IE only.  The "on" is mandatory.
                elem.attachEvent("on" + event, listener);
            }
            else {
                // Other browsers.
                elem.addEventListener(event, listener, false);
            }
        };
    
    
        // Removes a listener callback from a DOM element which is fired on a specified
        // event.
        util.removeEvent = function (elem, event, listener) {
            if (elem.detachEvent) {
                // IE only.  The "on" is mandatory.
                elem.detachEvent("on" + event, listener);
            }
            else {
                // Other browsers.
                elem.removeEventListener(event, listener, false);
            }
        };
    
        // Converts \r\n and \r to \n.
        util.fixEolChars = function (text) {
            text = text.replace(/\r\n/g, "\n");
            text = text.replace(/\r/g, "\n");
            return text;
        };
    
        // Extends a regular expression.  Returns a new RegExp
        // using pre + regex + post as the expression.
        // Used in a few functions where we have a base
        // expression and we want to pre- or append some
        // conditions to it (e.g. adding "$" to the end).
        // The flags are unchanged.
        //
        // regex is a RegExp, pre and post are strings.
        util.extendRegExp = function (regex, pre, post) {
    
            if (pre === null || pre === undefined) {
                pre = "";
            }
            if (post === null || post === undefined) {
                post = "";
            }
    
            var pattern = regex.toString();
            var flags;
    
            // Replace the flags with empty space and store them.
            pattern = pattern.replace(/\/([gim]*)$/, function (wholeMatch, flagsPart) {
                flags = flagsPart;
                return "";
            });
    
            // Remove the slash delimiters on the regular expression.
            pattern = pattern.replace(/(^\/|\/$)/g, "");
            pattern = pre + pattern + post;
    
            return new re(pattern, flags);
        }
    
        // UNFINISHED
        // The assignment in the while loop makes jslint cranky.
        // I'll change it to a better loop later.
        position.getTop = function (elem, isInner) {
            var result = elem.offsetTop;
            if (!isInner) {
                while (elem = elem.offsetParent) {
                    result += elem.offsetTop;
                }
            }
            return result;
        };
    
        position.getHeight = function (elem) {
            return elem.offsetHeight || elem.scrollHeight;
        };
    
        position.getWidth = function (elem) {
            return elem.offsetWidth || elem.scrollWidth;
        };
    
        position.getPageSize = function () {
    
            var scrollWidth, scrollHeight;
            var innerWidth, innerHeight;
    
            // It's not very clear which blocks work with which browsers.
            if (self.innerHeight && self.scrollMaxY) {
                scrollWidth = doc.body.scrollWidth;
                scrollHeight = self.innerHeight + self.scrollMaxY;
            }
            else if (doc.body.scrollHeight > doc.body.offsetHeight) {
                scrollWidth = doc.body.scrollWidth;
                scrollHeight = doc.body.scrollHeight;
            }
            else {
                scrollWidth = doc.body.offsetWidth;
                scrollHeight = doc.body.offsetHeight;
            }
    
            if (self.innerHeight) {
                // Non-IE browser
                innerWidth = self.innerWidth;
                innerHeight = self.innerHeight;
            }
            else if (doc.documentElement && doc.documentElement.clientHeight) {
                // Some versions of IE (IE 6 w/ a DOCTYPE declaration)
                innerWidth = doc.documentElement.clientWidth;
                innerHeight = doc.documentElement.clientHeight;
            }
            else if (doc.body) {
                // Other versions of IE
                innerWidth = doc.body.clientWidth;
                innerHeight = doc.body.clientHeight;
            }
    
            var maxWidth = Math.max(scrollWidth, innerWidth);
            var maxHeight = Math.max(scrollHeight, innerHeight);
            return [maxWidth, maxHeight, innerWidth, innerHeight];
        };
    
        // Handles pushing and popping TextareaStates for undo/redo commands.
        // I should rename the stack variables to list.
        function UndoManager(callback, panels) {
    
            var undoObj = this;
            var undoStack = []; // A stack of undo states
            var stackPtr = 0; // The index of the current state
            var mode = "none";
            var lastState; // The last state
            var timer; // The setTimeout handle for cancelling the timer
            var inputStateObj;
    
            // Set the mode for later logic steps.
            var setMode = function (newMode, noSave) {
                if (mode != newMode) {
                    mode = newMode;
                    if (!noSave) {
                        saveState();
                    }
                }
    
                if (!uaSniffed.isIE || mode != "moving") {
                    timer = setTimeout(refreshState, 1);
                }
                else {
                    inputStateObj = null;
                }
            };
    
            var refreshState = function (isInitialState) {
                inputStateObj = new TextareaState(panels, isInitialState);
                timer = undefined;
            };
    
            this.setCommandMode = function () {
                mode = "command";
                saveState();
                timer = setTimeout(refreshState, 0);
            };
    
            this.canUndo = function () {
                return stackPtr > 1;
            };
    
            this.canRedo = function () {
                if (undoStack[stackPtr + 1]) {
                    return true;
                }
                return false;
            };
    
            // Removes the last state and restores it.
            this.undo = function () {
    
                if (undoObj.canUndo()) {
                    if (lastState) {
                        // What about setting state -1 to null or checking for undefined?
                        lastState.restore();
                        lastState = null;
                    }
                    else {
                        undoStack[stackPtr] = new TextareaState(panels);
                        undoStack[--stackPtr].restore();
    
                        if (callback) {
                            callback();
                        }
                    }
                }
    
                mode = "none";
                panels.input.focus();
                refreshState();
            };
    
            // Redo an action.
            this.redo = function () {
    
                if (undoObj.canRedo()) {
    
                    undoStack[++stackPtr].restore();
    
                    if (callback) {
                        callback();
                    }
                }
    
                mode = "none";
                panels.input.focus();
                refreshState();
            };
    
            // Push the input area state to the stack.
            var saveState = function () {
                var currState = inputStateObj || new TextareaState(panels);
    
                if (!currState) {
                    return false;
                }
                if (mode == "moving") {
                    if (!lastState) {
                        lastState = currState;
                    }
                    return;
                }
                if (lastState) {
                    if (undoStack[stackPtr - 1].text != lastState.text) {
                        undoStack[stackPtr++] = lastState;
                    }
                    lastState = null;
                }
                undoStack[stackPtr++] = currState;
                undoStack[stackPtr + 1] = null;
                if (callback) {
                    callback();
                }
            };
    
            var handleCtrlYZ = function (event) {
    
                var handled = false;
    
                if ((event.ctrlKey || event.metaKey) && !event.altKey) {
    
                    // IE and Opera do not support charCode.
                    var keyCode = event.charCode || event.keyCode;
                    var keyCodeChar = String.fromCharCode(keyCode);
    
                    switch (keyCodeChar.toLowerCase()) {
    
                        case "y":
                            undoObj.redo();
                            handled = true;
                            break;
    
                        case "z":
                            if (!event.shiftKey) {
                                undoObj.undo();
                            }
                            else {
                                undoObj.redo();
                            }
                            handled = true;
                            break;
                    }
                }
    
                if (handled) {
                    if (event.preventDefault) {
                        event.preventDefault();
                    }
                    if (window.event) {
                        window.event.returnValue = false;
                    }
                    return;
                }
            };
    
            // Set the mode depending on what is going on in the input area.
            var handleModeChange = function (event) {
    
                if (!event.ctrlKey && !event.metaKey) {
    
                    var keyCode = event.keyCode;
    
                    if ((keyCode >= 33 && keyCode <= 40) || (keyCode >= 63232 && keyCode <= 63235)) {
                        // 33 - 40: page up/dn and arrow keys
                        // 63232 - 63235: page up/dn and arrow keys on safari
                        setMode("moving");
                    }
                    else if (keyCode == 8 || keyCode == 46 || keyCode == 127) {
                        // 8: backspace
                        // 46: delete
                        // 127: delete
                        setMode("deleting");
                    }
                    else if (keyCode == 13) {
                        // 13: Enter
                        setMode("newlines");
                    }
                    else if (keyCode == 27) {
                        // 27: escape
                        setMode("escape");
                    }
                    else if ((keyCode < 16 || keyCode > 20) && keyCode != 91) {
                        // 16-20 are shift, etc.
                        // 91: left window key
                        // I think this might be a little messed up since there are
                        // a lot of nonprinting keys above 20.
                        setMode("typing");
                    }
                }
            };
    
            var setEventHandlers = function () {
                util.addEvent(panels.input, "keypress", function (event) {
                    // keyCode 89: y
                    // keyCode 90: z
                    if ((event.ctrlKey || event.metaKey) && !event.altKey && (event.keyCode == 89 || event.keyCode == 90)) {
                        event.preventDefault();
                    }
                });
    
                var handlePaste = function () {
                    if (uaSniffed.isIE || (inputStateObj && inputStateObj.text != panels.input.value)) {
                        if (timer == undefined) {
                            mode = "paste";
                            saveState();
                            refreshState();
                        }
                    }
                };
    
                //util.addEvent(panels.input, "keydown", handleCtrlYZ);
                util.addEvent(panels.input, "keydown", handleModeChange);
                util.addEvent(panels.input, "mousedown", function () {
                    setMode("moving");
                });
    
                panels.input.onpaste = handlePaste;
                panels.input.ondrop = handlePaste;
            };
    
            var init = function () {
                setEventHandlers();
                refreshState(true);
                //Not necessary
                //saveState();
            };
    
            this.reinit = function(content, start, end, scrollTop) {
                undoStack = [];
                stackPtr = 0;
                mode = "none";
                lastState = undefined;
                timer = undefined;
                refreshState();
                inputStateObj.text = content;
                inputStateObj.start = start;
                inputStateObj.end = end;
                inputStateObj.scrollTop = scrollTop;
                inputStateObj.setInputAreaSelection();
                saveState();
            };
            this.setMode = setMode;
    
            init();
        }
    
        // end of UndoManager
    
        // The input textarea state/contents.
        // This is used to implement undo/redo by the undo manager.
        function TextareaState(panels, isInitialState) {
    
            // Aliases
            var stateObj = this;
            var inputArea = panels.input;
            this.init = function () {
                if (!util.isVisible(inputArea)) {
                    return;
                }
    
                this.setInputAreaSelectionStartEnd();
                this.scrollTop = inputArea.scrollTop;
                if (!this.text && inputArea.selectionStart || inputArea.selectionStart === 0) {
                    this.text = inputArea.value;
                }
    
            }
    
            // Sets the selected text in the input box after we've performed an
            // operation.
            this.setInputAreaSelection = function () {
    
                if (!util.isVisible(inputArea)) {
                    return;
                }
    
                //if (inputArea.selectionStart !== undefined && !uaSniffed.isOpera) {
    
                    inputArea.focus();
                    inputArea.selectionStart = stateObj.start;
                    inputArea.selectionEnd = stateObj.end;
    	        /*
                    inputArea.scrollTop = stateObj.scrollTop;
    
                }
                else if (doc.selection) {
    
                    inputArea.focus();
                    var range = inputArea.createTextRange();
                    range.moveStart("character", -inputArea.value.length);
                    range.moveEnd("character", -inputArea.value.length);
                    range.moveEnd("character", stateObj.end);
                    range.moveStart("character", stateObj.start);
                    range.select();
                }
                */
            };
    
            this.setInputAreaSelectionStartEnd = function () {
    
                //if (!panels.ieCachedRange && (inputArea.selectionStart || inputArea.selectionStart === 0)) {
    
                    stateObj.start = inputArea.selectionStart;
                    stateObj.end = inputArea.selectionEnd;
                    /*
                }
                else if (doc.selection) {
    
                    stateObj.text = util.fixEolChars(inputArea.value);
    
                    // IE loses the selection in the textarea when buttons are
                    // clicked.  On IE we cache the selection. Here, if something is cached,
                    // we take it.
                    var range = panels.ieCachedRange || doc.selection.createRange();
    
                    var fixedRange = util.fixEolChars(range.text);
                    var marker = "\x07";
                    var markedRange = marker + fixedRange + marker;
                    range.text = markedRange;
                    var inputText = util.fixEolChars(inputArea.value);
    
                    range.moveStart("character", -markedRange.length);
                    range.text = fixedRange;
    
                    stateObj.start = inputText.indexOf(marker);
                    stateObj.end = inputText.lastIndexOf(marker) - marker.length;
    
                    var len = stateObj.text.length - util.fixEolChars(inputArea.value).length;
    
                    if (len) {
                        range.moveStart("character", -fixedRange.length);
                        while (len--) {
                            fixedRange += "\n";
                            stateObj.end += 1;
                        }
                        range.text = fixedRange;
                    }
    
                    if (panels.ieCachedRange)
                        stateObj.scrollTop = panels.ieCachedScrollTop; // this is set alongside with ieCachedRange
    
                    panels.ieCachedRange = null;
    
                    this.setInputAreaSelection();
                }
                */
            };
    
            // Restore this state into the input area.
            this.restore = function () {
    
                if (stateObj.text != undefined && stateObj.text != inputArea.value) {
                    inputArea.value = stateObj.text;
                }
                this.setInputAreaSelection();
    	        /*
                setTimeout(function() {
                    inputArea.scrollTop = stateObj.scrollTop;
                }, 0);
                */
            };
    
            // Gets a collection of HTML chunks from the inptut textarea.
            this.getChunks = function () {
    
                var chunk = new Chunks();
                chunk.before = util.fixEolChars(stateObj.text.substring(0, stateObj.start));
                chunk.startTag = "";
                chunk.selection = util.fixEolChars(stateObj.text.substring(stateObj.start, stateObj.end));
                chunk.endTag = "";
                chunk.after = util.fixEolChars(stateObj.text.substring(stateObj.end));
                chunk.scrollTop = stateObj.scrollTop;
    
                return chunk;
            };
    
            // Sets the TextareaState properties given a chunk of markdown.
            this.setChunks = function (chunk) {
    
                chunk.before = chunk.before + chunk.startTag;
                chunk.after = chunk.endTag + chunk.after;
    
                this.start = chunk.before.length;
                this.end = chunk.before.length + chunk.selection.length;
                this.text = chunk.before + chunk.selection + chunk.after;
                this.scrollTop = chunk.scrollTop;
            };
            this.init();
        };
    
        function PreviewManager(converter, panels, previewRefreshCallback) {
    
            var managerObj = this;
            var timeout;
            var elapsedTime;
            var oldInputText;
            var maxDelay = 3000;
            var startType = "manual"; // The other legal value is "manual"
    
            // Adds event listeners to elements
            var setupEvents = function (inputElem, listener) {
    
                util.addEvent(inputElem, "input", listener);
                inputElem.onpaste = listener;
                inputElem.ondrop = listener;
    
                util.addEvent(inputElem, "keypress", listener);
                util.addEvent(inputElem, "keydown", listener);
            };
    
            var getDocScrollTop = function () {
    
                var result = 0;
    
                if (window.innerHeight) {
                    result = window.pageYOffset;
                }
                else
                    if (doc.documentElement && doc.documentElement.scrollTop) {
                        result = doc.documentElement.scrollTop;
                    }
                    else
                        if (doc.body) {
                            result = doc.body.scrollTop;
                        }
    
                return result;
            };
    
            var makePreviewHtml = function () {
    
                // If there is no registered preview panel
                // there is nothing to do.
                if (!panels.preview)
                    return;
    
    
                var text = panels.input.value;
                if (text && text == oldInputText) {
                    return; // Input text hasn't changed.
                }
                else {
                    oldInputText = text;
                }
    
                var prevTime = new Date().getTime();
    
                text = converter.makeHtml(text);
    
                // Calculate the processing time of the HTML creation.
                // It's used as the delay time in the event listener.
                var currTime = new Date().getTime();
                elapsedTime = currTime - prevTime;
    
                pushPreviewHtml(text);
            };
    
            // setTimeout is already used.  Used as an event listener.
            var applyTimeout = function () {
    
                if (timeout) {
                    clearTimeout(timeout);
                    timeout = undefined;
                }
    
                if (startType !== "manual") {
    
                    var delay = 0;
    
                    if (startType === "delayed") {
                        delay = elapsedTime;
                    }
    
                    if (delay > maxDelay) {
                        delay = maxDelay;
                    }
                    timeout = setTimeout(makePreviewHtml, delay);
                }
            };
    
            var getScaleFactor = function (panel) {
                if (panel.scrollHeight <= panel.clientHeight) {
                    return 1;
                }
                return panel.scrollTop / (panel.scrollHeight - panel.clientHeight);
            };
    
            var setPanelScrollTops = function () {
                if (panels.preview) {
                    panels.preview.scrollTop = (panels.preview.scrollHeight - panels.preview.clientHeight) * getScaleFactor(panels.preview);
                }
            };
    
            this.refresh = function (requiresRefresh) {
    
                if (requiresRefresh) {
                    oldInputText = "";
                    makePreviewHtml();
                }
                else {
                    applyTimeout();
                }
            };
    
            this.processingTime = function () {
                return elapsedTime;
            };
    
            var isFirstTimeFilled = true;
    
            // IE doesn't let you use innerHTML if the element is contained somewhere in a table
            // (which is the case for inline editing) -- in that case, detach the element, set the
            // value, and reattach. Yes, that *is* ridiculous.
            var ieSafePreviewSet = function (text) {
                var preview = panels.preview;
                var parent = preview.parentNode;
                var sibling = preview.nextSibling;
                parent.removeChild(preview);
                preview.innerHTML = text;
                if (!sibling)
                    parent.appendChild(preview);
                else
                    parent.insertBefore(preview, sibling);
            }
    
            var nonSuckyBrowserPreviewSet = function (text) {
                panels.preview.innerHTML = text;
            }
    
            var previewSetter;
    
            var previewSet = function (text) {
                if (previewSetter)
                    return previewSetter(text);
    
                try {
                    nonSuckyBrowserPreviewSet(text);
                    previewSetter = nonSuckyBrowserPreviewSet;
                } catch (e) {
                    previewSetter = ieSafePreviewSet;
                    previewSetter(text);
                }
            };
    
            var pushPreviewHtml = function (text) {
    
                //var emptyTop = position.getTop(panels.input) - getDocScrollTop();
    
                if (panels.preview) {
                    previewSet(text);
                    previewRefreshCallback();
                }
                /*
    
                setPanelScrollTops();
    
                if (isFirstTimeFilled) {
                    isFirstTimeFilled = false;
                    return;
                }
    
                var fullTop = position.getTop(panels.input) - getDocScrollTop();
    
                if (uaSniffed.isIE) {
                    setTimeout(function () {
                        window.scrollBy(0, fullTop - emptyTop);
                    }, 0);
                }
                else {
                    window.scrollBy(0, fullTop - emptyTop);
                }
                */
            };
    
            var init = function () {
    
                setupEvents(panels.input, applyTimeout);
                //Not necessary
                //makePreviewHtml();
    
                if (panels.preview) {
                    panels.preview.scrollTop = 0;
                }
            };
    
            init();
        };
    
        // Creates the background behind the hyperlink text entry box.
        // And download dialog
        // Most of this has been moved to CSS but the div creation and
        // browser-specific hacks remain here.
        ui.createBackground = function () {
    
            var background = doc.createElement("div"),
                style = background.style;
    
            background.className = "wmd-prompt-background";
    
            style.position = "absolute";
            style.top = "0";
    
            style.zIndex = "1000";
    
            if (uaSniffed.isIE) {
                style.filter = "alpha(opacity=50)";
            }
            else {
                style.opacity = "0.5";
            }
    
            var pageSize = position.getPageSize();
            style.height = pageSize[1] + "px";
    
            if (uaSniffed.isIE) {
                style.left = doc.documentElement.scrollLeft;
                style.width = doc.documentElement.clientWidth;
            }
            else {
                style.left = "0";
                style.width = "100%";
            }
    
            doc.body.appendChild(background);
            return background;
        };
    
        // This simulates a modal dialog box and asks for the URL when you
        // click the hyperlink or image buttons.
        //
        // text: The html for the input box.
        // defaultInputText: The default value that appears in the input box.
        // callback: The function which is executed when the prompt is dismissed, either via OK or Cancel.
        //      It receives a single argument; either the entered text (if OK was chosen) or null (if Cancel
        //      was chosen).
        ui.prompt = function (text, defaultInputText, callback) {
    
            // These variables need to be declared at this level since they are used
            // in multiple functions.
            var dialog;         // The dialog box.
            var input;         // The text box where you enter the hyperlink.
    
    
            if (defaultInputText === undefined) {
                defaultInputText = "";
            }
    
            // Used as a keydown event handler. Esc dismisses the prompt.
            // Key code 27 is ESC.
            var checkEscape = function (key) {
                var code = (key.charCode || key.keyCode);
                if (code === 27) {
                    close(true);
                }
            };
    
            // Dismisses the hyperlink input box.
            // isCancel is true if we don't care about the input text.
            // isCancel is false if we are going to keep the text.
            var close = function (isCancel) {
                util.removeEvent(doc.body, "keydown", checkEscape);
                var text = input.value;
    
                if (isCancel) {
                    text = null;
                }
                else {
                    // Fixes common pasting errors.
                    text = text.replace(/^http:\/\/(https?|ftp):\/\//, '$1://');
                    if (!/^(?:https?|ftp):\/\//.test(text))
                        text = 'http://' + text;
                }
    
                dialog.parentNode.removeChild(dialog);
    
                callback(text);
                return false;
            };
    
    
    
            // Create the text input box form/window.
            var createDialog = function () {
    
                // The main dialog box.
                dialog = doc.createElement("div");
                dialog.className = "wmd-prompt-dialog";
                dialog.style.padding = "10px;";
                dialog.style.position = "fixed";
                dialog.style.width = "400px";
                dialog.style.zIndex = "1001";
    
                // The dialog text.
                var question = doc.createElement("div");
                question.innerHTML = text;
                question.style.padding = "5px";
                dialog.appendChild(question);
    
                // The web form container for the text box and buttons.
                var form = doc.createElement("form"),
                    style = form.style;
                form.onsubmit = function () { return close(false); };
                style.padding = "0";
                style.margin = "0";
                style.cssFloat = "left";
                style.width = "100%";
                style.textAlign = "center";
                style.position = "relative";
                dialog.appendChild(form);
    
                // The input text box
                input = doc.createElement("input");
                input.type = "text";
                input.value = defaultInputText;
                style = input.style;
                style.display = "block";
                style.width = "80%";
                style.marginLeft = style.marginRight = "auto";
                form.appendChild(input);
    
                // The ok button
                var okButton = doc.createElement("input");
                okButton.type = "button";
                okButton.onclick = function () { return close(false); };
                okButton.value = "OK";
                style = okButton.style;
                style.margin = "10px";
                style.display = "inline";
                style.width = "7em";
    
    
                // The cancel button
                var cancelButton = doc.createElement("input");
                cancelButton.type = "button";
                cancelButton.onclick = function () { return close(true); };
                cancelButton.value = "Cancel";
                style = cancelButton.style;
                style.margin = "10px";
                style.display = "inline";
                style.width = "7em";
    
                form.appendChild(okButton);
                form.appendChild(cancelButton);
    
                util.addEvent(doc.body, "keydown", checkEscape);
                dialog.style.top = "50%";
                dialog.style.left = "50%";
                dialog.style.display = "block";
                if (uaSniffed.isIE_5or6) {
                    dialog.style.position = "absolute";
                    dialog.style.top = doc.documentElement.scrollTop + 200 + "px";
                    dialog.style.left = "50%";
                }
                doc.body.appendChild(dialog);
    
                // This has to be done AFTER adding the dialog to the form if you
                // want it to be centered.
                dialog.style.marginTop = -(position.getHeight(dialog) / 2) + "px";
                dialog.style.marginLeft = -(position.getWidth(dialog) / 2) + "px";
    
            };
    
            // Why is this in a zero-length timeout?
            // Is it working around a browser bug?
            setTimeout(function () {
    
                createDialog();
    
                var defTextLen = defaultInputText.length;
                if (input.selectionStart !== undefined) {
                    input.selectionStart = 0;
                    input.selectionEnd = defTextLen;
                }
                else if (input.createTextRange) {
                    var range = input.createTextRange();
                    range.collapse(false);
                    range.moveStart("character", -defTextLen);
                    range.moveEnd("character", defTextLen);
                    range.select();
                }
    
                input.focus();
            }, 0);
        };
    
        function UIManager(postfix, panels, undoManager, previewManager, commandManager, helpOptions, getString) {
    
            var inputBox = panels.input,
                buttons = {}; // buttons.undo, buttons.link, etc. The actual DOM elements.
    
            makeSpritedButtonRow();
    
            var keyEvent = "keydown";
            if (uaSniffed.isOpera) {
                keyEvent = "keypress";
            }
    
            /*
            util.addEvent(inputBox, keyEvent, function (key) {
    
                // Check to see if we have a button key and, if so execute the callback.
                if ((key.ctrlKey || key.metaKey) && !key.altKey) {
    
                    var keyCode = key.charCode || key.keyCode;
                    var keyCodeStr = String.fromCharCode(keyCode).toLowerCase();
    
                    switch (keyCodeStr) {
                        case "b":
                            doClick(buttons.bold);
                            break;
                        case "i":
                            doClick(buttons.italic);
                            break;
                        case "l":
                            doClick(buttons.link);
                            break;
                        case "q":
                            doClick(buttons.quote);
                            break;
                        case "k":
                            doClick(buttons.code);
                            break;
                        case "g":
                            doClick(buttons.image);
                            break;
                        case "o":
                            doClick(buttons.olist);
                            break;
                        case "u":
                            doClick(buttons.ulist);
                            break;
                        case "h":
                            doClick(buttons.heading);
                            break;
                        case "r":
                            doClick(buttons.hr);
                            break;
                        case "y":
                            doClick(buttons.redo);
                            break;
                        case "z":
                            if (key.shiftKey) {
                                doClick(buttons.redo);
                            }
                            else {
                                doClick(buttons.undo);
                            }
                            break;
                        case "v":
                            undoManager.setMode("typing");
                            return;
                        case "x":
                            undoManager.setMode("deleting");
                            return;
                        default:
                            return;
                    }
    
    
                    if (key.preventDefault) {
                        key.preventDefault();
                    }
    
                    if (window.event) {
                        window.event.returnValue = false;
                    }
                }
            });
    
            // Auto-indent on shift-enter
            util.addEvent(inputBox, "keyup", function (key) {
                if (key.shiftKey && !key.ctrlKey && !key.metaKey) {
                    var keyCode = key.charCode || key.keyCode;
                    // Character 13 is Enter
                    if (keyCode === 13) {
                        var fakeButton = {};
                        fakeButton.textOp = bindCommand("doAutoindent");
                        doClick(fakeButton);
                    }
                }
            });
    
            // special handler because IE clears the context of the textbox on ESC
            if (uaSniffed.isIE) {
                util.addEvent(inputBox, "keydown", function (key) {
                    var code = key.keyCode;
                    if (code === 27) {
                        return false;
                    }
                });
            }
            */
           
            // life 新添加函数
            // life
            // isImage 2015/3/1
            function insertLinkLife(link, text, isImage) {
                inputBox.focus();
                if (undoManager) {
                    undoManager.setCommandMode();
                }
    
                var state = new TextareaState(panels);
    
                if (!state) {
                    return;
                }
    
                var chunks = state.getChunks(); // 得到chunk
                var fixupInputArea = function () {
                    inputBox.focus();
    
                    if (chunks) {
                        state.setChunks(chunks);
                    }
    
                    state.restore();
                    previewManager.refresh();
                };
    
                var a = commandProto.insertLink(chunks, fixupInputArea, link, text, isImage);
                if(!a) fixupInputArea();
            }
           
            // life
            MD.insertLink = insertLinkLife;
    
            // Perform the button's action.
            function doClick(button) {
    
                inputBox.focus();
                var linkOrImage = button.id == "wmd-link-button" || button.id == "wmd-image-button";
    
                if (button.textOp) {
    
                    if (undoManager && !linkOrImage) {
                        undoManager.setCommandMode();
                    }
    
                    var state = new TextareaState(panels);
    
                    if (!state) {
                        return;
                    }
    
                    var chunks = state.getChunks();
    
                    // Some commands launch a "modal" prompt dialog.  Javascript
                    // can't really make a modal dialog box and the WMD code
                    // will continue to execute while the dialog is displayed.
                    // This prevents the dialog pattern I'm used to and means
                    // I can't do something like this:
                    //
                    // var link = CreateLinkDialog();
                    // makeMarkdownLink(link);
                    //
                    // Instead of this straightforward method of handling a
                    // dialog I have to pass any code which would execute
                    // after the dialog is dismissed (e.g. link creation)
                    // in a function parameter.
                    //
                    // Yes this is awkward and I think it sucks, but there's
                    // no real workaround.  Only the image and link code
                    // create dialogs and require the function pointers.
                    var fixupInputArea = function () {
    
                        inputBox.focus();
    
                        if (chunks) {
                            state.setChunks(chunks);
                        }
    
                        state.restore();
                        previewManager.refresh();
                    };
    
                    var noCleanup = button.textOp(chunks, fixupInputArea);
    
                    if (!noCleanup) {
                        fixupInputArea();
                        if(!linkOrImage) {
    	                    inputBox.adjustCursorPosition();
    	                    //inputBox.dispatchEvent(new Event('keydown'));
                        }
                    }
    
                }
    
                if (button.execute) {
                    button.execute(undoManager);
                }
            };
    
            function setupButton(button, isEnabled) {
    
                var normalYShift = "0px";
                var disabledYShift = "-20px";
                var highlightYShift = "-40px";
                var image = button.getElementsByTagName("span")[0];
                button.className = button.className.replace(/ disabled/g, "");
                if (isEnabled) {
                    image.style.backgroundPosition = button.XShift + " " + normalYShift;
                    button.onmouseover = function () {
                        image.style.backgroundPosition = this.XShift + " " + highlightYShift;
                    };
    
                    button.onmouseout = function () {
                        image.style.backgroundPosition = this.XShift + " " + normalYShift;
                    };
    
                    // IE tries to select the background image "button" text (it's
                    // implemented in a list item) so we have to cache the selection
                    // on mousedown.
                    if (uaSniffed.isIE) {
                        button.onmousedown = function () {
                            panels.ieCachedRange = document.selection.createRange();
                            panels.ieCachedScrollTop = panels.input.scrollTop;
                        };
                    }
    
                    if (!button.isHelp) {
                        button.onclick = function () {
                            if (this.onmouseout) {
                                this.onmouseout();
                            }
                            doClick(this);
                            return false;
                        }
                    }
                }
                else {
                    image.style.backgroundPosition = button.XShift + " " + disabledYShift;
                    button.onmouseover = button.onmouseout = button.onclick = function () { };
                    button.className += " disabled";
                }
            }
    
            function bindCommand(method) {
                if (typeof method === "string")
                    method = commandManager[method];
                return function () { method.apply(commandManager, arguments); }
            }
    
            function makeSpritedButtonRow() {
                var buttonBar = panels.buttonBar;
    
                var normalYShift = "0px";
                var disabledYShift = "-20px";
                var highlightYShift = "-40px";
    
                var buttonRow = document.createElement("ul");
                buttonRow.id = "wmd-button-row" + postfix;
                buttonRow.className = 'wmd-button-row';
                buttonRow = buttonBar.appendChild(buttonRow);
                var xPosition = 0;
                var makeButton = function (id, title, XShift, textOp) {
                    var button = document.createElement("li");
                    button.className = "wmd-button";
                    button.style.left = xPosition + "px";
                    xPosition += 25;
                    var buttonImage = document.createElement("span");
                    button.id = id + postfix;
                    button.appendChild(buttonImage);
                    button.title = title;
                    button.XShift = XShift;
                    if (textOp)
                        button.textOp = textOp;
                    setupButton(button, true);
                    buttonRow.appendChild(button);
                    return button;
                };
    
                buttons.bold = makeButton("wmd-bold-button", getString("bold"), "0px", bindCommand("doBold"));
                buttons.italic = makeButton("wmd-italic-button", getString("italic"), "-20px", bindCommand("doItalic"));
                // makeSpacer(1);
                buttons.link = makeButton("wmd-link-button", getString("link"), "-40px", bindCommand(function (chunk, postProcessing) {
                    return this.doLinkOrImage(chunk, postProcessing, false);
                }));
                buttons.quote = makeButton("wmd-quote-button", getString("quote"), "-60px", bindCommand("doBlockquote"));
                buttons.code = makeButton("wmd-code-button", getString("code"), "-80px", bindCommand("doCode"));
                buttons.image = makeButton("wmd-image-button", getString("image"), "-100px", bindCommand(function (chunk, postProcessing) {
                    return this.doLinkOrImage(chunk, postProcessing, true);
                }));
                // makeSpacer(2);
                buttons.olist = makeButton("wmd-olist-button", getString("olist"), "-120px", bindCommand(function (chunk, postProcessing) {
                    this.doList(chunk, postProcessing, true);
                }));
                buttons.ulist = makeButton("wmd-ulist-button", getString("ulist"), "-140px", bindCommand(function (chunk, postProcessing) {
                    this.doList(chunk, postProcessing, false);
                }));
                buttons.heading = makeButton("wmd-heading-button", getString("heading"), "-160px", bindCommand("doHeading"));
                buttons.hr = makeButton("wmd-hr-button", getString("hr"), "-180px", bindCommand("doHorizontalRule"));
                // makeSpacer(3);
                buttons.undo = makeButton("wmd-undo-button", getString("undo"), "-200px", null);
                buttons.undo.execute = function (manager) { if (manager) manager.undo(); };
    
                buttons.redo = makeButton("wmd-redo-button", getString("redo"), "-220px", null);
                buttons.redo.execute = function (manager) { if (manager) manager.redo(); };
    
                if (helpOptions) {
                    var helpButton = document.createElement("li");
                    var helpButtonImage = document.createElement("span");
                    helpButton.appendChild(helpButtonImage);
                    helpButton.className = "wmd-button wmd-help-button";
                    helpButton.id = "wmd-help-button" + postfix;
                    helpButton.XShift = "-240px";
                    helpButton.isHelp = true;
                    helpButton.style.right = "0px";
                    helpButton.title = getString("help");
                    helpButton.onclick = helpOptions.handler;
    
                    setupButton(helpButton, true);
                    buttonRow.appendChild(helpButton);
                    buttons.help = helpButton;
                }
    
                setUndoRedoButtonStates();
            }
    
            function setUndoRedoButtonStates() {
                if (undoManager) {
                    setupButton(buttons.undo, undoManager.canUndo());
                    setupButton(buttons.redo, undoManager.canRedo());
                }
            };
    
            this.setUndoRedoButtonStates = setUndoRedoButtonStates;
            this.buttons = buttons;
            this.doClick = doClick;
    
        }
    
        function CommandManager(pluginHooks, getString) {
            this.hooks = pluginHooks;
            this.getString = getString;
        }
    
        var commandProto = CommandManager.prototype;
    
        // The markdown symbols - 4 spaces = code, > = blockquote, etc.
        commandProto.prefixes = "(?:\\s{4,}|\\s*>|\\s*-\\s+|\\s*\\d+\\.|=|\\+|-|_|\\*|#|\\s*\\[[^\n]]+\\]:)";
    
        // Remove markdown symbols from the chunk selection.
        commandProto.unwrap = function (chunk) {
            var txt = new re("([^\\n])\\n(?!(\\n|" + this.prefixes + "))", "g");
            chunk.selection = chunk.selection.replace(txt, "$1 $2");
        };
    
        commandProto.wrap = function (chunk, len) {
            this.unwrap(chunk);
            var regex = new re("(.{1," + len + "})( +|$\\n?)", "gm"),
                that = this;
    
            chunk.selection = chunk.selection.replace(regex, function (line, marked) {
                if (new re("^" + that.prefixes, "").test(line)) {
                    return line;
                }
                return marked + "\n";
            });
    
            chunk.selection = chunk.selection.replace(/\s+$/, "");
        };
    
        commandProto.doBold = function (chunk, postProcessing) {
            return this.doBorI(chunk, postProcessing, 2, this.getString("boldexample"));
        };
    
        commandProto.doItalic = function (chunk, postProcessing) {
            return this.doBorI(chunk, postProcessing, 1, this.getString("italicexample"));
        };
    
        // chunk: The selected region that will be enclosed with */**
        // nStars: 1 for italics, 2 for bold
        // insertText: If you just click the button without highlighting text, this gets inserted
        commandProto.doBorI = function (chunk, postProcessing, nStars, insertText) {
    
            // Get rid of whitespace and fixup newlines.
            chunk.trimWhitespace();
            chunk.selection = chunk.selection.replace(/\n{2,}/g, "\n");
    
            // Look for stars before and after.  Is the chunk already marked up?
            // note that these regex matches cannot fail
            var starsBefore = /(\**$)/.exec(chunk.before)[0];
            var starsAfter = /(^\**)/.exec(chunk.after)[0];
    
            var prevStars = Math.min(starsBefore.length, starsAfter.length);
    
            // Remove stars if we have to since the button acts as a toggle.
            if ((prevStars >= nStars) && (prevStars != 2 || nStars != 1)) {
                chunk.before = chunk.before.replace(re("[*]{" + nStars + "}$", ""), "");
                chunk.after = chunk.after.replace(re("^[*]{" + nStars + "}", ""), "");
            }
            else if (!chunk.selection && starsAfter) {
                // It's not really clear why this code is necessary.  It just moves
                // some arbitrary stuff around.
                chunk.after = chunk.after.replace(/^([*_]*)/, "");
                chunk.before = chunk.before.replace(/(\s?)$/, "");
                var whitespace = re.$1;
                chunk.before = chunk.before + starsAfter + whitespace;
            }
            else {
    
                // In most cases, if you don't have any selected text and click the button
                // you'll get a selected, marked up region with the default text inserted.
                if (!chunk.selection && !starsAfter) {
                    chunk.selection = insertText;
                }
    
                // Add the true markup.
                var markup = nStars <= 1 ? "*" : "**"; // shouldn't the test be = ?
                chunk.before = chunk.before + markup;
                chunk.after = markup + chunk.after;
            }
    
            return;
        };
    
        commandProto.stripLinkDefs = function (text, defsToAdd) {
    
            text = text.replace(/^[ ]{0,3}\[(\d+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|$)/gm,
                function (totalMatch, id, link, newlines, title) {
                    defsToAdd[id] = totalMatch.replace(/\s*$/, "");
                    if (newlines) {
                        // Strip the title and return that separately.
                        defsToAdd[id] = totalMatch.replace(/["(](.+?)[")]$/, "");
                        return newlines + title;
                    }
                    return "";
                });
    
            return text;
        };
    
        commandProto.addLinkDef = function (chunk, linkDef) {
    
            var refNumber = 0; // The current reference number
            var defsToAdd = {}; //
            // Start with a clean slate by removing all previous link definitions.
            chunk.before = this.stripLinkDefs(chunk.before, defsToAdd);
            chunk.selection = this.stripLinkDefs(chunk.selection, defsToAdd);
            chunk.after = this.stripLinkDefs(chunk.after, defsToAdd);
    
            var defs = "";
            var regex = /(\[)((?:\[[^\]]*\]|[^\[\]])*)(\][ ]?(?:\n[ ]*)?\[)(\d+)(\])/g;
    
            var addDefNumber = function (def) {
                refNumber++;
                def = def.replace(/^[ ]{0,3}\[(\d+)\]:/, "  [" + refNumber + "]:");
                defs += "\n" + def;
            };
    
            // note that
            // a) the recursive call to getLink cannot go infinite, because by definition
            //    of regex, inner is always a proper substring of wholeMatch, and
            // b) more than one level of nesting is neither supported by the regex
            //    nor making a lot of sense (the only use case for nesting is a linked image)
            var getLink = function (wholeMatch, before, inner, afterInner, id, end) {
                inner = inner.replace(regex, getLink);
                if (defsToAdd[id]) {
                    addDefNumber(defsToAdd[id]);
                    return before + inner + afterInner + refNumber + end;
                }
                return wholeMatch;
            };
    
            chunk.before = chunk.before.replace(regex, getLink);
    
            if (linkDef) {
                addDefNumber(linkDef);
            }
            else {
                chunk.selection = chunk.selection.replace(regex, getLink);
            }
    
            var refOut = refNumber;
    
            chunk.after = chunk.after.replace(regex, getLink);
    
            if (chunk.after) {
                chunk.after = chunk.after.replace(/\n*$/, "");
            }
            if (!chunk.after) {
                chunk.selection = chunk.selection.replace(/\n*$/, "");
            }
    
            chunk.after += "\n\n" + defs;
    
            return refOut;
        };
    
        // takes the line as entered into the add link/as image dialog and makes
        // sure the URL and the optinal title are "nice".
        function properlyEncoded(linkdef) {
            return linkdef.replace(/^\s*(.*?)(?:\s+"(.+)")?\s*$/, function (wholematch, link, title) {
                link = link.replace(/\?.*$/, function (querypart) {
                    return querypart.replace(/\+/g, " "); // in the query string, a plus and a space are identical
                });
                link = decodeURIComponent(link); // unencode first, to prevent double encoding
                link = encodeURI(link).replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29');
                link = link.replace(/\?.*$/, function (querypart) {
                    return querypart.replace(/\+/g, "%2b"); // since we replaced plus with spaces in the query part, all pluses that now appear where originally encoded
                });
                if (title) {
                    title = title.trim ? title.trim() : title.replace(/^\s*/, "").replace(/\s*$/, "");
                    title = title.replace(/"/g, "quot;").replace(/\(/g, "&#40;").replace(/\)/g, "&#41;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
                }
                return title ? link + ' "' + title + '"' : link;
            });
        }
    
        // life 添加
        commandProto.insertLink = function (chunk, postProcessing, link, text, isImage) {
            chunk.trimWhitespace();
            chunk.findTags(/\s*!?\[/, /\][ ]?(?:\n[ ]*)?(\[.*?\])?/);
            var background;
    
            if (chunk.endTag.length > 1 && chunk.startTag.length > 0) {
    
                chunk.startTag = chunk.startTag.replace(/!?\[/, "");
                chunk.endTag = "";
                this.addLinkDef(chunk, null);
            }
            else {
    
                // We're moving start and end tag back into the selection, since (as we're in the else block) we're not
                // *removing* a link, but *adding* one, so whatever findTags() found is now back to being part of the
                // link text. linkEnteredCallback takes care of escaping any brackets.
                chunk.selection = chunk.startTag + chunk.selection + chunk.endTag;
                chunk.startTag = chunk.endTag = "";
    
                if (/\n\n/.test(chunk.selection)) {
                    this.addLinkDef(chunk, null);
                    return;
                }
                var that = this;
                // The function to be executed when you enter a link and press OK or Cancel.
                // Marks up the link and adds the ref.
                var linkEnteredCallback = function (link) {
                    background.parentNode.removeChild(background);
    
                    if (link !== null) {
                        chunk.selection = (" " + chunk.selection).replace(/([^\\](?:\\\\)*)(?=[[\]])/g, "$1\\").substr(1);
    
                        chunk.startTag = isImage ? "![" : "[";
                        //chunk.endTag = "][" + num + "]";
                        chunk.endTag = "](" + properlyEncoded(link) + ")";
    
                        chunk.selection = text;
                    }
                    postProcessing();
                };
    
                background = ui.createBackground();
                linkEnteredCallback(link);
                return true;
            }
        };
    
        commandProto.doLinkOrImage = function (chunk, postProcessing, isImage) {
    
            chunk.trimWhitespace();
            //chunk.findTags(/\s*!?\[/, /\][ ]?(?:\n[ ]*)?(\[.*?\])?/);
            chunk.findTags(/\s*!?\[/, /\][ ]?(?:\n[ ]*)?(\(.*?\))?/);
    
            var background;
    
            if (chunk.endTag.length > 1 && chunk.startTag.length > 0) {
    
                chunk.startTag = chunk.startTag.replace(/!?\[/, "");
                chunk.endTag = "";
                this.addLinkDef(chunk, null);
    
            }
            else {
    
                // We're moving start and end tag back into the selection, since (as we're in the else block) we're not
                // *removing* a link, but *adding* one, so whatever findTags() found is now back to being part of the
                // link text. linkEnteredCallback takes care of escaping any brackets.
                chunk.selection = chunk.startTag + chunk.selection + chunk.endTag;
                chunk.startTag = chunk.endTag = "";
    
                if (/\n\n/.test(chunk.selection)) {
                    this.addLinkDef(chunk, null);
                    return;
                }
                var that = this;
                // The function to be executed when you enter a link and press OK or Cancel.
                // Marks up the link and adds the ref.
                var linkEnteredCallback = function (link, text) {
                    background.parentNode.removeChild(background);
    
                    if (link !== null) {
                        // (                          $1
                        //     [^\\]                  anything that's not a backslash
                        //     (?:\\\\)*              an even number (this includes zero) of backslashes
                        // )
                        // (?=                        followed by
                        //     [[\]]                  an opening or closing bracket
                        // )
                        //
                        // In other words, a non-escaped bracket. These have to be escaped now to make sure they
                        // don't count as the end of the link or similar.
                        // Note that the actual bracket has to be a lookahead, because (in case of to subsequent brackets),
                        // the bracket in one match may be the "not a backslash" character in the next match, so it
                        // should not be consumed by the first match.
                        // The "prepend a space and finally remove it" steps makes sure there is a "not a backslash" at the
                        // start of the string, so this also works if the selection begins with a bracket. We cannot solve
                        // this by anchoring with ^, because in the case that the selection starts with two brackets, this
                        // would mean a zero-width match at the start. Since zero-width matches advance the string position,
                        // the first bracket could then not act as the "not a backslash" for the second.
                        chunk.selection = (" " + chunk.selection).replace(/([^\\](?:\\\\)*)(?=[[\]])/g, "$1\\").substr(1);
    
                        /*
                        var linkDef = " [999]: " + properlyEncoded(link);
    
                        var num = that.addLinkDef(chunk, linkDef);
                        */
                        chunk.startTag = isImage ? "![" : "[";
                        // chunk.endTag = "][" + num + "]";
                        chunk.endTag = "](" + properlyEncoded(link) + ")";
    
                        if (!chunk.selection) {
                            var str = '';
                            if (text) {
                                str = text;
                            } else if (isImage) {
                                str = that.getString("imagedescription");
                            }
                            else {
                                str = that.getString("linkdescription");
                            }
    
                            chunk.selection = str;
                        }
                    }
                    postProcessing();
                };
    
    
                background = ui.createBackground();
    
                if (isImage) {
                    if (!this.hooks.insertImageDialog(linkEnteredCallback))
                        ui.prompt(this.getString("imagedialog"), imageDefaultText, linkEnteredCallback);
                }
                else {
                    if (!this.hooks.insertLinkDialog(linkEnteredCallback))
                    	ui.prompt(this.getString("linkdialog"), linkDefaultText, linkEnteredCallback);
                }
                return true;
            }
        };
    
        // When making a list, hitting shift-enter will put your cursor on the next line
        // at the current indent level.
        commandProto.doAutoindent = function (chunk, postProcessing) {
    
            var commandMgr = this,
                fakeSelection = false;
    
            chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]*\n$/, "\n\n");
            chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}>[ \t]*\n$/, "\n\n");
            chunk.before = chunk.before.replace(/(\n|^)[ \t]+\n$/, "\n\n");
    
            // There's no selection, end the cursor wasn't at the end of the line:
            // The user wants to split the current list item / code line / blockquote line
            // (for the latter it doesn't really matter) in two. Temporarily select the
            // (rest of the) line to achieve this.
            if (!chunk.selection && !/^[ \t]*(?:\n|$)/.test(chunk.after)) {
                chunk.after = chunk.after.replace(/^[^\n]*/, function (wholeMatch) {
                    chunk.selection = wholeMatch;
                    return "";
                });
                fakeSelection = true;
            }
    
            if (/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]+.*\n$/.test(chunk.before)) {
                if (commandMgr.doList) {
                    commandMgr.doList(chunk);
                }
            }
            if (/(\n|^)[ ]{0,3}>[ \t]+.*\n$/.test(chunk.before)) {
                if (commandMgr.doBlockquote) {
                    commandMgr.doBlockquote(chunk);
                }
            }
            if (/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)) {
                if (commandMgr.doCode) {
                    commandMgr.doCode(chunk);
                }
            }
    
            if (fakeSelection) {
                chunk.after = chunk.selection + chunk.after;
                chunk.selection = "";
            }
        };
    
        commandProto.doBlockquote = function (chunk, postProcessing) {
    
            chunk.selection = chunk.selection.replace(/^(\n*)([^\r]+?)(\n*)$/,
                function (totalMatch, newlinesBefore, text, newlinesAfter) {
                    chunk.before += newlinesBefore;
                    chunk.after = newlinesAfter + chunk.after;
                    return text;
                });
    
            chunk.before = chunk.before.replace(/(>[ \t]*)$/,
                function (totalMatch, blankLine) {
                    chunk.selection = blankLine + chunk.selection;
                    return "";
                });
    
            chunk.selection = chunk.selection.replace(/^(\s|>)+$/, "");
            chunk.selection = chunk.selection || this.getString("quoteexample");
    
            // The original code uses a regular expression to find out how much of the
            // text *directly before* the selection already was a blockquote:
    
            /*
            if (chunk.before) {
            chunk.before = chunk.before.replace(/\n?$/, "\n");
            }
            chunk.before = chunk.before.replace(/(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*$)/,
            function (totalMatch) {
            chunk.startTag = totalMatch;
            return "";
            });
            */
    
            // This comes down to:
            // Go backwards as many lines a possible, such that each line
            //  a) starts with ">", or
            //  b) is almost empty, except for whitespace, or
            //  c) is preceeded by an unbroken chain of non-empty lines
            //     leading up to a line that starts with ">" and at least one more character
            // and in addition
            //  d) at least one line fulfills a)
            //
            // Since this is essentially a backwards-moving regex, it's susceptible to
            // catstrophic backtracking and can cause the browser to hang;
            // see e.g. http://meta.stackoverflow.com/questions/9807.
            //
            // Hence we replaced this by a simple state machine that just goes through the
            // lines and checks for a), b), and c).
    
            var match = "",
                leftOver = "",
                line;
            if (chunk.before) {
                var lines = chunk.before.replace(/\n$/, "").split("\n");
                var inChain = false;
                for (var i = 0; i < lines.length; i++) {
                    var good = false;
                    line = lines[i];
                    inChain = inChain && line.length > 0; // c) any non-empty line continues the chain
                    if (/^>/.test(line)) {                // a)
                        good = true;
                        if (!inChain && line.length > 1)  // c) any line that starts with ">" and has at least one more character starts the chain
                            inChain = true;
                    } else if (/^[ \t]*$/.test(line)) {   // b)
                        good = true;
                    } else {
                        good = inChain;                   // c) the line is not empty and does not start with ">", so it matches if and only if we're in the chain
                    }
                    if (good) {
                        match += line + "\n";
                    } else {
                        leftOver += match + line;
                        match = "\n";
                    }
                }
                if (!/(^|\n)>/.test(match)) {             // d)
                    leftOver += match;
                    match = "";
                }
            }
    
            chunk.startTag = match;
            chunk.before = leftOver;
    
            // end of change
    
            if (chunk.after) {
                chunk.after = chunk.after.replace(/^\n?/, "\n");
            }
    
            chunk.after = chunk.after.replace(/^(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*)/,
                function (totalMatch) {
                    chunk.endTag = totalMatch;
                    return "";
                }
            );
    
            var replaceBlanksInTags = function (useBracket) {
    
                var replacement = useBracket ? "> " : "";
    
                if (chunk.startTag) {
                    chunk.startTag = chunk.startTag.replace(/\n((>|\s)*)\n$/,
                        function (totalMatch, markdown) {
                            return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n";
                        });
                }
                if (chunk.endTag) {
                    chunk.endTag = chunk.endTag.replace(/^\n((>|\s)*)\n/,
                        function (totalMatch, markdown) {
                            return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n";
                        });
                }
            };
    
            if (/^(?![ ]{0,3}>)/m.test(chunk.selection)) {
                this.wrap(chunk, SETTINGS.lineLength - 2);
                chunk.selection = chunk.selection.replace(/^/gm, "> ");
                replaceBlanksInTags(true);
                chunk.skipLines();
            } else {
                chunk.selection = chunk.selection.replace(/^[ ]{0,3}> ?/gm, "");
                this.unwrap(chunk);
                replaceBlanksInTags(false);
    
                if (!/^(\n|^)[ ]{0,3}>/.test(chunk.selection) && chunk.startTag) {
                    chunk.startTag = chunk.startTag.replace(/\n{0,2}$/, "\n\n");
                }
    
                if (!/(\n|^)[ ]{0,3}>.*$/.test(chunk.selection) && chunk.endTag) {
                    chunk.endTag = chunk.endTag.replace(/^\n{0,2}/, "\n\n");
                }
            }
    
            chunk.selection = this.hooks.postBlockquoteCreation(chunk.selection);
    
            if (!/\n/.test(chunk.selection)) {
                chunk.selection = chunk.selection.replace(/^(> *)/,
                function (wholeMatch, blanks) {
                    chunk.startTag += blanks;
                    return "";
                });
            }
        };
    
        // 这里, 应该用 ``` ```
        commandProto.doCode = function (chunk, postProcessing) {
    
            var hasTextBefore = /\S[ ]*$/.test(chunk.before);
            var hasTextAfter = /^[ ]*\S/.test(chunk.after);
    
            // Use 'four space' markdown if the selection is on its own
            // line or is multiline.
            if ((!hasTextAfter && !hasTextBefore) || /\n/.test(chunk.selection)) {
    
                chunk.before = chunk.before.replace(/[ ]{4}$/,
                    function (totalMatch) {
                        chunk.selection = totalMatch + chunk.selection;
                        return "";
                    });
    
                var nLinesBack = 1;
                var nLinesForward = 1;
    
                if (/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)) {
                    nLinesBack = 0;
                }
                if (/^\n(\t|[ ]{4,})/.test(chunk.after)) {
                    nLinesForward = 0;
                }
    
                chunk.skipLines(nLinesBack, nLinesForward);
    
                if (!chunk.selection) {
                    chunk.startTag = "    ";
                    chunk.selection = this.getString("codeexample");
                }
                else {
                    if (/^[ ]{0,3}\S/m.test(chunk.selection)) {
                        if (/\n/.test(chunk.selection))
                            chunk.selection = chunk.selection.replace(/^/gm, "    ");
                        else // if it's not multiline, do not select the four added spaces; this is more consistent with the doList behavior
                            chunk.before += "    ";
                    }
                    else {
                        chunk.selection = chunk.selection.replace(/^(?:[ ]{4}|[ ]{0,3}\t)/gm, "");
                    }
                }
            }
            else {
                // Use backticks (`) to delimit the code block.
    
                chunk.trimWhitespace();
                chunk.findTags(/`/, /`/);
    
                if (!chunk.startTag && !chunk.endTag) {
                    chunk.startTag = chunk.endTag = "`";
                    if (!chunk.selection) {
                        chunk.selection = this.getString("codeexample");
                    }
                }
                else if (chunk.endTag && !chunk.startTag) {
                    chunk.before += chunk.endTag;
                    chunk.endTag = "";
                }
                else {
                    chunk.startTag = chunk.endTag = "";
                }
            }
        };
    
        commandProto.doList = function (chunk, postProcessing, isNumberedList) {
    
            // These are identical except at the very beginning and end.
            // Should probably use the regex extension function to make this clearer.
            var previousItemsRegex = /(\n|^)(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*$/;
            var nextItemsRegex = /^\n*(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*/;
    
            // The default bullet is a dash but others are possible.
            // This has nothing to do with the particular HTML bullet,
            // it's just a markdown bullet.
            var bullet = "-";
    
            // The number in a numbered list.
            var num = 1;
    
            // Get the item prefix - e.g. " 1. " for a numbered list, " - " for a bulleted list.
            var getItemPrefix = function () {
                var prefix;
                if (isNumberedList) {
                    prefix = " " + num + ". ";
                    num++;
                }
                else {
                    prefix = " " + bullet + " ";
                }
                return prefix;
            };
    
            // Fixes the prefixes of the other list items.
            var getPrefixedItem = function (itemText) {
    
                // The numbering flag is unset when called by autoindent.
                if (isNumberedList === undefined) {
                    isNumberedList = /^\s*\d/.test(itemText);
                }
    
                // Renumber/bullet the list element.
                itemText = itemText.replace(/^[ ]{0,3}([*+-]|\d+[.])\s/gm,
                    function (_) {
                        return getItemPrefix();
                    });
    
                return itemText;
            };
    
            chunk.findTags(/(\n|^)*[ ]{0,3}([*+-]|\d+[.])\s+/, null);
    
            if (chunk.before && !/\n$/.test(chunk.before) && !/^\n/.test(chunk.startTag)) {
                chunk.before += chunk.startTag;
                chunk.startTag = "";
            }
    
            if (chunk.startTag) {
    
                var hasDigits = /\d+[.]/.test(chunk.startTag);
                chunk.startTag = "";
                chunk.selection = chunk.selection.replace(/\n[ ]{4}/g, "\n");
                this.unwrap(chunk);
                chunk.skipLines();
    
                if (hasDigits) {
                    // Have to renumber the bullet points if this is a numbered list.
                    chunk.after = chunk.after.replace(nextItemsRegex, getPrefixedItem);
                }
                if (isNumberedList == hasDigits) {
                    return;
                }
            }
    
            var nLinesUp = 1;
    
            chunk.before = chunk.before.replace(previousItemsRegex,
                function (itemText) {
                    if (/^\s*([*+-])/.test(itemText)) {
                        bullet = re.$1;
                    }
                    nLinesUp = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0;
                    return getPrefixedItem(itemText);
                });
    
            if (!chunk.selection) {
                chunk.selection = this.getString("litem");
            }
    
            var prefix = getItemPrefix();
    
            var nLinesDown = 1;
    
            chunk.after = chunk.after.replace(nextItemsRegex,
                function (itemText) {
                    nLinesDown = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0;
                    return getPrefixedItem(itemText);
                });
    
            chunk.trimWhitespace(true);
            chunk.skipLines(nLinesUp, nLinesDown, true);
            chunk.startTag = prefix;
            var spaces = prefix.replace(/./g, " ");
            this.wrap(chunk, SETTINGS.lineLength - spaces.length);
            chunk.selection = chunk.selection.replace(/\n/g, "\n" + spaces);
    
        };
    
        // 要改成 ## ### #### 
        // life 2015/7/12
        commandProto.doHeading = function (chunk, postProcessing) {
            // Remove leading/trailing whitespace and reduce internal spaces to single spaces.
            chunk.selection = chunk.selection.replace(/\s+/g, " ");
            chunk.selection = chunk.selection.replace(/(^\s+|\s+$)/g, "");
    
            // If we clicked the button with no selected text, we just
            // make a level 2 hash header around some default text.
            if (!chunk.selection) {
                // 需要skip的时候 life
                if(chunk.before && (chunk.before[chunk.before.length - 1] != "\n")) {
                    chunk.skipLines(1, 1);
                }
                chunk.startTag = "# ";
                chunk.selection = this.getString("headingexample");
                chunk.endTag = ""; // ##
                return;
            }
    
            chunk.findTags(/#+[ ]*/, /[ ]*#+/);
            // console.log(chunk);
    
            if(chunk.before && (chunk.before[chunk.before.length - 1] != "\n")) {
                chunk.skipLines(1, 1);
            }
    
            var beforeHLevel = 0;
            var startTag = chunk.startTag;
            if (/^#+[ ]*$/.test(startTag)) {
                startTag = startTag.replace(/ /g, '');
                beforeHLevel = startTag.length;
            }
    
            // [0, 4]
            var headerLevelToCreate = 0;
            if(beforeHLevel >= 0 && beforeHLevel <= 3) {
                headerLevelToCreate = beforeHLevel + 1;
            }
            if(beforeHLevel >= 4) {
                headerLevelToCreate = 0;
                chunk.startTag = '';
            }
    
            if (headerLevelToCreate > 0) {
                var header = "";
                while (headerLevelToCreate--) {
                    header += "#";
                }
                header += " ";
    
                chunk.startTag = header;
            }
            return;
        };
    
        commandProto.doHorizontalRule = function (chunk, postProcessing) {
            chunk.startTag = "----------\n";
            chunk.selection = "";
            chunk.skipLines(1, 1, true);
        }
    
    })();
    
    define("pagedown", function(){});
    
    /*globals MD:true, Markdown */
    define('core',[
    	"underscore",
    	"crel",
    	"editor",
    	// "layout",
    	// "constants",
    	"utils",
    	// "storage",
    	"settings",
    	"eventMgr",
    	'pagedown'
    ], function( _, crel, editor, utils, settings, eventMgr) {
    
    	var core = {};
    
    	// life commonjs使用
    	MD = editor;
    
    	// Used to detect user activity
    	var isUserReal = false;
    	var userActive = false;
    	var userLastActivity = 0;
    
    	function setUserActive() {
    		isUserReal = true;
    		userActive = true;
    		var currentTime = utils.currentTime;
    		if(currentTime > userLastActivity + 1000) {
    			userLastActivity = currentTime;
    			eventMgr.onUserActive();
    		}
    	}
    
    	// Create the PageDown editor
    	var pagedownEditor;
    	var fileDesc;
    	var insertLinkO = $('<div class="modal fade modal-insert-link"><div class="modal-dialog"><div class="modal-content">'
    			+ '<div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>'
    			+ '<h4 class="modal-title">' + getMsg('Hyperlink') + '</h4></div>'
    			+ '<div class="modal-body"><p>' + getMsg('Please provide the link URL and an optional title') + ':</p>'
    			+ '<div class="input-group"><span class="input-group-addon"><i class="fa fa-link"></i></span><input id="input-insert-link" type="text" class="col-sm-5 form-control" placeholder="http://example.com  ' + getMsg('optional title') + '"></div></div><div class="modal-footer"><a href="#" class="btn btn-default" data-dismiss="modal">' + getMsg('Cancel') + '</a> <a href="#" class="btn btn-primary action-insert-link" data-dismiss="modal">' + getMsg('OK') + '</a></div></div></div></div>');
    
    	var actionInsertLinkO = insertLinkO.find('.action-insert-link');
    
    
    	// Load settings in settings dialog
    	// var $themeInputElt;
    
    	core.initEditorFirst = function() {
    		// Create the converter and the editor
    		var converter = new Markdown.Converter();
    		var options = {
    			_DoItalicsAndBold: function(text) {
    				// Restore original markdown implementation
    				text = text.replace(/(\*\*|__)(?=\S)(.+?[*_]*)(?=\S)\1/g,
    					"<strong>$2</strong>");
    				text = text.replace(/(\*|_)(?=\S)(.+?)(?=\S)\1/g,
    					"<em>$2</em>");
    				return text;
    			}
    		};
    		converter.setOptions(options);
    		
    		pagedownEditor = new Markdown.Editor(converter, undefined, {
    			undoManager: editor.undoMgr
    			// ,
    			// helpButton: { handler: markdownHelp },
    	        // strings: "Markdown syntax"
    		});
    
    		MD.pagedownEditor = pagedownEditor;
    		// 重置undo
    		// 11/12
    		MD.clearUndo = function () {
    			MD.undoMgr.init();
    			MD.pagedownEditor.uiManager.setUndoRedoButtonStates();
    		};
    
    		MD.insertLink2 = pagedownEditor.insertLink;
    
    		// Custom insert link dialog
    		pagedownEditor.hooks.set("insertLinkDialog", function(callback) {
    			core.insertLinkCallback = callback;
    			utils.resetModalInputs();
    			insertLinkO.modal();
    			return true;
    		});
    		// Custom insert image dialog
    		pagedownEditor.hooks.set("insertImageDialog", function(callback) {
    			core.insertLinkCallback = callback;
    			if(core.catchModal) {
    				return true;
    			}
    			utils.resetModalInputs();
    			var ifr = $("#leauiIfrForMD");
    			if(!ifr.attr('src')) {
    				ifr.attr('src', '/album/index?md=1');
    			}
    
    			$(".modal-insert-image").modal();
    			return true;
    		});
    
    		eventMgr.onPagedownConfigure(pagedownEditor);
    		pagedownEditor.hooks.chain("onPreviewRefresh", eventMgr.onAsyncPreview);
    		pagedownEditor.run();
    		// editor.undoMgr.init();
    
    		// Hide default buttons
    		$(".wmd-button-row li").addClass("btn btn-success").css("left", 0).find("span").hide();
    
    		// Add customized buttons
    		var $btnGroupElt = $('.wmd-button-group1');
    		
    		$("#wmd-bold-button").append($('<i class="fa fa-bold">')).appendTo($btnGroupElt);
    		$("#wmd-italic-button").append($('<i class="fa fa-italic">')).appendTo($btnGroupElt);
    		$btnGroupElt = $('.wmd-button-group2');
    		$("#wmd-link-button").append($('<i class="fa fa-link">')).appendTo($btnGroupElt);
    		$("#wmd-quote-button").append($('<i class="fa fa-quote-left">')).appendTo($btnGroupElt);
    		$("#wmd-code-button").append($('<i class="fa fa-code">')).appendTo($btnGroupElt);
    		$("#wmd-image-button").append($('<i class="fa fa-picture-o">')).appendTo($btnGroupElt);
    		$btnGroupElt = $('.wmd-button-group3');
    		$("#wmd-olist-button").append($('<i class="fa fa-list-ol">')).appendTo($btnGroupElt);
    		$("#wmd-ulist-button").append($('<i class="fa fa-list-ul">')).appendTo($btnGroupElt);
    		$("#wmd-heading-button").append($('<i class="fa fa-header">')).appendTo($btnGroupElt);
    		$("#wmd-hr-button").append($('<i class="fa fa-ellipsis-h">')).appendTo($btnGroupElt);
    		$btnGroupElt = $('.wmd-button-group4');
    		$("#wmd-undo-button").append($('<i class="fa fa-undo">')).appendTo($btnGroupElt);
    		$("#wmd-redo-button").append($('<i class="fa fa-repeat">')).appendTo($btnGroupElt);
    		$("#wmd-help-button").show();
    	};
    
    	core.initEditor = function(fileDescParam) {
    		if(fileDesc !== undefined) {
    			eventMgr.onFileClosed(fileDesc);
    		}
    		fileDesc = fileDescParam;
    
    		// If the editor is already created, 返回之
    		// 再fileDEsc有什么用?
    		if(pagedownEditor !== undefined) {
    			editor.undoMgr.init();
    			return pagedownEditor.uiManager.setUndoRedoButtonStates();
    		}
    		core.initEditorFirst();
    		editor.undoMgr.init();
    	};
    
    	// Initialize multiple things and then fire eventMgr.onReady
    	// 主入口
    	core.onReady = function() {
    		// Add RTL class
    		document.body.className += ' ' + settings.editMode;
    
    		// 这里, 以后肯定都是bodyEditorHTML, 用bodyEditorHTML不是这里加载的, 直接在html写上
    		// document.body.innerHTML = bodyEditorHTML;
    
    		// Initialize utils library
    		utils.init();
    
    		// Detect user activity
    		$(document).mousemove(setUserActive).keypress(setUserActive);
    
    		// 先发送事件, 不然partialRendering有问题
    		eventMgr.onReady();
    
    		// 布局, 一些事件, 比如打开左侧menu
    		// layout.init();
    		core.initEditorFirst();
    		editor.init();
    
    		// life
    		// var fileDesc = {content: ""};
    		// eventMgr.onFileSelected(fileDesc);
    		// core.initEditor(fileDesc);
    	};
    
    	// Other initialization that are not prioritary
    	eventMgr.addListener("onReady", function() {
    
    		$(document.body).on('shown.bs.modal', '.modal', function() {
    			var $elt = $(this);
    			setTimeout(function() {
    				// When modal opens focus on the first button
    				$elt.find('.btn:first').focus();
    				// Or on the first link if any
    				$elt.find('button:first').focus();
    				// Or on the first input if any
    				$elt.find("input:enabled:visible:first").focus();
    			}, 50);
    		}).on('hidden.bs.modal', '.modal', function() {
    			// Focus on the editor when modal is gone
    			editor.focus();
    			// Revert to current theme when settings modal is closed
    			// applyTheme(window.theme);
    		}).on('keypress', '.modal', function(e) {
    			// Handle enter key in modals
    			if(e.which == 13 && !$(e.target).is("textarea")) {
    				$(this).find(".modal-footer a:last").click();
    			}
    		});
    
    		// Click events on "insert link" and "insert image" dialog buttons
    		actionInsertLinkO.click(function(e) {
    			var value = utils.getInputTextValue($("#input-insert-link"), e);
    			if(value !== undefined) {
    				var arr = value.split(' ');
    				var text = '';
    				var link = arr[0];
    				if (arr.length > 1) {
    					arr.shift();
    					text = $.trim(arr.join(' '));
    				}
    				core.insertLinkCallback(link, text);
    				core.insertLinkCallback = undefined;
    			}
    		});
    
    		// 插入图片
    		$(".action-insert-image").click(function() {
    			// 得到图片链接或图片
    			/*
    			https://github.com/leanote/leanote/issues/171
    			同遇到了网页编辑markdown时不能添加图片的问题。
    			可以上传图片,但是按下“插入图片”按钮之后,编辑器中没有加入![...](...)
    			我的控制台有这样的错误: TypeError: document.mdImageManager is undefined
    			*/
    			// mdImageManager是iframe的name, mdGetImgSrc是iframe内的全局方法
    			// var value = document.mdImageManager.mdGetImgSrc();
    			var value = document.getElementById('leauiIfrForMD').contentWindow.mdGetImgSrc();
    			// var value = utils.getInputTextValue($("#input-insert-image"), e);
    			if(value) {
    				core.insertLinkCallback(value);
    				core.insertLinkCallback = undefined;
    			}
    		});
    
    		// Hide events on "insert link" and "insert image" dialogs
    		insertLinkO.on('hidden.bs.modal', function() {
    			if(core.insertLinkCallback !== undefined) {
    				core.insertLinkCallback(null);
    				core.insertLinkCallback = undefined;
    			}
    		});
    
    		// Avoid dropdown panels to close on click
    		$("div.dropdown-menu").click(function(e) {
    			e.stopPropagation();
    		});
    
    		// 弹框显示markdown语法
    		$('#wmd-help-button').click(function() {
    	        window.open("http://leanote.com/blog/post/531b263bdfeb2c0ea9000002");
    		});
    
    		// Load images
    		_.each(document.querySelectorAll('img'), function(imgElt) {
    			var $imgElt = $(imgElt);
    			var src = $imgElt.data('stackeditSrc');
    			if(src) {
    				$imgElt.attr('src', window.baseDir + '/img/' + src);
    			}
    		});
    
    	// 	if(window.viewerMode === false) {
    	// 		// Load theme list
    	// 		var themeOptions = _.reduce(constants.THEME_LIST, function(themeOptions, name, value) {
    	// 			return themeOptions + '<option value="' + value + '">' + name + '</option>';
    	// 		}, '');
    	// 		document.getElementById('input-settings-theme').innerHTML = themeOptions;
    	// 	}
    	});
    
    	return core;
    });
    
    /**
     * @license CSS Class Applier module for Rangy.
     * Adds, removes and toggles CSS classes on Ranges and Selections
     *
     * Part of Rangy, a cross-browser JavaScript range and selection library
     * http://code.google.com/p/rangy/
     *
     * Depends on Rangy core.
     *
     * Copyright 2012, Tim Down
     * Licensed under the MIT license.
     * Version: 1.2.3
     * Build date: 26 February 2012
     */
    rangy.createModule("CssClassApplier", function(api, module) {
        api.requireModules( ["WrappedSelection", "WrappedRange"] );
    
        var dom = api.dom;
    
    
    
        var defaultTagName = "span";
    
        function trim(str) {
            return str.replace(/^\s\s*/, "").replace(/\s\s*$/, "");
        }
    
        function hasClass(el, cssClass) {
            return el.className && new RegExp("(?:^|\\s)" + cssClass + "(?:\\s|$)").test(el.className);
        }
    
        function addClass(el, cssClass) {
            if (el.className) {
                if (!hasClass(el, cssClass)) {
                    el.className += " " + cssClass;
                }
            } else {
                el.className = cssClass;
            }
        }
    
        var removeClass = (function() {
            function replacer(matched, whiteSpaceBefore, whiteSpaceAfter) {
                return (whiteSpaceBefore && whiteSpaceAfter) ? " " : "";
            }
    
            return function(el, cssClass) {
                if (el.className) {
                    el.className = el.className.replace(new RegExp("(?:^|\\s)" + cssClass + "(?:\\s|$)"), replacer);
                }
            };
        })();
    
        function sortClassName(className) {
            return className.split(/\s+/).sort().join(" ");
        }
    
        function getSortedClassName(el) {
            return sortClassName(el.className);
        }
    
        function haveSameClasses(el1, el2) {
            return getSortedClassName(el1) == getSortedClassName(el2);
        }
    
        function replaceWithOwnChildren(el) {
    
            var parent = el.parentNode;
            while (el.hasChildNodes()) {
                parent.insertBefore(el.firstChild, el);
            }
            parent.removeChild(el);
        }
    
        function rangeSelectsAnyText(range, textNode) {
            var textRange = range.cloneRange();
            textRange.selectNodeContents(textNode);
    
            var intersectionRange = textRange.intersection(range);
            var text = intersectionRange ? intersectionRange.toString() : "";
            textRange.detach();
    
            return text != "";
        }
    
        function getEffectiveTextNodes(range) {
            return range.getNodes([3], function(textNode) {
                return rangeSelectsAnyText(range, textNode);
            });
        }
    
        function elementsHaveSameNonClassAttributes(el1, el2) {
            if (el1.attributes.length != el2.attributes.length) return false;
            for (var i = 0, len = el1.attributes.length, attr1, attr2, name; i < len; ++i) {
                attr1 = el1.attributes[i];
                name = attr1.name;
                if (name != "class") {
                    attr2 = el2.attributes.getNamedItem(name);
                    if (attr1.specified != attr2.specified) return false;
                    if (attr1.specified && attr1.nodeValue !== attr2.nodeValue) return false;
                }
            }
            return true;
        }
    
        function elementHasNonClassAttributes(el, exceptions) {
            for (var i = 0, len = el.attributes.length, attrName; i < len; ++i) {
                attrName = el.attributes[i].name;
                if ( !(exceptions && dom.arrayContains(exceptions, attrName)) && el.attributes[i].specified && attrName != "class") {
                    return true;
                }
            }
            return false;
        }
    
        function elementHasProps(el, props) {
            for (var p in props) {
                if (props.hasOwnProperty(p) && el[p] !== props[p]) {
                    return false;
                }
            }
            return true;
        }
    
        var getComputedStyleProperty;
    
        if (typeof window.getComputedStyle != "undefined") {
            getComputedStyleProperty = function(el, propName) {
                return dom.getWindow(el).getComputedStyle(el, null)[propName];
            };
        } else if (typeof document.documentElement.currentStyle != "undefined") {
            getComputedStyleProperty = function(el, propName) {
                return el.currentStyle[propName];
            };
        } else {
            module.fail("No means of obtaining computed style properties found");
        }
    
        var isEditableElement;
    
        (function() {
            var testEl = document.createElement("div");
            if (typeof testEl.isContentEditable == "boolean") {
                isEditableElement = function(node) {
                    return node && node.nodeType == 1 && node.isContentEditable;
                };
            } else {
                isEditableElement = function(node) {
                    if (!node || node.nodeType != 1 || node.contentEditable == "false") {
                        return false;
                    }
                    return node.contentEditable == "true" || isEditableElement(node.parentNode);
                };
            }
        })();
    
        function isEditingHost(node) {
            var parent;
            return node && node.nodeType == 1
                && (( (parent = node.parentNode) && parent.nodeType == 9 && parent.designMode == "on")
                || (isEditableElement(node) && !isEditableElement(node.parentNode)));
        }
    
        function isEditable(node) {
            return (isEditableElement(node) || (node.nodeType != 1 && isEditableElement(node.parentNode))) && !isEditingHost(node);
        }
    
        var inlineDisplayRegex = /^inline(-block|-table)?$/i;
    
        function isNonInlineElement(node) {
            return node && node.nodeType == 1 && !inlineDisplayRegex.test(getComputedStyleProperty(node, "display"));
        }
    
        // White space characters as defined by HTML 4 (http://www.w3.org/TR/html401/struct/text.html)
        var htmlNonWhiteSpaceRegex = /[^\r\n\t\f \u200B]/;
    
        function isUnrenderedWhiteSpaceNode(node) {
            if (node.data.length == 0) {
                return true;
            }
            if (htmlNonWhiteSpaceRegex.test(node.data)) {
                return false;
            }
            var cssWhiteSpace = getComputedStyleProperty(node.parentNode, "whiteSpace");
            switch (cssWhiteSpace) {
                case "pre":
                case "pre-wrap":
                case "-moz-pre-wrap":
                    return false;
                case "pre-line":
                    if (/[\r\n]/.test(node.data)) {
                        return false;
                    }
            }
    
            // We now have a whitespace-only text node that may be rendered depending on its context. If it is adjacent to a
            // non-inline element, it will not be rendered. This seems to be a good enough definition.
            return isNonInlineElement(node.previousSibling) || isNonInlineElement(node.nextSibling);
        }
    
        function isSplitPoint(node, offset) {
            if (dom.isCharacterDataNode(node)) {
                if (offset == 0) {
                    return !!node.previousSibling;
                } else if (offset == node.length) {
                    return !!node.nextSibling;
                } else {
                    return true;
                }
            }
    
            return offset > 0 && offset < node.childNodes.length;
        }
    
        function splitNodeAt(node, descendantNode, descendantOffset, rangesToPreserve) {
            var newNode;
            var splitAtStart = (descendantOffset == 0);
    
            if (dom.isAncestorOf(descendantNode, node)) {
    
                return node;
            }
    
            if (dom.isCharacterDataNode(descendantNode)) {
                if (descendantOffset == 0) {
                    descendantOffset = dom.getNodeIndex(descendantNode);
                    descendantNode = descendantNode.parentNode;
                } else if (descendantOffset == descendantNode.length) {
                    descendantOffset = dom.getNodeIndex(descendantNode) + 1;
                    descendantNode = descendantNode.parentNode;
                } else {
                    throw module.createError("splitNodeAt should not be called with offset in the middle of a data node ("
                        + descendantOffset + " in " + descendantNode.data);
                }
            }
    
            if (isSplitPoint(descendantNode, descendantOffset)) {
                if (!newNode) {
                    newNode = descendantNode.cloneNode(false);
                    if (newNode.id) {
                        newNode.removeAttribute("id");
                    }
                    var child;
                    while ((child = descendantNode.childNodes[descendantOffset])) {
                        newNode.appendChild(child);
                    }
                    dom.insertAfter(newNode, descendantNode);
                }
                return (descendantNode == node) ? newNode : splitNodeAt(node, newNode.parentNode, dom.getNodeIndex(newNode), rangesToPreserve);
            } else if (node != descendantNode) {
                newNode = descendantNode.parentNode;
    
                // Work out a new split point in the parent node
                var newNodeIndex = dom.getNodeIndex(descendantNode);
    
                if (!splitAtStart) {
                    newNodeIndex++;
                }
                return splitNodeAt(node, newNode, newNodeIndex, rangesToPreserve);
            }
            return node;
        }
    
        function areElementsMergeable(el1, el2) {
            return el1.tagName == el2.tagName && haveSameClasses(el1, el2) && elementsHaveSameNonClassAttributes(el1, el2);
        }
    
        function createAdjacentMergeableTextNodeGetter(forward) {
            var propName = forward ? "nextSibling" : "previousSibling";
    
            return function(textNode, checkParentElement) {
                var el = textNode.parentNode;
                var adjacentNode = textNode[propName];
                if (adjacentNode) {
                    // Can merge if the node's previous/next sibling is a text node
                    if (adjacentNode && adjacentNode.nodeType == 3) {
                        return adjacentNode;
                    }
                } else if (checkParentElement) {
                    // Compare text node parent element with its sibling
                    adjacentNode = el[propName];
    
                    if (adjacentNode && adjacentNode.nodeType == 1 && areElementsMergeable(el, adjacentNode)) {
                        return adjacentNode[forward ? "firstChild" : "lastChild"];
                    }
                }
                return null;
            }
        }
    
        var getPreviousMergeableTextNode = createAdjacentMergeableTextNodeGetter(false),
            getNextMergeableTextNode = createAdjacentMergeableTextNodeGetter(true);
    
    
        function Merge(firstNode) {
            this.isElementMerge = (firstNode.nodeType == 1);
            this.firstTextNode = this.isElementMerge ? firstNode.lastChild : firstNode;
            this.textNodes = [this.firstTextNode];
        }
    
        Merge.prototype = {
            doMerge: function() {
                var textBits = [], textNode, parent, text;
                for (var i = 0, len = this.textNodes.length; i < len; ++i) {
                    textNode = this.textNodes[i];
                    parent = textNode.parentNode;
                    textBits[i] = textNode.data;
                    if (i) {
                        parent.removeChild(textNode);
                        if (!parent.hasChildNodes()) {
                            parent.parentNode.removeChild(parent);
                        }
                    }
                }
                this.firstTextNode.data = text = textBits.join("");
                return text;
            },
    
            getLength: function() {
                var i = this.textNodes.length, len = 0;
                while (i--) {
                    len += this.textNodes[i].length;
                }
                return len;
            },
    
            toString: function() {
                var textBits = [];
                for (var i = 0, len = this.textNodes.length; i < len; ++i) {
                    textBits[i] = "'" + this.textNodes[i].data + "'";
                }
                return "[Merge(" + textBits.join(",") + ")]";
            }
        };
    
        var optionProperties = ["elementTagName", "ignoreWhiteSpace", "applyToEditableOnly"];
    
        // Allow "class" as a property name in object properties
        var mappedPropertyNames = {"class" : "className"};
    
        function CssClassApplier(cssClass, options, tagNames) {
            this.cssClass = cssClass;
            var normalize, i, len, propName;
    
            var elementPropertiesFromOptions = null;
    
            // Initialize from options object
            if (typeof options == "object" && options !== null) {
                tagNames = options.tagNames;
                elementPropertiesFromOptions = options.elementProperties;
    
                for (i = 0; propName = optionProperties[i++]; ) {
                    if (options.hasOwnProperty(propName)) {
                        this[propName] = options[propName];
                    }
                }
                normalize = options.normalize;
            } else {
                normalize = options;
            }
    
            // Backwards compatibility: the second parameter can also be a Boolean indicating whether normalization
            this.normalize = (typeof normalize == "undefined") ? true : normalize;
    
            // Initialize element properties and attribute exceptions
            this.attrExceptions = [];
            var el = document.createElement(this.elementTagName);
            this.elementProperties = {};
            for (var p in elementPropertiesFromOptions) {
                if (elementPropertiesFromOptions.hasOwnProperty(p)) {
                    // Map "class" to "className"
                    if (mappedPropertyNames.hasOwnProperty(p)) {
                        p = mappedPropertyNames[p];
                    }
                    el[p] = elementPropertiesFromOptions[p];
    
                    // Copy the property back from the dummy element so that later comparisons to check whether elements
                    // may be removed are checking against the right value. For example, the href property of an element
                    // returns a fully qualified URL even if it was previously assigned a relative URL.
                    this.elementProperties[p] = el[p];
                    this.attrExceptions.push(p);
                }
            }
    
            this.elementSortedClassName = this.elementProperties.hasOwnProperty("className") ?
                sortClassName(this.elementProperties.className + " " + cssClass) : cssClass;
    
            // Initialize tag names
            this.applyToAnyTagName = false;
            var type = typeof tagNames;
            if (type == "string") {
                if (tagNames == "*") {
                    this.applyToAnyTagName = true;
                } else {
                    this.tagNames = trim(tagNames.toLowerCase()).split(/\s*,\s*/);
                }
            } else if (type == "object" && typeof tagNames.length == "number") {
                this.tagNames = [];
                for (i = 0, len = tagNames.length; i < len; ++i) {
                    if (tagNames[i] == "*") {
                        this.applyToAnyTagName = true;
                    } else {
                        this.tagNames.push(tagNames[i].toLowerCase());
                    }
                }
            } else {
                this.tagNames = [this.elementTagName];
            }
        }
    
        CssClassApplier.prototype = {
            elementTagName: defaultTagName,
            elementProperties: {},
            ignoreWhiteSpace: true,
            applyToEditableOnly: false,
    
            hasClass: function(node) {
                return node.nodeType == 1 && dom.arrayContains(this.tagNames, node.tagName.toLowerCase()) && hasClass(node, this.cssClass);
            },
    
            getSelfOrAncestorWithClass: function(node) {
                while (node) {
                    if (this.hasClass(node, this.cssClass)) {
                        return node;
                    }
                    node = node.parentNode;
                }
                return null;
            },
    
            isModifiable: function(node) {
                return !this.applyToEditableOnly || isEditable(node);
            },
    
            // White space adjacent to an unwrappable node can be ignored for wrapping
            isIgnorableWhiteSpaceNode: function(node) {
                return this.ignoreWhiteSpace && node && node.nodeType == 3 && isUnrenderedWhiteSpaceNode(node);
            },
    
            // Normalizes nodes after applying a CSS class to a Range.
            postApply: function(textNodes, range, isUndo) {
    
                var firstNode = textNodes[0], lastNode = textNodes[textNodes.length - 1];
    
                var merges = [], currentMerge;
    
                var rangeStartNode = firstNode, rangeEndNode = lastNode;
                var rangeStartOffset = 0, rangeEndOffset = lastNode.length;
    
                var textNode, precedingTextNode;
    
                for (var i = 0, len = textNodes.length; i < len; ++i) {
                    textNode = textNodes[i];
                    precedingTextNode = getPreviousMergeableTextNode(textNode, !isUndo);
    
                    if (precedingTextNode) {
                        if (!currentMerge) {
                            currentMerge = new Merge(precedingTextNode);
                            merges.push(currentMerge);
                        }
                        currentMerge.textNodes.push(textNode);
                        if (textNode === firstNode) {
                            rangeStartNode = currentMerge.firstTextNode;
                            rangeStartOffset = rangeStartNode.length;
                        }
                        if (textNode === lastNode) {
                            rangeEndNode = currentMerge.firstTextNode;
                            rangeEndOffset = currentMerge.getLength();
                        }
                    } else {
                        currentMerge = null;
                    }
                }
    
                // Test whether the first node after the range needs merging
                var nextTextNode = getNextMergeableTextNode(lastNode, !isUndo);
    
                if (nextTextNode) {
                    if (!currentMerge) {
                        currentMerge = new Merge(lastNode);
                        merges.push(currentMerge);
                    }
                    currentMerge.textNodes.push(nextTextNode);
                }
    
                // Do the merges
                if (merges.length) {
    
                    for (i = 0, len = merges.length; i < len; ++i) {
                        merges[i].doMerge();
                    }
    
    
                    // Set the range boundaries
                    range.setStart(rangeStartNode, rangeStartOffset);
                    range.setEnd(rangeEndNode, rangeEndOffset);
                }
    
            },
    
            createContainer: function(doc) {
                var el = doc.createElement(this.elementTagName);
                api.util.extend(el, this.elementProperties);
                addClass(el, this.cssClass);
                return el;
            },
    
            applyToTextNode: function(textNode) {
    
    
                var parent = textNode.parentNode;
                if (parent.childNodes.length == 1 && dom.arrayContains(this.tagNames, parent.tagName.toLowerCase())) {
                    addClass(parent, this.cssClass);
                } else {
                    var el = this.createContainer(dom.getDocument(textNode));
                    textNode.parentNode.insertBefore(el, textNode);
                    el.appendChild(textNode);
                }
    
            },
    
            isRemovable: function(el) {
                return el.tagName.toLowerCase() == this.elementTagName
                        && getSortedClassName(el) == this.elementSortedClassName
                        && elementHasProps(el, this.elementProperties)
                        && !elementHasNonClassAttributes(el, this.attrExceptions)
                        && this.isModifiable(el);
            },
    
            undoToTextNode: function(textNode, range, ancestorWithClass) {
    
                if (!range.containsNode(ancestorWithClass)) {
                    // Split out the portion of the ancestor from which we can remove the CSS class
                    //var parent = ancestorWithClass.parentNode, index = dom.getNodeIndex(ancestorWithClass);
                    var ancestorRange = range.cloneRange();
                    ancestorRange.selectNode(ancestorWithClass);
    
                    if (ancestorRange.isPointInRange(range.endContainer, range.endOffset)/* && isSplitPoint(range.endContainer, range.endOffset)*/) {
                        splitNodeAt(ancestorWithClass, range.endContainer, range.endOffset, [range]);
                        range.setEndAfter(ancestorWithClass);
                    }
                    if (ancestorRange.isPointInRange(range.startContainer, range.startOffset)/* && isSplitPoint(range.startContainer, range.startOffset)*/) {
                        ancestorWithClass = splitNodeAt(ancestorWithClass, range.startContainer, range.startOffset, [range]);
                    }
                }
    
                if (this.isRemovable(ancestorWithClass)) {
                    replaceWithOwnChildren(ancestorWithClass);
                } else {
                    removeClass(ancestorWithClass, this.cssClass);
                }
            },
    
            applyToRange: function(range) {
                range.splitBoundaries();
                var textNodes = getEffectiveTextNodes(range);
    
                if (textNodes.length) {
                    var textNode;
    
                    for (var i = 0, len = textNodes.length; i < len; ++i) {
                        textNode = textNodes[i];
    
                        if (!this.isIgnorableWhiteSpaceNode(textNode) && !this.getSelfOrAncestorWithClass(textNode)
                                && this.isModifiable(textNode)) {
                            this.applyToTextNode(textNode);
                        }
                    }
                    range.setStart(textNodes[0], 0);
                    textNode = textNodes[textNodes.length - 1];
                    range.setEnd(textNode, textNode.length);
                    if (this.normalize) {
                        this.postApply(textNodes, range, false);
                    }
                }
            },
    
            applyToSelection: function(win) {
    
                win = win || window;
                var sel = api.getSelection(win);
    
                var range, ranges = sel.getAllRanges();
                sel.removeAllRanges();
                var i = ranges.length;
                while (i--) {
                    range = ranges[i];
                    this.applyToRange(range);
                    sel.addRange(range);
                }
    
            },
    
            undoToRange: function(range) {
    
                range.splitBoundaries();
                var textNodes = getEffectiveTextNodes(range);
                var textNode, ancestorWithClass;
                var lastTextNode = textNodes[textNodes.length - 1];
    
                if (textNodes.length) {
                    for (var i = 0, len = textNodes.length; i < len; ++i) {
                        textNode = textNodes[i];
                        ancestorWithClass = this.getSelfOrAncestorWithClass(textNode);
                        if (ancestorWithClass && this.isModifiable(textNode)) {
                            this.undoToTextNode(textNode, range, ancestorWithClass);
                        }
    
                        // Ensure the range is still valid
                        range.setStart(textNodes[0], 0);
                        range.setEnd(lastTextNode, lastTextNode.length);
                    }
    
    
    
                    if (this.normalize) {
                        this.postApply(textNodes, range, true);
                    }
                }
            },
    
            undoToSelection: function(win) {
                win = win || window;
                var sel = api.getSelection(win);
                var ranges = sel.getAllRanges(), range;
                sel.removeAllRanges();
                for (var i = 0, len = ranges.length; i < len; ++i) {
                    range = ranges[i];
                    this.undoToRange(range);
                    sel.addRange(range);
                }
            },
    
            getTextSelectedByRange: function(textNode, range) {
                var textRange = range.cloneRange();
                textRange.selectNodeContents(textNode);
    
                var intersectionRange = textRange.intersection(range);
                var text = intersectionRange ? intersectionRange.toString() : "";
                textRange.detach();
    
                return text;
            },
    
            isAppliedToRange: function(range) {
                if (range.collapsed) {
                    return !!this.getSelfOrAncestorWithClass(range.commonAncestorContainer);
                } else {
                    var textNodes = range.getNodes( [3] );
                    for (var i = 0, textNode; textNode = textNodes[i++]; ) {
                        if (!this.isIgnorableWhiteSpaceNode(textNode) && rangeSelectsAnyText(range, textNode)
                                && this.isModifiable(textNode) && !this.getSelfOrAncestorWithClass(textNode)) {
                            return false;
                        }
                    }
                    return true;
                }
            },
    
            isAppliedToSelection: function(win) {
                win = win || window;
                var sel = api.getSelection(win);
                var ranges = sel.getAllRanges();
                var i = ranges.length;
                while (i--) {
                    if (!this.isAppliedToRange(ranges[i])) {
                        return false;
                    }
                }
    
                return true;
            },
    
            toggleRange: function(range) {
                if (this.isAppliedToRange(range)) {
                    this.undoToRange(range);
                } else {
                    this.applyToRange(range);
                }
            },
    
            toggleSelection: function(win) {
                if (this.isAppliedToSelection(win)) {
                    this.undoToSelection(win);
                } else {
                    this.applyToSelection(win);
                }
            },
    
            detach: function() {}
        };
    
        function createCssClassApplier(cssClass, options, tagNames) {
            return new CssClassApplier(cssClass, options, tagNames);
        }
    
        CssClassApplier.util = {
            hasClass: hasClass,
            addClass: addClass,
            removeClass: removeClass,
            hasSameClasses: haveSameClasses,
            replaceWithOwnChildren: replaceWithOwnChildren,
            elementsHaveSameNonClassAttributes: elementsHaveSameNonClassAttributes,
            elementHasNonClassAttributes: elementHasNonClassAttributes,
            splitNodeAt: splitNodeAt,
            isEditableElement: isEditableElement,
            isEditingHost: isEditingHost,
            isEditable: isEditable
        };
    
        api.CssClassApplier = CssClassApplier;
        api.createCssClassApplier = createCssClassApplier;
    });
    
    define("rangy-cssclassapplier", function(){});
    
    // RequireJS configuration
    /*global requirejs */
    requirejs.config({
    	waitSeconds: 0,
    	packages: [
    		/*
    		{
    			name: 'css',
    			location: 'bower-libs/require-css',
    			main: 'css'
    		},
    		{
    			name: 'less',
    			location: 'bower-libs/require-less',
    			main: 'less'
    		}
    		*/
    	],
    	paths: {
    		// jquery: 'bower-libs/jquery/jquery',
    		underscore: 'bower-libs/underscore/underscore',
    		crel: 'bower-libs/crel/crel',
    		jgrowl: 'bower-libs/jgrowl/jquery.jgrowl',
    		mousetrap: 'bower-libs/mousetrap/mousetrap',
    		'mousetrap-record': 'bower-libs/mousetrap/plugins/record/mousetrap-record',
    		toMarkdown: 'bower-libs/to-markdown/src/to-markdown',
    		text: 'bower-libs/requirejs-text/text',
    		mathjax: 'libs/MathJax/MathJax.js?config=TeX-AMS_HTML',
    		// bootstrap: 'bower-libs/bootstrap/dist/js/bootstrap',
    		requirejs: 'bower-libs/requirejs/require',
    		'google-code-prettify': 'bower-libs/google-code-prettify/src/prettify',
    		// highlightjs: 'libs/highlight/highlight.pack',
    		'jquery-waitforimages': 'bower-libs/waitForImages/src/jquery.waitforimages',
    		// FileSaver: 'bower-libs/FileSaver/FileSaver',
    		// stacktrace: 'bower-libs/stacktrace/stacktrace',
    		// 'requirejs-text': 'bower-libs/requirejs-text/text',
    		// 'bootstrap-tour': 'bower-libs/bootstrap-tour/build/js/bootstrap-tour',
    		css_browser_selector: 'bower-libs/css_browser_selector/css_browser_selector',
    		'pagedown-extra': 'bower-libs/pagedown-extra/node-pagedown-extra',
    		pagedownExtra: 'bower-libs/pagedown-extra/Markdown.Extra',
    		pagedown: 'libs/Markdown.Editor',
    		// 'require-css': 'bower-libs/require-css/css',
    		xregexp: 'bower-libs/xregexp/xregexp-all',
    		// yaml: 'bower-libs/yaml.js/bin/yaml',
    		// 'yaml.js': 'bower-libs/yaml.js',
    		// 'yaml-js': 'bower-libs/yaml.js/bin/yaml',
    		// css: 'bower-libs/require-css/css',
    		// 'css-builder': 'bower-libs/require-css/css-builder',
    		normalize: 'bower-libs/require-css/normalize',
    		prism: 'bower-libs/prism/prism',
    		'prism-core': 'bower-libs/prism/components/prism-core',
    		MutationObservers: 'bower-libs/MutationObservers/MutationObserver',
    		WeakMap: 'bower-libs/WeakMap/weakmap',
    		rangy: 'bower-libs/rangy/rangy-core',
    		'rangy-cssclassapplier': 'bower-libs/rangy/rangy-cssclassapplier',
    		diff_match_patch: 'bower-libs/google-diff-match-patch-js/diff_match_patch',
    		diff_match_patch_uncompressed: 'bower-libs/google-diff-match-patch-js/diff_match_patch_uncompressed',
    		// jsondiffpatch: 'bower-libs/jsondiffpatch/build/bundle',
    		hammerjs: 'bower-libs/hammerjs/hammer',
    		Diagram: 'bower-libs/js-sequence-diagrams/src/sequence-diagram',
    		'diagram-grammar': 'bower-libs/js-sequence-diagrams/build/diagram-grammar',
    		raphael: 'bower-libs/raphael/raphael',
    		'flow-chart': 'bower-libs/flowchart/release/flowchart.amd-1.3.4.min',
    		flowchart: 'bower-libs/flowchart/release/flowchart-1.3.4.min',
    		// monetizejs: 'bower-libs/monetizejs/src/monetize',
    		waitForImages: 'bower-libs/waitForImages/dist/jquery.waitforimages',
    		MathJax: '../libs/MathJax/MathJax'
    	},
    	shim: {
    		underscore: {
    			exports: '_'
    		},
    		mathjax: [
    			'libs/mathjax_init'
    		],
    		jgrowl: {
    			deps: [
    				
    			],
    			exports: 'jQuery.jGrowl'
    		},
    		diff_match_patch_uncompressed: {
    			exports: 'diff_match_patch'
    		},
    		// jsondiffpatch: [
    		// 	'diff_match_patch_uncompressed'
    		// ],
    		rangy: {
    			exports: 'rangy'
    		},
    		'rangy-cssclassapplier': [
    			'rangy'
    		],
    		mousetrap: {
    			exports: 'Mousetrap'
    		},
    		// 'yaml-js': {
    		// 	exports: 'YAML'
    		// },
    		'prism-core': {
    			exports: 'Prism'
    		},
    		'bower-libs/prism/components/prism-markup': [
    			'prism-core'
    		],
    		'libs/prism-latex': [
    			'prism-core'
    		],
    		'libs/prism-markdown': [
    			'bower-libs/prism/components/prism-markup',
    			'libs/prism-latex'
    		],
    		// 'bootstrap-record': [
    		// 	'mousetrap'
    		// ],
    		// stacktrace: {
    		// 	exports: 'printStackTrace'
    		// },
    		// FileSaver: {
    		// 	exports: 'saveAs'
    		// },
    		MutationObservers: [
    			'WeakMap'
    		],
    		// highlightjs: {
    		// 	exports: 'hljs'
    		// },
    		'jquery-waitforimages': [
    		],
    		pagedown: [
    			'libs/Markdown.Converter'
    		],
    		pagedownExtra: [
    			'libs/Markdown.Converter'
    		],
    		'flow-chart': [
    			'raphael'
    		],
    		'diagram-grammar': [
    			'underscore'
    		],
    		Diagram: [
    			'raphael',
    			'diagram-grammar'
    		]
    	}
    });
    
    window.viewerMode = false;
    // Keep the theme in a global variable
    // window.theme = 'default';
    
    window.getMsg || (getMsg = function(msg) {
    	return msg;
    });
    
    require([
    	// "jquery",
    	"rangy",
    	"core",
    	"eventMgr",
    	// "synchronizer",
    	// "publisher",
    	// "sharing",
    	// "mediaImporter",
    	// "css",
    	"rangy-cssclassapplier"
    	// ,themeModule // 生产模式
    ], function( rangy, core) {
    	$(function() {
    		rangy.init();
    		// Here, all the modules are loaded and the DOM is ready
    		core.onReady();
    	});
    });
    
    define("main", function(){});