krafthaus/bauhaus

View on GitHub
bower_components/tinymce/tinymce.js

Summary

Maintainability
A
0 mins
Test Coverage
// 4.1.2 (2014-07-15)

/**
 * Compiled inline version. (Library mode)
 */

/*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */
/*globals $code */

(function(exports, undefined) {
    "use strict";

    var modules = {};

    function require(ids, callback) {
        var module, defs = [];

        for (var i = 0; i < ids.length; ++i) {
            module = modules[ids[i]] || resolve(ids[i]);
            if (!module) {
                throw 'module definition dependecy not found: ' + ids[i];
            }

            defs.push(module);
        }

        callback.apply(null, defs);
    }

    function define(id, dependencies, definition) {
        if (typeof id !== 'string') {
            throw 'invalid module definition, module id must be defined and be a string';
        }

        if (dependencies === undefined) {
            throw 'invalid module definition, dependencies must be specified';
        }

        if (definition === undefined) {
            throw 'invalid module definition, definition function must be specified';
        }

        require(dependencies, function() {
            modules[id] = definition.apply(null, arguments);
        });
    }

    function defined(id) {
        return !!modules[id];
    }

    function resolve(id) {
        var target = exports;
        var fragments = id.split(/[.\/]/);

        for (var fi = 0; fi < fragments.length; ++fi) {
            if (!target[fragments[fi]]) {
                return;
            }

            target = target[fragments[fi]];
        }

        return target;
    }

    function expose(ids) {
        for (var i = 0; i < ids.length; i++) {
            var target = exports;
            var id = ids[i];
            var fragments = id.split(/[.\/]/);

            for (var fi = 0; fi < fragments.length - 1; ++fi) {
                if (target[fragments[fi]] === undefined) {
                    target[fragments[fi]] = {};
                }

                target = target[fragments[fi]];
            }

            target[fragments[fragments.length - 1]] = modules[id];
        }
    }

// Included from: js/tinymce/classes/dom/EventUtils.js

/**
 * EventUtils.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/*jshint loopfunc:true*/
/*eslint no-loop-func:0 */

/**
 * This class wraps the browsers native event logic with more convenient methods.
 *
 * @class tinymce.dom.EventUtils
 */
define("tinymce/dom/EventUtils", [], function() {
    "use strict";

    var eventExpandoPrefix = "mce-data-";
    var mouseEventRe = /^(?:mouse|contextmenu)|click/;
    var deprecated = {keyLocation: 1, layerX: 1, layerY: 1, returnValue: 1};

    /**
     * Binds a native event to a callback on the speified target.
     */
    function addEvent(target, name, callback, capture) {
        if (target.addEventListener) {
            target.addEventListener(name, callback, capture || false);
        } else if (target.attachEvent) {
            target.attachEvent('on' + name, callback);
        }
    }

    /**
     * Unbinds a native event callback on the specified target.
     */
    function removeEvent(target, name, callback, capture) {
        if (target.removeEventListener) {
            target.removeEventListener(name, callback, capture || false);
        } else if (target.detachEvent) {
            target.detachEvent('on' + name, callback);
        }
    }

    /**
     * Normalizes a native event object or just adds the event specific methods on a custom event.
     */
    function fix(originalEvent, data) {
        var name, event = data || {}, undef;

        // Dummy function that gets replaced on the delegation state functions
        function returnFalse() {
            return false;
        }

        // Dummy function that gets replaced on the delegation state functions
        function returnTrue() {
            return true;
        }

        // Copy all properties from the original event
        for (name in originalEvent) {
            // layerX/layerY is deprecated in Chrome and produces a warning
            if (!deprecated[name]) {
                event[name] = originalEvent[name];
            }
        }

        // Normalize target IE uses srcElement
        if (!event.target) {
            event.target = event.srcElement || document;
        }

        // Calculate pageX/Y if missing and clientX/Y available
        if (originalEvent && mouseEventRe.test(originalEvent.type) && originalEvent.pageX === undef && originalEvent.clientX !== undef) {
            var eventDoc = event.target.ownerDocument || document;
            var doc = eventDoc.documentElement;
            var body = eventDoc.body;

            event.pageX = originalEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -
                ( doc && doc.clientLeft || body && body.clientLeft || 0);

            event.pageY = originalEvent.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0 ) -
                ( doc && doc.clientTop  || body && body.clientTop  || 0);
        }

        // Add preventDefault method
        event.preventDefault = function() {
            event.isDefaultPrevented = returnTrue;

            // Execute preventDefault on the original event object
            if (originalEvent) {
                if (originalEvent.preventDefault) {
                    originalEvent.preventDefault();
                } else {
                    originalEvent.returnValue = false; // IE
                }
            }
        };

        // Add stopPropagation
        event.stopPropagation = function() {
            event.isPropagationStopped = returnTrue;

            // Execute stopPropagation on the original event object
            if (originalEvent) {
                if (originalEvent.stopPropagation) {
                    originalEvent.stopPropagation();
                } else {
                    originalEvent.cancelBubble = true; // IE
                }
            }
        };

        // Add stopImmediatePropagation
        event.stopImmediatePropagation = function() {
            event.isImmediatePropagationStopped = returnTrue;
            event.stopPropagation();
        };

        // Add event delegation states
        if (!event.isDefaultPrevented) {
            event.isDefaultPrevented = returnFalse;
            event.isPropagationStopped = returnFalse;
            event.isImmediatePropagationStopped = returnFalse;
        }

        return event;
    }

    /**
     * Bind a DOMContentLoaded event across browsers and executes the callback once the page DOM is initialized.
     * It will also set/check the domLoaded state of the event_utils instance so ready isn't called multiple times.
     */
    function bindOnReady(win, callback, eventUtils) {
        var doc = win.document, event = {type: 'ready'};

        if (eventUtils.domLoaded) {
            callback(event);
            return;
        }

        // Gets called when the DOM is ready
        function readyHandler() {
            if (!eventUtils.domLoaded) {
                eventUtils.domLoaded = true;
                callback(event);
            }
        }

        function waitForDomLoaded() {
            // Check complete or interactive state if there is a body
            // element on some iframes IE 8 will produce a null body
            if (doc.readyState === "complete" || (doc.readyState === "interactive" && doc.body)) {
                removeEvent(doc, "readystatechange", waitForDomLoaded);
                readyHandler();
            }
        }

        function tryScroll() {
            try {
                // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
                // http://javascript.nwbox.com/IEContentLoaded/
                doc.documentElement.doScroll("left");
            } catch (ex) {
                setTimeout(tryScroll, 0);
                return;
            }

            readyHandler();
        }

        // Use W3C method
        if (doc.addEventListener) {
            if (doc.readyState === "complete") {
                readyHandler();
            } else {
                addEvent(win, 'DOMContentLoaded', readyHandler);
            }
        } else {
            // Use IE method
            addEvent(doc, "readystatechange", waitForDomLoaded);

            // Wait until we can scroll, when we can the DOM is initialized
            if (doc.documentElement.doScroll && win.self === win.top) {
                tryScroll();
            }
        }

        // Fallback if any of the above methods should fail for some odd reason
        addEvent(win, 'load', readyHandler);
    }

    /**
     * This class enables you to bind/unbind native events to elements and normalize it's behavior across browsers.
     */
    function EventUtils() {
        var self = this, events = {}, count, expando, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;

        expando = eventExpandoPrefix + (+new Date()).toString(32);
        hasMouseEnterLeave = "onmouseenter" in document.documentElement;
        hasFocusIn = "onfocusin" in document.documentElement;
        mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
        count = 1;

        // State if the DOMContentLoaded was executed or not
        self.domLoaded = false;
        self.events = events;

        /**
         * Executes all event handler callbacks for a specific event.
         *
         * @private
         * @param {Event} evt Event object.
         * @param {String} id Expando id value to look for.
         */
        function executeHandlers(evt, id) {
            var callbackList, i, l, callback, container = events[id];

            callbackList = container && container[evt.type];
            if (callbackList) {
                for (i = 0, l = callbackList.length; i < l; i++) {
                    callback = callbackList[i];

                    // Check if callback exists might be removed if a unbind is called inside the callback
                    if (callback && callback.func.call(callback.scope, evt) === false) {
                        evt.preventDefault();
                    }

                    // Should we stop propagation to immediate listeners
                    if (evt.isImmediatePropagationStopped()) {
                        return;
                    }
                }
            }
        }

        /**
         * Binds a callback to an event on the specified target.
         *
         * @method bind
         * @param {Object} target Target node/window or custom object.
         * @param {String} names Name of the event to bind.
         * @param {function} callback Callback function to execute when the event occurs.
         * @param {Object} scope Scope to call the callback function on, defaults to target.
         * @return {function} Callback function that got bound.
         */
        self.bind = function(target, names, callback, scope) {
            var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;

            // Native event handler function patches the event and executes the callbacks for the expando
            function defaultNativeHandler(evt) {
                executeHandlers(fix(evt || win.event), id);
            }

            // Don't bind to text nodes or comments
            if (!target || target.nodeType === 3 || target.nodeType === 8) {
                return;
            }

            // Create or get events id for the target
            if (!target[expando]) {
                id = count++;
                target[expando] = id;
                events[id] = {};
            } else {
                id = target[expando];
            }

            // Setup the specified scope or use the target as a default
            scope = scope || target;

            // Split names and bind each event, enables you to bind multiple events with one call
            names = names.split(' ');
            i = names.length;
            while (i--) {
                name = names[i];
                nativeHandler = defaultNativeHandler;
                fakeName = capture = false;

                // Use ready instead of DOMContentLoaded
                if (name === "DOMContentLoaded") {
                    name = "ready";
                }

                // DOM is already ready
                if (self.domLoaded && name === "ready" && target.readyState == 'complete') {
                    callback.call(scope, fix({type: name}));
                    continue;
                }

                // Handle mouseenter/mouseleaver
                if (!hasMouseEnterLeave) {
                    fakeName = mouseEnterLeave[name];

                    if (fakeName) {
                        nativeHandler = function(evt) {
                            var current, related;

                            current = evt.currentTarget;
                            related = evt.relatedTarget;

                            // Check if related is inside the current target if it's not then the event should
                            // be ignored since it's a mouseover/mouseout inside the element
                            if (related && current.contains) {
                                // Use contains for performance
                                related = current.contains(related);
                            } else {
                                while (related && related !== current) {
                                    related = related.parentNode;
                                }
                            }

                            // Fire fake event
                            if (!related) {
                                evt = fix(evt || win.event);
                                evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
                                evt.target = current;
                                executeHandlers(evt, id);
                            }
                        };
                    }
                }

                // Fake bubbeling of focusin/focusout
                if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
                    capture = true;
                    fakeName = name === "focusin" ? "focus" : "blur";
                    nativeHandler = function(evt) {
                        evt = fix(evt || win.event);
                        evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
                        executeHandlers(evt, id);
                    };
                }

                // Setup callback list and bind native event
                callbackList = events[id][name];
                if (!callbackList) {
                    events[id][name] = callbackList = [{func: callback, scope: scope}];
                    callbackList.fakeName = fakeName;
                    callbackList.capture = capture;

                    // Add the nativeHandler to the callback list so that we can later unbind it
                    callbackList.nativeHandler = nativeHandler;

                    // Check if the target has native events support

                    if (name === "ready") {
                        bindOnReady(target, nativeHandler, self);
                    } else {
                        addEvent(target, fakeName || name, nativeHandler, capture);
                    }
                } else {
                    if (name === "ready" && self.domLoaded) {
                        callback({type: name});
                    } else {
                        // If it already has an native handler then just push the callback
                        callbackList.push({func: callback, scope: scope});
                    }
                }
            }

            target = callbackList = 0; // Clean memory for IE

            return callback;
        };

        /**
         * Unbinds the specified event by name, name and callback or all events on the target.
         *
         * @method unbind
         * @param {Object} target Target node/window or custom object.
         * @param {String} names Optional event name to unbind.
         * @param {function} callback Optional callback function to unbind.
         * @return {EventUtils} Event utils instance.
         */
        self.unbind = function(target, names, callback) {
            var id, callbackList, i, ci, name, eventMap;

            // Don't bind to text nodes or comments
            if (!target || target.nodeType === 3 || target.nodeType === 8) {
                return self;
            }

            // Unbind event or events if the target has the expando
            id = target[expando];
            if (id) {
                eventMap = events[id];

                // Specific callback
                if (names) {
                    names = names.split(' ');
                    i = names.length;
                    while (i--) {
                        name = names[i];
                        callbackList = eventMap[name];

                        // Unbind the event if it exists in the map
                        if (callbackList) {
                            // Remove specified callback
                            if (callback) {
                                ci = callbackList.length;
                                while (ci--) {
                                    if (callbackList[ci].func === callback) {
                                        var nativeHandler = callbackList.nativeHandler;
                                        var fakeName = callbackList.fakeName, capture = callbackList.capture;

                                        // Clone callbackList since unbind inside a callback would otherwise break the handlers loop
                                        callbackList = callbackList.slice(0, ci).concat(callbackList.slice(ci + 1));
                                        callbackList.nativeHandler = nativeHandler;
                                        callbackList.fakeName = fakeName;
                                        callbackList.capture = capture;

                                        eventMap[name] = callbackList;
                                    }
                                }
                            }

                            // Remove all callbacks if there isn't a specified callback or there is no callbacks left
                            if (!callback || callbackList.length === 0) {
                                delete eventMap[name];
                                removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
                            }
                        }
                    }
                } else {
                    // All events for a specific element
                    for (name in eventMap) {
                        callbackList = eventMap[name];
                        removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
                    }

                    eventMap = {};
                }

                // Check if object is empty, if it isn't then we won't remove the expando map
                for (name in eventMap) {
                    return self;
                }

                // Delete event object
                delete events[id];

                // Remove expando from target
                try {
                    // IE will fail here since it can't delete properties from window
                    delete target[expando];
                } catch (ex) {
                    // IE will set it to null
                    target[expando] = null;
                }
            }

            return self;
        };

        /**
         * Fires the specified event on the specified target.
         *
         * @method fire
         * @param {Object} target Target node/window or custom object.
         * @param {String} name Event name to fire.
         * @param {Object} args Optional arguments to send to the observers.
         * @return {EventUtils} Event utils instance.
         */
        self.fire = function(target, name, args) {
            var id;

            // Don't bind to text nodes or comments
            if (!target || target.nodeType === 3 || target.nodeType === 8) {
                return self;
            }

            // Build event object by patching the args
            args = fix(null, args);
            args.type = name;
            args.target = target;

            do {
                // Found an expando that means there is listeners to execute
                id = target[expando];
                if (id) {
                    executeHandlers(args, id);
                }

                // Walk up the DOM
                target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
            } while (target && !args.isPropagationStopped());

            return self;
        };

        /**
         * Removes all bound event listeners for the specified target. This will also remove any bound
         * listeners to child nodes within that target.
         *
         * @method clean
         * @param {Object} target Target node/window object.
         * @return {EventUtils} Event utils instance.
         */
        self.clean = function(target) {
            var i, children, unbind = self.unbind;

            // Don't bind to text nodes or comments
            if (!target || target.nodeType === 3 || target.nodeType === 8) {
                return self;
            }

            // Unbind any element on the specificed target
            if (target[expando]) {
                unbind(target);
            }

            // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
            if (!target.getElementsByTagName) {
                target = target.document;
            }

            // Remove events from each child element
            if (target && target.getElementsByTagName) {
                unbind(target);

                children = target.getElementsByTagName('*');
                i = children.length;
                while (i--) {
                    target = children[i];

                    if (target[expando]) {
                        unbind(target);
                    }
                }
            }

            return self;
        };

        /**
         * Destroys the event object. Call this on IE to remove memory leaks.
         */
        self.destroy = function() {
            events = {};
        };

        // Legacy function for canceling events
        self.cancel = function(e) {
            if (e) {
                e.preventDefault();
                e.stopImmediatePropagation();
            }

            return false;
        };
    }

    EventUtils.Event = new EventUtils();
    EventUtils.Event.bind(window, 'ready', function() {});

    return EventUtils;
});

// Included from: js/tinymce/classes/dom/Sizzle.js

/**
 * Sizzle.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 *
 * @ignore-file
 */

/*jshint bitwise:false, expr:true, noempty:false, sub:true, eqnull:true, latedef:false, maxlen:255 */
/*eslint dot-notation:0, no-empty:0, no-cond-assign:0, no-unused-expressions:0, new-cap:0 */
/*eslint no-nested-ternary:0, func-style:0, no-bitwise:0, max-len:0, brace-style:0, no-return-assign:0 */

/**
 * Sizzle CSS Selector Engine v@VERSION
 * http://sizzlejs.com/
 *
 * Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors
 * Released under the MIT license
 * http://jquery.org/license
 *
 * Date: @DATE
 */
define("tinymce/dom/Sizzle", [], function() {
var i,
    support,
    Expr,
    getText,
    isXML,
    tokenize,
    compile,
    select,
    outermostContext,
    sortInput,
    hasDuplicate,

    // Local document vars
    setDocument,
    document,
    docElem,
    documentIsHTML,
    rbuggyQSA,
    rbuggyMatches,
    matches,
    contains,

    // Instance-specific data
    expando = "sizzle" + -(new Date()),
    preferredDoc = window.document,
    dirruns = 0,
    done = 0,
    classCache = createCache(),
    tokenCache = createCache(),
    compilerCache = createCache(),
    sortOrder = function( a, b ) {
        if ( a === b ) {
            hasDuplicate = true;
        }
        return 0;
    },

    // General-purpose constants
    strundefined = typeof undefined,
    MAX_NEGATIVE = 1 << 31,

    // Instance methods
    hasOwn = ({}).hasOwnProperty,
    arr = [],
    pop = arr.pop,
    push_native = arr.push,
    push = arr.push,
    slice = arr.slice,
    // Use a stripped-down indexOf if we can't use a native one
    indexOf = arr.indexOf || function( elem ) {
        var i = 0,
            len = this.length;
        for ( ; i < len; i++ ) {
            if ( this[i] === elem ) {
                return i;
            }
        }
        return -1;
    },

    booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",

    // Regular expressions

    // http://www.w3.org/TR/css3-selectors/#whitespace
    whitespace = "[\\x20\\t\\r\\n\\f]",

    // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
    identifier = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",

    // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
    attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
        // Operator (capture 2)
        "*([*^$|!~]?=)" + whitespace +
        // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
        "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
        "*\\]",

    pseudos = ":(" + identifier + ")(?:\\((" +
        // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
        // 1. quoted (capture 3; capture 4 or capture 5)
        "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
        // 2. simple (capture 6)
        "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
        // 3. anything else (capture 2)
        ".*" +
        ")\\)|)",

    // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
    rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),

    rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
    rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),

    rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),

    rpseudo = new RegExp( pseudos ),
    ridentifier = new RegExp( "^" + identifier + "$" ),

    matchExpr = {
        "ID": new RegExp( "^#(" + identifier + ")" ),
        "CLASS": new RegExp( "^\\.(" + identifier + ")" ),
        "TAG": new RegExp( "^(" + identifier + "|[*])" ),
        "ATTR": new RegExp( "^" + attributes ),
        "PSEUDO": new RegExp( "^" + pseudos ),
        "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
            "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
            "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
        "bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
        // For use in libraries implementing .is()
        // We use this for POS matching in `select`
        "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
            whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
    },

    rinputs = /^(?:input|select|textarea|button)$/i,
    rheader = /^h\d$/i,

    rnative = /^[^{]+\{\s*\[native \w/,

    // Easily-parseable/retrievable ID or TAG or CLASS selectors
    rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,

    rsibling = /[+~]/,
    rescape = /'|\\/g,

    // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
    runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
    funescape = function( _, escaped, escapedWhitespace ) {
        var high = "0x" + escaped - 0x10000;
        // NaN means non-codepoint
        // Support: Firefox<24
        // Workaround erroneous numeric interpretation of +"0x"
        return high !== high || escapedWhitespace ?
            escaped :
            high < 0 ?
                // BMP codepoint
                String.fromCharCode( high + 0x10000 ) :
                // Supplemental Plane codepoint (surrogate pair)
                String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
    };

// Optimize for push.apply( _, NodeList )
try {
    push.apply(
        (arr = slice.call( preferredDoc.childNodes )),
        preferredDoc.childNodes
    );
    // Support: Android<4.0
    // Detect silently failing push.apply
    arr[ preferredDoc.childNodes.length ].nodeType;
} catch ( e ) {
    push = { apply: arr.length ?

        // Leverage slice if possible
        function( target, els ) {
            push_native.apply( target, slice.call(els) );
        } :

        // Support: IE<9
        // Otherwise append directly
        function( target, els ) {
            var j = target.length,
                i = 0;
            // Can't trust NodeList.length
            while ( (target[j++] = els[i++]) ) {}
            target.length = j - 1;
        }
    };
}

function Sizzle( selector, context, results, seed ) {
    var match, elem, m, nodeType,
        // QSA vars
        i, groups, old, nid, newContext, newSelector;

    if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
        setDocument( context );
    }

    context = context || document;
    results = results || [];

    if ( !selector || typeof selector !== "string" ) {
        return results;
    }

    if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
        return [];
    }

    if ( documentIsHTML && !seed ) {

        // Shortcuts
        if ( (match = rquickExpr.exec( selector )) ) {
            // Speed-up: Sizzle("#ID")
            if ( (m = match[1]) ) {
                if ( nodeType === 9 ) {
                    elem = context.getElementById( m );
                    // Check parentNode to catch when Blackberry 4.6 returns
                    // nodes that are no longer in the document (jQuery #6963)
                    if ( elem && elem.parentNode ) {
                        // Handle the case where IE, Opera, and Webkit return items
                        // by name instead of ID
                        if ( elem.id === m ) {
                            results.push( elem );
                            return results;
                        }
                    } else {
                        return results;
                    }
                } else {
                    // Context is not a document
                    if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
                        contains( context, elem ) && elem.id === m ) {
                        results.push( elem );
                        return results;
                    }
                }

            // Speed-up: Sizzle("TAG")
            } else if ( match[2] ) {
                push.apply( results, context.getElementsByTagName( selector ) );
                return results;

            // Speed-up: Sizzle(".CLASS")
            } else if ( (m = match[3]) && support.getElementsByClassName ) {
                push.apply( results, context.getElementsByClassName( m ) );
                return results;
            }
        }

        // QSA path
        if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
            nid = old = expando;
            newContext = context;
            newSelector = nodeType === 9 && selector;

            // qSA works strangely on Element-rooted queries
            // We can work around this by specifying an extra ID on the root
            // and working up from there (Thanks to Andrew Dupont for the technique)
            // IE 8 doesn't work on object elements
            if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
                groups = tokenize( selector );

                if ( (old = context.getAttribute("id")) ) {
                    nid = old.replace( rescape, "\\$&" );
                } else {
                    context.setAttribute( "id", nid );
                }
                nid = "[id='" + nid + "'] ";

                i = groups.length;
                while ( i-- ) {
                    groups[i] = nid + toSelector( groups[i] );
                }
                newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;
                newSelector = groups.join(",");
            }

            if ( newSelector ) {
                try {
                    push.apply( results,
                        newContext.querySelectorAll( newSelector )
                    );
                    return results;
                } catch(qsaError) {
                } finally {
                    if ( !old ) {
                        context.removeAttribute("id");
                    }
                }
            }
        }
    }

    // All others
    return select( selector.replace( rtrim, "$1" ), context, results, seed );
}

/**
 * Create key-value caches of limited size
 * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
 *    property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
 *    deleting the oldest entry
 */
function createCache() {
    var keys = [];

    function cache( key, value ) {
        // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
        if ( keys.push( key + " " ) > Expr.cacheLength ) {
            // Only keep the most recent entries
            delete cache[ keys.shift() ];
        }
        return (cache[ key + " " ] = value);
    }
    return cache;
}

/**
 * Mark a function for special use by Sizzle
 * @param {Function} fn The function to mark
 */
function markFunction( fn ) {
    fn[ expando ] = true;
    return fn;
}

/**
 * Support testing using an element
 * @param {Function} fn Passed the created div and expects a boolean result
 */
function assert( fn ) {
    var div = document.createElement("div");

    try {
        return !!fn( div );
    } catch (e) {
        return false;
    } finally {
        // Remove from its parent by default
        if ( div.parentNode ) {
            div.parentNode.removeChild( div );
        }
        // release memory in IE
        div = null;
    }
}

/**
 * Adds the same handler for all of the specified attrs
 * @param {String} attrs Pipe-separated list of attributes
 * @param {Function} handler The method that will be applied
 */
function addHandle( attrs, handler ) {
    var arr = attrs.split("|"),
        i = attrs.length;

    while ( i-- ) {
        Expr.attrHandle[ arr[i] ] = handler;
    }
}

/**
 * Checks document order of two siblings
 * @param {Element} a
 * @param {Element} b
 * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
 */
function siblingCheck( a, b ) {
    var cur = b && a,
        diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
            ( ~b.sourceIndex || MAX_NEGATIVE ) -
            ( ~a.sourceIndex || MAX_NEGATIVE );

    // Use IE sourceIndex if available on both nodes
    if ( diff ) {
        return diff;
    }

    // Check if b follows a
    if ( cur ) {
        while ( (cur = cur.nextSibling) ) {
            if ( cur === b ) {
                return -1;
            }
        }
    }

    return a ? 1 : -1;
}

/**
 * Returns a function to use in pseudos for input types
 * @param {String} type
 */
function createInputPseudo( type ) {
    return function( elem ) {
        var name = elem.nodeName.toLowerCase();
        return name === "input" && elem.type === type;
    };
}

/**
 * Returns a function to use in pseudos for buttons
 * @param {String} type
 */
function createButtonPseudo( type ) {
    return function( elem ) {
        var name = elem.nodeName.toLowerCase();
        return (name === "input" || name === "button") && elem.type === type;
    };
}

/**
 * Returns a function to use in pseudos for positionals
 * @param {Function} fn
 */
function createPositionalPseudo( fn ) {
    return markFunction(function( argument ) {
        argument = +argument;
        return markFunction(function( seed, matches ) {
            var j,
                matchIndexes = fn( [], seed.length, argument ),
                i = matchIndexes.length;

            // Match elements found at the specified indexes
            while ( i-- ) {
                if ( seed[ (j = matchIndexes[i]) ] ) {
                    seed[j] = !(matches[j] = seed[j]);
                }
            }
        });
    });
}

/**
 * Checks a node for validity as a Sizzle context
 * @param {Element|Object=} context
 * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
 */
function testContext( context ) {
    return context && typeof context.getElementsByTagName !== strundefined && context;
}

// Expose support vars for convenience
support = Sizzle.support = {};

/**
 * Detects XML nodes
 * @param {Element|Object} elem An element or a document
 * @returns {Boolean} True iff elem is a non-HTML XML node
 */
isXML = Sizzle.isXML = function( elem ) {
    // documentElement is verified for cases where it doesn't yet exist
    // (such as loading iframes in IE - #4833)
    var documentElement = elem && (elem.ownerDocument || elem).documentElement;
    return documentElement ? documentElement.nodeName !== "HTML" : false;
};

/**
 * Sets document-related variables once based on the current document
 * @param {Element|Object} [doc] An element or document object to use to set the document
 * @returns {Object} Returns the current document
 */
setDocument = Sizzle.setDocument = function( node ) {
    var hasCompare,
        doc = node ? node.ownerDocument || node : preferredDoc,
        parent = doc.defaultView;

    // If no document and documentElement is available, return
    if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
        return document;
    }

    // Set our document
    document = doc;
    docElem = doc.documentElement;

    // Support tests
    documentIsHTML = !isXML( doc );

    // Support: IE>8
    // If iframe document is assigned to "document" variable and if iframe has been reloaded,
    // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936
    // IE6-8 do not support the defaultView property so parent will be undefined
    if ( parent && parent !== parent.top ) {
        // IE11 does not have attachEvent, so all must suffer
        if ( parent.addEventListener ) {
            parent.addEventListener( "unload", function() {
                setDocument();
            }, false );
        } else if ( parent.attachEvent ) {
            parent.attachEvent( "onunload", function() {
                setDocument();
            });
        }
    }

    /* Attributes
    ---------------------------------------------------------------------- */

    // Support: IE<8
    // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans)
    support.attributes = assert(function( div ) {
        div.className = "i";
        return !div.getAttribute("className");
    });

    /* getElement(s)By*
    ---------------------------------------------------------------------- */

    // Check if getElementsByTagName("*") returns only elements
    support.getElementsByTagName = assert(function( div ) {
        div.appendChild( doc.createComment("") );
        return !div.getElementsByTagName("*").length;
    });

    // Support: IE<9
    support.getElementsByClassName = rnative.test( doc.getElementsByClassName );

    // Support: IE<10
    // Check if getElementById returns elements by name
    // The broken getElementById methods don't pick up programatically-set names,
    // so use a roundabout getElementsByName test
    support.getById = assert(function( div ) {
        docElem.appendChild( div ).id = expando;
        return !doc.getElementsByName || !doc.getElementsByName( expando ).length;
    });

    // ID find and filter
    if ( support.getById ) {
        Expr.find["ID"] = function( id, context ) {
            if ( typeof context.getElementById !== strundefined && documentIsHTML ) {
                var m = context.getElementById( id );
                // Check parentNode to catch when Blackberry 4.6 returns
                // nodes that are no longer in the document #6963
                return m && m.parentNode ? [ m ] : [];
            }
        };
        Expr.filter["ID"] = function( id ) {
            var attrId = id.replace( runescape, funescape );
            return function( elem ) {
                return elem.getAttribute("id") === attrId;
            };
        };
    } else {
        // Support: IE6/7
        // getElementById is not reliable as a find shortcut
        delete Expr.find["ID"];

        Expr.filter["ID"] =  function( id ) {
            var attrId = id.replace( runescape, funescape );
            return function( elem ) {
                var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
                return node && node.value === attrId;
            };
        };
    }

    // Tag
    Expr.find["TAG"] = support.getElementsByTagName ?
        function( tag, context ) {
            if ( typeof context.getElementsByTagName !== strundefined ) {
                return context.getElementsByTagName( tag );
            }
        } :
        function( tag, context ) {
            var elem,
                tmp = [],
                i = 0,
                results = context.getElementsByTagName( tag );

            // Filter out possible comments
            if ( tag === "*" ) {
                while ( (elem = results[i++]) ) {
                    if ( elem.nodeType === 1 ) {
                        tmp.push( elem );
                    }
                }

                return tmp;
            }
            return results;
        };

    // Class
    Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
        if ( documentIsHTML ) {
            return context.getElementsByClassName( className );
        }
    };

    /* QSA/matchesSelector
    ---------------------------------------------------------------------- */

    // QSA and matchesSelector support

    // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
    rbuggyMatches = [];

    // qSa(:focus) reports false when true (Chrome 21)
    // We allow this because of a bug in IE8/9 that throws an error
    // whenever `document.activeElement` is accessed on an iframe
    // So, we allow :focus to pass through QSA all the time to avoid the IE error
    // See http://bugs.jquery.com/ticket/13378
    rbuggyQSA = [];

    if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {
        // Build QSA regex
        // Regex strategy adopted from Diego Perini
        assert(function( div ) {
            // Select is set to empty string on purpose
            // This is to test IE's treatment of not explicitly
            // setting a boolean content attribute,
            // since its presence should be enough
            // http://bugs.jquery.com/ticket/12359
            div.innerHTML = "<select msallowcapture=''><option selected=''></option></select>";

            // Support: IE8, Opera 11-12.16
            // Nothing should be selected when empty strings follow ^= or $= or *=
            // The test attribute must be unknown in Opera but "safe" for WinRT
            // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
            if ( div.querySelectorAll("[msallowcapture^='']").length ) {
                rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
            }

            // Support: IE8
            // Boolean attributes and "value" are not treated correctly
            if ( !div.querySelectorAll("[selected]").length ) {
                rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
            }

            // Webkit/Opera - :checked should return selected option elements
            // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
            // IE8 throws error here and will not see later tests
            if ( !div.querySelectorAll(":checked").length ) {
                rbuggyQSA.push(":checked");
            }
        });

        assert(function( div ) {
            // Support: Windows 8 Native Apps
            // The type and name attributes are restricted during .innerHTML assignment
            var input = doc.createElement("input");
            input.setAttribute( "type", "hidden" );
            div.appendChild( input ).setAttribute( "name", "D" );

            // Support: IE8
            // Enforce case-sensitivity of name attribute
            if ( div.querySelectorAll("[name=d]").length ) {
                rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
            }

            // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
            // IE8 throws error here and will not see later tests
            if ( !div.querySelectorAll(":enabled").length ) {
                rbuggyQSA.push( ":enabled", ":disabled" );
            }

            // Opera 10-11 does not throw on post-comma invalid pseudos
            div.querySelectorAll("*,:x");
            rbuggyQSA.push(",.*:");
        });
    }

    if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
        docElem.webkitMatchesSelector ||
        docElem.mozMatchesSelector ||
        docElem.oMatchesSelector ||
        docElem.msMatchesSelector) )) ) {

        assert(function( div ) {
            // Check to see if it's possible to do matchesSelector
            // on a disconnected node (IE 9)
            support.disconnectedMatch = matches.call( div, "div" );

            // This should fail with an exception
            // Gecko does not error, returns false instead
            matches.call( div, "[s!='']:x" );
            rbuggyMatches.push( "!=", pseudos );
        });
    }

    rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
    rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );

    /* Contains
    ---------------------------------------------------------------------- */
    hasCompare = rnative.test( docElem.compareDocumentPosition );

    // Element contains another
    // Purposefully does not implement inclusive descendent
    // As in, an element does not contain itself
    contains = hasCompare || rnative.test( docElem.contains ) ?
        function( a, b ) {
            var adown = a.nodeType === 9 ? a.documentElement : a,
                bup = b && b.parentNode;
            return a === bup || !!( bup && bup.nodeType === 1 && (
                adown.contains ?
                    adown.contains( bup ) :
                    a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
            ));
        } :
        function( a, b ) {
            if ( b ) {
                while ( (b = b.parentNode) ) {
                    if ( b === a ) {
                        return true;
                    }
                }
            }
            return false;
        };

    /* Sorting
    ---------------------------------------------------------------------- */

    // Document order sorting
    sortOrder = hasCompare ?
    function( a, b ) {

        // Flag for duplicate removal
        if ( a === b ) {
            hasDuplicate = true;
            return 0;
        }

        // Sort on method existence if only one input has compareDocumentPosition
        var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
        if ( compare ) {
            return compare;
        }

        // Calculate position if both inputs belong to the same document
        compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
            a.compareDocumentPosition( b ) :

            // Otherwise we know they are disconnected
            1;

        // Disconnected nodes
        if ( compare & 1 ||
            (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {

            // Choose the first element that is related to our preferred document
            if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
                return -1;
            }
            if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
                return 1;
            }

            // Maintain original order
            return sortInput ?
                ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
                0;
        }

        return compare & 4 ? -1 : 1;
    } :
    function( a, b ) {
        // Exit early if the nodes are identical
        if ( a === b ) {
            hasDuplicate = true;
            return 0;
        }

        var cur,
            i = 0,
            aup = a.parentNode,
            bup = b.parentNode,
            ap = [ a ],
            bp = [ b ];

        // Parentless nodes are either documents or disconnected
        if ( !aup || !bup ) {
            return a === doc ? -1 :
                b === doc ? 1 :
                aup ? -1 :
                bup ? 1 :
                sortInput ?
                ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
                0;

        // If the nodes are siblings, we can do a quick check
        } else if ( aup === bup ) {
            return siblingCheck( a, b );
        }

        // Otherwise we need full lists of their ancestors for comparison
        cur = a;
        while ( (cur = cur.parentNode) ) {
            ap.unshift( cur );
        }
        cur = b;
        while ( (cur = cur.parentNode) ) {
            bp.unshift( cur );
        }

        // Walk down the tree looking for a discrepancy
        while ( ap[i] === bp[i] ) {
            i++;
        }

        return i ?
            // Do a sibling check if the nodes have a common ancestor
            siblingCheck( ap[i], bp[i] ) :

            // Otherwise nodes in our document sort first
            ap[i] === preferredDoc ? -1 :
            bp[i] === preferredDoc ? 1 :
            0;
    };

    return doc;
};

Sizzle.matches = function( expr, elements ) {
    return Sizzle( expr, null, null, elements );
};

Sizzle.matchesSelector = function( elem, expr ) {
    // Set document vars if needed
    if ( ( elem.ownerDocument || elem ) !== document ) {
        setDocument( elem );
    }

    // Make sure that attribute selectors are quoted
    expr = expr.replace( rattributeQuotes, "='$1']" );

    if ( support.matchesSelector && documentIsHTML &&
        ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
        ( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {

        try {
            var ret = matches.call( elem, expr );

            // IE 9's matchesSelector returns false on disconnected nodes
            if ( ret || support.disconnectedMatch ||
                    // As well, disconnected nodes are said to be in a document
                    // fragment in IE 9
                    elem.document && elem.document.nodeType !== 11 ) {
                return ret;
            }
        } catch(e) {}
    }

    return Sizzle( expr, document, null, [ elem ] ).length > 0;
};

Sizzle.contains = function( context, elem ) {
    // Set document vars if needed
    if ( ( context.ownerDocument || context ) !== document ) {
        setDocument( context );
    }
    return contains( context, elem );
};

Sizzle.attr = function( elem, name ) {
    // Set document vars if needed
    if ( ( elem.ownerDocument || elem ) !== document ) {
        setDocument( elem );
    }

    var fn = Expr.attrHandle[ name.toLowerCase() ],
        // Don't get fooled by Object.prototype properties (jQuery #13807)
        val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
            fn( elem, name, !documentIsHTML ) :
            undefined;

    return val !== undefined ?
        val :
        support.attributes || !documentIsHTML ?
            elem.getAttribute( name ) :
            (val = elem.getAttributeNode(name)) && val.specified ?
                val.value :
                null;
};

Sizzle.error = function( msg ) {
    throw new Error( "Syntax error, unrecognized expression: " + msg );
};

/**
 * Document sorting and removing duplicates
 * @param {ArrayLike} results
 */
Sizzle.uniqueSort = function( results ) {
    var elem,
        duplicates = [],
        j = 0,
        i = 0;

    // Unless we *know* we can detect duplicates, assume their presence
    hasDuplicate = !support.detectDuplicates;
    sortInput = !support.sortStable && results.slice( 0 );
    results.sort( sortOrder );

    if ( hasDuplicate ) {
        while ( (elem = results[i++]) ) {
            if ( elem === results[ i ] ) {
                j = duplicates.push( i );
            }
        }
        while ( j-- ) {
            results.splice( duplicates[ j ], 1 );
        }
    }

    // Clear input after sorting to release objects
    // See https://github.com/jquery/sizzle/pull/225
    sortInput = null;

    return results;
};

/**
 * Utility function for retrieving the text value of an array of DOM nodes
 * @param {Array|Element} elem
 */
getText = Sizzle.getText = function( elem ) {
    var node,
        ret = "",
        i = 0,
        nodeType = elem.nodeType;

    if ( !nodeType ) {
        // If no nodeType, this is expected to be an array
        while ( (node = elem[i++]) ) {
            // Do not traverse comment nodes
            ret += getText( node );
        }
    } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
        // Use textContent for elements
        // innerText usage removed for consistency of new lines (jQuery #11153)
        if ( typeof elem.textContent === "string" ) {
            return elem.textContent;
        } else {
            // Traverse its children
            for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
                ret += getText( elem );
            }
        }
    } else if ( nodeType === 3 || nodeType === 4 ) {
        return elem.nodeValue;
    }
    // Do not include comment or processing instruction nodes

    return ret;
};

Expr = Sizzle.selectors = {

    // Can be adjusted by the user
    cacheLength: 50,

    createPseudo: markFunction,

    match: matchExpr,

    attrHandle: {},

    find: {},

    relative: {
        ">": { dir: "parentNode", first: true },
        " ": { dir: "parentNode" },
        "+": { dir: "previousSibling", first: true },
        "~": { dir: "previousSibling" }
    },

    preFilter: {
        "ATTR": function( match ) {
            match[1] = match[1].replace( runescape, funescape );

            // Move the given value to match[3] whether quoted or unquoted
            match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape );

            if ( match[2] === "~=" ) {
                match[3] = " " + match[3] + " ";
            }

            return match.slice( 0, 4 );
        },

        "CHILD": function( match ) {
            /* matches from matchExpr["CHILD"]
                1 type (only|nth|...)
                2 what (child|of-type)
                3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
                4 xn-component of xn+y argument ([+-]?\d*n|)
                5 sign of xn-component
                6 x of xn-component
                7 sign of y-component
                8 y of y-component
            */
            match[1] = match[1].toLowerCase();

            if ( match[1].slice( 0, 3 ) === "nth" ) {
                // nth-* requires argument
                if ( !match[3] ) {
                    Sizzle.error( match[0] );
                }

                // numeric x and y parameters for Expr.filter.CHILD
                // remember that false/true cast respectively to 0/1
                match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
                match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );

            // other types prohibit arguments
            } else if ( match[3] ) {
                Sizzle.error( match[0] );
            }

            return match;
        },

        "PSEUDO": function( match ) {
            var excess,
                unquoted = !match[6] && match[2];

            if ( matchExpr["CHILD"].test( match[0] ) ) {
                return null;
            }

            // Accept quoted arguments as-is
            if ( match[3] ) {
                match[2] = match[4] || match[5] || "";

            // Strip excess characters from unquoted arguments
            } else if ( unquoted && rpseudo.test( unquoted ) &&
                // Get excess from tokenize (recursively)
                (excess = tokenize( unquoted, true )) &&
                // advance to the next closing parenthesis
                (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {

                // excess is a negative index
                match[0] = match[0].slice( 0, excess );
                match[2] = unquoted.slice( 0, excess );
            }

            // Return only captures needed by the pseudo filter method (type and argument)
            return match.slice( 0, 3 );
        }
    },

    filter: {

        "TAG": function( nodeNameSelector ) {
            var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
            return nodeNameSelector === "*" ?
                function() { return true; } :
                function( elem ) {
                    return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
                };
        },

        "CLASS": function( className ) {
            var pattern = classCache[ className + " " ];

            return pattern ||
                (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
                classCache( className, function( elem ) {
                    return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" );
                });
        },

        "ATTR": function( name, operator, check ) {
            return function( elem ) {
                var result = Sizzle.attr( elem, name );

                if ( result == null ) {
                    return operator === "!=";
                }
                if ( !operator ) {
                    return true;
                }

                result += "";

                return operator === "=" ? result === check :
                    operator === "!=" ? result !== check :
                    operator === "^=" ? check && result.indexOf( check ) === 0 :
                    operator === "*=" ? check && result.indexOf( check ) > -1 :
                    operator === "$=" ? check && result.slice( -check.length ) === check :
                    operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
                    operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
                    false;
            };
        },

        "CHILD": function( type, what, argument, first, last ) {
            var simple = type.slice( 0, 3 ) !== "nth",
                forward = type.slice( -4 ) !== "last",
                ofType = what === "of-type";

            return first === 1 && last === 0 ?

                // Shortcut for :nth-*(n)
                function( elem ) {
                    return !!elem.parentNode;
                } :

                function( elem, context, xml ) {
                    var cache, outerCache, node, diff, nodeIndex, start,
                        dir = simple !== forward ? "nextSibling" : "previousSibling",
                        parent = elem.parentNode,
                        name = ofType && elem.nodeName.toLowerCase(),
                        useCache = !xml && !ofType;

                    if ( parent ) {

                        // :(first|last|only)-(child|of-type)
                        if ( simple ) {
                            while ( dir ) {
                                node = elem;
                                while ( (node = node[ dir ]) ) {
                                    if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
                                        return false;
                                    }
                                }
                                // Reverse direction for :only-* (if we haven't yet done so)
                                start = dir = type === "only" && !start && "nextSibling";
                            }
                            return true;
                        }

                        start = [ forward ? parent.firstChild : parent.lastChild ];

                        // non-xml :nth-child(...) stores cache data on `parent`
                        if ( forward && useCache ) {
                            // Seek `elem` from a previously-cached index
                            outerCache = parent[ expando ] || (parent[ expando ] = {});
                            cache = outerCache[ type ] || [];
                            nodeIndex = cache[0] === dirruns && cache[1];
                            diff = cache[0] === dirruns && cache[2];
                            node = nodeIndex && parent.childNodes[ nodeIndex ];

                            while ( (node = ++nodeIndex && node && node[ dir ] ||

                                // Fallback to seeking `elem` from the start
                                (diff = nodeIndex = 0) || start.pop()) ) {

                                // When found, cache indexes on `parent` and break
                                if ( node.nodeType === 1 && ++diff && node === elem ) {
                                    outerCache[ type ] = [ dirruns, nodeIndex, diff ];
                                    break;
                                }
                            }

                        // Use previously-cached element index if available
                        } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
                            diff = cache[1];

                        // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
                        } else {
                            // Use the same loop as above to seek `elem` from the start
                            while ( (node = ++nodeIndex && node && node[ dir ] ||
                                (diff = nodeIndex = 0) || start.pop()) ) {

                                if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
                                    // Cache the index of each encountered element
                                    if ( useCache ) {
                                        (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
                                    }

                                    if ( node === elem ) {
                                        break;
                                    }
                                }
                            }
                        }

                        // Incorporate the offset, then check against cycle size
                        diff -= last;
                        return diff === first || ( diff % first === 0 && diff / first >= 0 );
                    }
                };
        },

        "PSEUDO": function( pseudo, argument ) {
            // pseudo-class names are case-insensitive
            // http://www.w3.org/TR/selectors/#pseudo-classes
            // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
            // Remember that setFilters inherits from pseudos
            var args,
                fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
                    Sizzle.error( "unsupported pseudo: " + pseudo );

            // The user may use createPseudo to indicate that
            // arguments are needed to create the filter function
            // just as Sizzle does
            if ( fn[ expando ] ) {
                return fn( argument );
            }

            // But maintain support for old signatures
            if ( fn.length > 1 ) {
                args = [ pseudo, pseudo, "", argument ];
                return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
                    markFunction(function( seed, matches ) {
                        var idx,
                            matched = fn( seed, argument ),
                            i = matched.length;
                        while ( i-- ) {
                            idx = indexOf.call( seed, matched[i] );
                            seed[ idx ] = !( matches[ idx ] = matched[i] );
                        }
                    }) :
                    function( elem ) {
                        return fn( elem, 0, args );
                    };
            }

            return fn;
        }
    },

    pseudos: {
        // Potentially complex pseudos
        "not": markFunction(function( selector ) {
            // Trim the selector passed to compile
            // to avoid treating leading and trailing
            // spaces as combinators
            var input = [],
                results = [],
                matcher = compile( selector.replace( rtrim, "$1" ) );

            return matcher[ expando ] ?
                markFunction(function( seed, matches, context, xml ) {
                    var elem,
                        unmatched = matcher( seed, null, xml, [] ),
                        i = seed.length;

                    // Match elements unmatched by `matcher`
                    while ( i-- ) {
                        if ( (elem = unmatched[i]) ) {
                            seed[i] = !(matches[i] = elem);
                        }
                    }
                }) :
                function( elem, context, xml ) {
                    input[0] = elem;
                    matcher( input, null, xml, results );
                    return !results.pop();
                };
        }),

        "has": markFunction(function( selector ) {
            return function( elem ) {
                return Sizzle( selector, elem ).length > 0;
            };
        }),

        "contains": markFunction(function( text ) {
            text = text.replace( runescape, funescape );
            return function( elem ) {
                return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
            };
        }),

        // "Whether an element is represented by a :lang() selector
        // is based solely on the element's language value
        // being equal to the identifier C,
        // or beginning with the identifier C immediately followed by "-".
        // The matching of C against the element's language value is performed case-insensitively.
        // The identifier C does not have to be a valid language name."
        // http://www.w3.org/TR/selectors/#lang-pseudo
        "lang": markFunction( function( lang ) {
            // lang value must be a valid identifier
            if ( !ridentifier.test(lang || "") ) {
                Sizzle.error( "unsupported lang: " + lang );
            }
            lang = lang.replace( runescape, funescape ).toLowerCase();
            return function( elem ) {
                var elemLang;
                do {
                    if ( (elemLang = documentIsHTML ?
                        elem.lang :
                        elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {

                        elemLang = elemLang.toLowerCase();
                        return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
                    }
                } while ( (elem = elem.parentNode) && elem.nodeType === 1 );
                return false;
            };
        }),

        // Miscellaneous
        "target": function( elem ) {
            var hash = window.location && window.location.hash;
            return hash && hash.slice( 1 ) === elem.id;
        },

        "root": function( elem ) {
            return elem === docElem;
        },

        "focus": function( elem ) {
            return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
        },

        // Boolean properties
        "enabled": function( elem ) {
            return elem.disabled === false;
        },

        "disabled": function( elem ) {
            return elem.disabled === true;
        },

        "checked": function( elem ) {
            // In CSS3, :checked should return both checked and selected elements
            // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
            var nodeName = elem.nodeName.toLowerCase();
            return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
        },

        "selected": function( elem ) {
            // Accessing this property makes selected-by-default
            // options in Safari work properly
            if ( elem.parentNode ) {
                elem.parentNode.selectedIndex;
            }

            return elem.selected === true;
        },

        // Contents
        "empty": function( elem ) {
            // http://www.w3.org/TR/selectors/#empty-pseudo
            // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
            //   but not by others (comment: 8; processing instruction: 7; etc.)
            // nodeType < 6 works because attributes (2) do not appear as children
            for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
                if ( elem.nodeType < 6 ) {
                    return false;
                }
            }
            return true;
        },

        "parent": function( elem ) {
            return !Expr.pseudos["empty"]( elem );
        },

        // Element/input types
        "header": function( elem ) {
            return rheader.test( elem.nodeName );
        },

        "input": function( elem ) {
            return rinputs.test( elem.nodeName );
        },

        "button": function( elem ) {
            var name = elem.nodeName.toLowerCase();
            return name === "input" && elem.type === "button" || name === "button";
        },

        "text": function( elem ) {
            var attr;
            return elem.nodeName.toLowerCase() === "input" &&
                elem.type === "text" &&

                // Support: IE<8
                // New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
                ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
        },

        // Position-in-collection
        "first": createPositionalPseudo(function() {
            return [ 0 ];
        }),

        "last": createPositionalPseudo(function( matchIndexes, length ) {
            return [ length - 1 ];
        }),

        "eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
            return [ argument < 0 ? argument + length : argument ];
        }),

        "even": createPositionalPseudo(function( matchIndexes, length ) {
            var i = 0;
            for ( ; i < length; i += 2 ) {
                matchIndexes.push( i );
            }
            return matchIndexes;
        }),

        "odd": createPositionalPseudo(function( matchIndexes, length ) {
            var i = 1;
            for ( ; i < length; i += 2 ) {
                matchIndexes.push( i );
            }
            return matchIndexes;
        }),

        "lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
            var i = argument < 0 ? argument + length : argument;
            for ( ; --i >= 0; ) {
                matchIndexes.push( i );
            }
            return matchIndexes;
        }),

        "gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
            var i = argument < 0 ? argument + length : argument;
            for ( ; ++i < length; ) {
                matchIndexes.push( i );
            }
            return matchIndexes;
        })
    }
};

Expr.pseudos["nth"] = Expr.pseudos["eq"];

// Add button/input type pseudos
for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
    Expr.pseudos[ i ] = createInputPseudo( i );
}
for ( i in { submit: true, reset: true } ) {
    Expr.pseudos[ i ] = createButtonPseudo( i );
}

// Easy API for creating new setFilters
function setFilters() {}
setFilters.prototype = Expr.filters = Expr.pseudos;
Expr.setFilters = new setFilters();

tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
    var matched, match, tokens, type,
        soFar, groups, preFilters,
        cached = tokenCache[ selector + " " ];

    if ( cached ) {
        return parseOnly ? 0 : cached.slice( 0 );
    }

    soFar = selector;
    groups = [];
    preFilters = Expr.preFilter;

    while ( soFar ) {

        // Comma and first run
        if ( !matched || (match = rcomma.exec( soFar )) ) {
            if ( match ) {
                // Don't consume trailing commas as valid
                soFar = soFar.slice( match[0].length ) || soFar;
            }
            groups.push( (tokens = []) );
        }

        matched = false;

        // Combinators
        if ( (match = rcombinators.exec( soFar )) ) {
            matched = match.shift();
            tokens.push({
                value: matched,
                // Cast descendant combinators to space
                type: match[0].replace( rtrim, " " )
            });
            soFar = soFar.slice( matched.length );
        }

        // Filters
        for ( type in Expr.filter ) {
            if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
                (match = preFilters[ type ]( match ))) ) {
                matched = match.shift();
                tokens.push({
                    value: matched,
                    type: type,
                    matches: match
                });
                soFar = soFar.slice( matched.length );
            }
        }

        if ( !matched ) {
            break;
        }
    }

    // Return the length of the invalid excess
    // if we're just parsing
    // Otherwise, throw an error or return tokens
    return parseOnly ?
        soFar.length :
        soFar ?
            Sizzle.error( selector ) :
            // Cache the tokens
            tokenCache( selector, groups ).slice( 0 );
};

function toSelector( tokens ) {
    var i = 0,
        len = tokens.length,
        selector = "";
    for ( ; i < len; i++ ) {
        selector += tokens[i].value;
    }
    return selector;
}

function addCombinator( matcher, combinator, base ) {
    var dir = combinator.dir,
        checkNonElements = base && dir === "parentNode",
        doneName = done++;

    return combinator.first ?
        // Check against closest ancestor/preceding element
        function( elem, context, xml ) {
            while ( (elem = elem[ dir ]) ) {
                if ( elem.nodeType === 1 || checkNonElements ) {
                    return matcher( elem, context, xml );
                }
            }
        } :

        // Check against all ancestor/preceding elements
        function( elem, context, xml ) {
            var oldCache, outerCache,
                newCache = [ dirruns, doneName ];

            // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
            if ( xml ) {
                while ( (elem = elem[ dir ]) ) {
                    if ( elem.nodeType === 1 || checkNonElements ) {
                        if ( matcher( elem, context, xml ) ) {
                            return true;
                        }
                    }
                }
            } else {
                while ( (elem = elem[ dir ]) ) {
                    if ( elem.nodeType === 1 || checkNonElements ) {
                        outerCache = elem[ expando ] || (elem[ expando ] = {});
                        if ( (oldCache = outerCache[ dir ]) &&
                            oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {

                            // Assign to newCache so results back-propagate to previous elements
                            return (newCache[ 2 ] = oldCache[ 2 ]);
                        } else {
                            // Reuse newcache so results back-propagate to previous elements
                            outerCache[ dir ] = newCache;

                            // A match means we're done; a fail means we have to keep checking
                            if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
                                return true;
                            }
                        }
                    }
                }
            }
        };
}

function elementMatcher( matchers ) {
    return matchers.length > 1 ?
        function( elem, context, xml ) {
            var i = matchers.length;
            while ( i-- ) {
                if ( !matchers[i]( elem, context, xml ) ) {
                    return false;
                }
            }
            return true;
        } :
        matchers[0];
}

function multipleContexts( selector, contexts, results ) {
    var i = 0,
        len = contexts.length;
    for ( ; i < len; i++ ) {
        Sizzle( selector, contexts[i], results );
    }
    return results;
}

function condense( unmatched, map, filter, context, xml ) {
    var elem,
        newUnmatched = [],
        i = 0,
        len = unmatched.length,
        mapped = map != null;

    for ( ; i < len; i++ ) {
        if ( (elem = unmatched[i]) ) {
            if ( !filter || filter( elem, context, xml ) ) {
                newUnmatched.push( elem );
                if ( mapped ) {
                    map.push( i );
                }
            }
        }
    }

    return newUnmatched;
}

function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
    if ( postFilter && !postFilter[ expando ] ) {
        postFilter = setMatcher( postFilter );
    }
    if ( postFinder && !postFinder[ expando ] ) {
        postFinder = setMatcher( postFinder, postSelector );
    }
    return markFunction(function( seed, results, context, xml ) {
        var temp, i, elem,
            preMap = [],
            postMap = [],
            preexisting = results.length,

            // Get initial elements from seed or context
            elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),

            // Prefilter to get matcher input, preserving a map for seed-results synchronization
            matcherIn = preFilter && ( seed || !selector ) ?
                condense( elems, preMap, preFilter, context, xml ) :
                elems,

            matcherOut = matcher ?
                // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
                postFinder || ( seed ? preFilter : preexisting || postFilter ) ?

                    // ...intermediate processing is necessary
                    [] :

                    // ...otherwise use results directly
                    results :
                matcherIn;

        // Find primary matches
        if ( matcher ) {
            matcher( matcherIn, matcherOut, context, xml );
        }

        // Apply postFilter
        if ( postFilter ) {
            temp = condense( matcherOut, postMap );
            postFilter( temp, [], context, xml );

            // Un-match failing elements by moving them back to matcherIn
            i = temp.length;
            while ( i-- ) {
                if ( (elem = temp[i]) ) {
                    matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
                }
            }
        }

        if ( seed ) {
            if ( postFinder || preFilter ) {
                if ( postFinder ) {
                    // Get the final matcherOut by condensing this intermediate into postFinder contexts
                    temp = [];
                    i = matcherOut.length;
                    while ( i-- ) {
                        if ( (elem = matcherOut[i]) ) {
                            // Restore matcherIn since elem is not yet a final match
                            temp.push( (matcherIn[i] = elem) );
                        }
                    }
                    postFinder( null, (matcherOut = []), temp, xml );
                }

                // Move matched elements from seed to results to keep them synchronized
                i = matcherOut.length;
                while ( i-- ) {
                    if ( (elem = matcherOut[i]) &&
                        (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {

                        seed[temp] = !(results[temp] = elem);
                    }
                }
            }

        // Add elements to results, through postFinder if defined
        } else {
            matcherOut = condense(
                matcherOut === results ?
                    matcherOut.splice( preexisting, matcherOut.length ) :
                    matcherOut
            );
            if ( postFinder ) {
                postFinder( null, results, matcherOut, xml );
            } else {
                push.apply( results, matcherOut );
            }
        }
    });
}

function matcherFromTokens( tokens ) {
    var checkContext, matcher, j,
        len = tokens.length,
        leadingRelative = Expr.relative[ tokens[0].type ],
        implicitRelative = leadingRelative || Expr.relative[" "],
        i = leadingRelative ? 1 : 0,

        // The foundational matcher ensures that elements are reachable from top-level context(s)
        matchContext = addCombinator( function( elem ) {
            return elem === checkContext;
        }, implicitRelative, true ),
        matchAnyContext = addCombinator( function( elem ) {
            return indexOf.call( checkContext, elem ) > -1;
        }, implicitRelative, true ),
        matchers = [ function( elem, context, xml ) {
            return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
                (checkContext = context).nodeType ?
                    matchContext( elem, context, xml ) :
                    matchAnyContext( elem, context, xml ) );
        } ];

    for ( ; i < len; i++ ) {
        if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
            matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
        } else {
            matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );

            // Return special upon seeing a positional matcher
            if ( matcher[ expando ] ) {
                // Find the next relative operator (if any) for proper handling
                j = ++i;
                for ( ; j < len; j++ ) {
                    if ( Expr.relative[ tokens[j].type ] ) {
                        break;
                    }
                }
                return setMatcher(
                    i > 1 && elementMatcher( matchers ),
                    i > 1 && toSelector(
                        // If the preceding token was a descendant combinator, insert an implicit any-element `*`
                        tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
                    ).replace( rtrim, "$1" ),
                    matcher,
                    i < j && matcherFromTokens( tokens.slice( i, j ) ),
                    j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
                    j < len && toSelector( tokens )
                );
            }
            matchers.push( matcher );
        }
    }

    return elementMatcher( matchers );
}

function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
    var bySet = setMatchers.length > 0,
        byElement = elementMatchers.length > 0,
        superMatcher = function( seed, context, xml, results, outermost ) {
            var elem, j, matcher,
                matchedCount = 0,
                i = "0",
                unmatched = seed && [],
                setMatched = [],
                contextBackup = outermostContext,
                // We must always have either seed elements or outermost context
                elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
                // Use integer dirruns iff this is the outermost matcher
                dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
                len = elems.length;

            if ( outermost ) {
                outermostContext = context !== document && context;
            }

            // Add elements passing elementMatchers directly to results
            // Keep `i` a string if there are no elements so `matchedCount` will be "00" below
            // Support: IE<9, Safari
            // Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
            for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
                if ( byElement && elem ) {
                    j = 0;
                    while ( (matcher = elementMatchers[j++]) ) {
                        if ( matcher( elem, context, xml ) ) {
                            results.push( elem );
                            break;
                        }
                    }
                    if ( outermost ) {
                        dirruns = dirrunsUnique;
                    }
                }

                // Track unmatched elements for set filters
                if ( bySet ) {
                    // They will have gone through all possible matchers
                    if ( (elem = !matcher && elem) ) {
                        matchedCount--;
                    }

                    // Lengthen the array for every element, matched or not
                    if ( seed ) {
                        unmatched.push( elem );
                    }
                }
            }

            // Apply set filters to unmatched elements
            matchedCount += i;
            if ( bySet && i !== matchedCount ) {
                j = 0;
                while ( (matcher = setMatchers[j++]) ) {
                    matcher( unmatched, setMatched, context, xml );
                }

                if ( seed ) {
                    // Reintegrate element matches to eliminate the need for sorting
                    if ( matchedCount > 0 ) {
                        while ( i-- ) {
                            if ( !(unmatched[i] || setMatched[i]) ) {
                                setMatched[i] = pop.call( results );
                            }
                        }
                    }

                    // Discard index placeholder values to get only actual matches
                    setMatched = condense( setMatched );
                }

                // Add matches to results
                push.apply( results, setMatched );

                // Seedless set matches succeeding multiple successful matchers stipulate sorting
                if ( outermost && !seed && setMatched.length > 0 &&
                    ( matchedCount + setMatchers.length ) > 1 ) {

                    Sizzle.uniqueSort( results );
                }
            }

            // Override manipulation of globals by nested matchers
            if ( outermost ) {
                dirruns = dirrunsUnique;
                outermostContext = contextBackup;
            }

            return unmatched;
        };

    return bySet ?
        markFunction( superMatcher ) :
        superMatcher;
}

compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
    var i,
        setMatchers = [],
        elementMatchers = [],
        cached = compilerCache[ selector + " " ];

    if ( !cached ) {
        // Generate a function of recursive functions that can be used to check each element
        if ( !match ) {
            match = tokenize( selector );
        }
        i = match.length;
        while ( i-- ) {
            cached = matcherFromTokens( match[i] );
            if ( cached[ expando ] ) {
                setMatchers.push( cached );
            } else {
                elementMatchers.push( cached );
            }
        }

        // Cache the compiled function
        cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );

        // Save selector and tokenization
        cached.selector = selector;
    }
    return cached;
};

/**
 * A low-level selection function that works with Sizzle's compiled
 *  selector functions
 * @param {String|Function} selector A selector or a pre-compiled
 *  selector function built with Sizzle.compile
 * @param {Element} context
 * @param {Array} [results]
 * @param {Array} [seed] A set of elements to match against
 */
select = Sizzle.select = function( selector, context, results, seed ) {
    var i, tokens, token, type, find,
        compiled = typeof selector === "function" && selector,
        match = !seed && tokenize( (selector = compiled.selector || selector) );

    results = results || [];

    // Try to minimize operations if there is no seed and only one group
    if ( match.length === 1 ) {

        // Take a shortcut and set the context if the root selector is an ID
        tokens = match[0] = match[0].slice( 0 );
        if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
                support.getById && context.nodeType === 9 && documentIsHTML &&
                Expr.relative[ tokens[1].type ] ) {

            context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
            if ( !context ) {
                return results;

            // Precompiled matchers will still verify ancestry, so step up a level
            } else if ( compiled ) {
                context = context.parentNode;
            }

            selector = selector.slice( tokens.shift().value.length );
        }

        // Fetch a seed set for right-to-left matching
        i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
        while ( i-- ) {
            token = tokens[i];

            // Abort if we hit a combinator
            if ( Expr.relative[ (type = token.type) ] ) {
                break;
            }
            if ( (find = Expr.find[ type ]) ) {
                // Search, expanding context for leading sibling combinators
                if ( (seed = find(
                    token.matches[0].replace( runescape, funescape ),
                    rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
                )) ) {

                    // If seed is empty or no tokens remain, we can return early
                    tokens.splice( i, 1 );
                    selector = seed.length && toSelector( tokens );
                    if ( !selector ) {
                        push.apply( results, seed );
                        return results;
                    }

                    break;
                }
            }
        }
    }

    // Compile and execute a filtering function if one is not provided
    // Provide `match` to avoid retokenization if we modified the selector above
    ( compiled || compile( selector, match ) )(
        seed,
        context,
        !documentIsHTML,
        results,
        rsibling.test( selector ) && testContext( context.parentNode ) || context
    );
    return results;
};

// One-time assignments

// Sort stability
support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;

// Support: Chrome 14-35+
// Always assume duplicates if they aren't passed to the comparison function
support.detectDuplicates = !!hasDuplicate;

// Initialize against the default document
setDocument();

// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
// Detached nodes confoundingly follow *each other*
support.sortDetached = assert(function( div1 ) {
    // Should return 1, but returns 4 (following)
    return div1.compareDocumentPosition( document.createElement("div") ) & 1;
});

// Support: IE<8
// Prevent attribute/property "interpolation"
// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
if ( !assert(function( div ) {
    div.innerHTML = "<a href='#'></a>";
    return div.firstChild.getAttribute("href") === "#" ;
}) ) {
    addHandle( "type|href|height|width", function( elem, name, isXML ) {
        if ( !isXML ) {
            return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
        }
    });
}

// Support: IE<9
// Use defaultValue in place of getAttribute("value")
if ( !support.attributes || !assert(function( div ) {
    div.innerHTML = "<input/>";
    div.firstChild.setAttribute( "value", "" );
    return div.firstChild.getAttribute( "value" ) === "";
}) ) {
    addHandle( "value", function( elem, name, isXML ) {
        if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
            return elem.defaultValue;
        }
    });
}

// Support: IE<9
// Use getAttributeNode to fetch booleans when getAttribute lies
if ( !assert(function( div ) {
    return div.getAttribute("disabled") == null;
}) ) {
    addHandle( booleans, function( elem, name, isXML ) {
        var val;
        if ( !isXML ) {
            return elem[ name ] === true ? name.toLowerCase() :
                    (val = elem.getAttributeNode( name )) && val.specified ?
                    val.value :
                null;
        }
    });
}

// EXPOSE
return Sizzle;
});

// Included from: js/tinymce/classes/util/Tools.js

/**
 * Tools.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class contains various utlity functions. These are also exposed
 * directly on the tinymce namespace.
 *
 * @class tinymce.util.Tools
 */
define("tinymce/util/Tools", [], function() {
    /**
     * Removes whitespace from the beginning and end of a string.
     *
     * @method trim
     * @param {String} s String to remove whitespace from.
     * @return {String} New string with removed whitespace.
     */
    var whiteSpaceRegExp = /^\s*|\s*$/g;

    function trim(str) {
        return (str === null || str === undefined) ? '' : ("" + str).replace(whiteSpaceRegExp, '');
    }

    /**
     * Returns true/false if the object is an array or not.
     *
     * @method isArray
     * @param {Object} obj Object to check.
     * @return {boolean} true/false state if the object is an array or not.
     */
    var isArray = Array.isArray || function(obj) {
        return Object.prototype.toString.call(obj) === "[object Array]";
    };

    /**
     * Checks if a object is of a specific type for example an array.
     *
     * @method is
     * @param {Object} o Object to check type of.
     * @param {string} t Optional type to check for.
     * @return {Boolean} true/false if the object is of the specified type.
     */
    function is(o, t) {
        if (!t) {
            return o !== undefined;
        }

        if (t == 'array' && isArray(o)) {
            return true;
        }

        return typeof(o) == t;
    }

    /**
     * Converts the specified object into a real JavaScript array.
     *
     * @method toArray
     * @param {Object} obj Object to convert into array.
     * @return {Array} Array object based in input.
     */
    function toArray(obj) {
        var array = obj, i, l;

        if (!isArray(obj)) {
            array = [];
            for (i = 0, l = obj.length; i < l; i++) {
                array[i] = obj[i];
            }
        }

        return array;
    }

    /**
     * Makes a name/object map out of an array with names.
     *
     * @method makeMap
     * @param {Array/String} items Items to make map out of.
     * @param {String} delim Optional delimiter to split string by.
     * @param {Object} map Optional map to add items to.
     * @return {Object} Name/value map of items.
     */
    function makeMap(items, delim, map) {
        var i;

        items = items || [];
        delim = delim || ',';

        if (typeof(items) == "string") {
            items = items.split(delim);
        }

        map = map || {};

        i = items.length;
        while (i--) {
            map[items[i]] = {};
        }

        return map;
    }

    /**
     * Performs an iteration of all items in a collection such as an object or array. This method will execure the
     * callback function for each item in the collection, if the callback returns false the iteration will terminate.
     * The callback has the following format: cb(value, key_or_index).
     *
     * @method each
     * @param {Object} o Collection to iterate.
     * @param {function} cb Callback function to execute for each item.
     * @param {Object} s Optional scope to execute the callback in.
     * @example
     * // Iterate an array
     * tinymce.each([1,2,3], function(v, i) {
     *     console.debug("Value: " + v + ", Index: " + i);
     * });
     *
     * // Iterate an object
     * tinymce.each({a: 1, b: 2, c: 3], function(v, k) {
     *     console.debug("Value: " + v + ", Key: " + k);
     * });
     */
    function each(o, cb, s) {
        var n, l;

        if (!o) {
            return 0;
        }

        s = s || o;

        if (o.length !== undefined) {
            // Indexed arrays, needed for Safari
            for (n = 0, l = o.length; n < l; n++) {
                if (cb.call(s, o[n], n, o) === false) {
                    return 0;
                }
            }
        } else {
            // Hashtables
            for (n in o) {
                if (o.hasOwnProperty(n)) {
                    if (cb.call(s, o[n], n, o) === false) {
                        return 0;
                    }
                }
            }
        }

        return 1;
    }

    /**
     * Creates a new array by the return value of each iteration function call. This enables you to convert
     * one array list into another.
     *
     * @method map
     * @param {Array} a Array of items to iterate.
     * @param {function} f Function to call for each item. It's return value will be the new value.
     * @return {Array} Array with new values based on function return values.
     */
    function map(a, f) {
        var o = [];

        each(a, function(v) {
            o.push(f(v));
        });

        return o;
    }

    /**
     * Filters out items from the input array by calling the specified function for each item.
     * If the function returns false the item will be excluded if it returns true it will be included.
     *
     * @method grep
     * @param {Array} a Array of items to loop though.
     * @param {function} f Function to call for each item. Include/exclude depends on it's return value.
     * @return {Array} New array with values imported and filtered based in input.
     * @example
     * // Filter out some items, this will return an array with 4 and 5
     * var items = tinymce.grep([1,2,3,4,5], function(v) {return v > 3;});
     */
    function grep(a, f) {
        var o = [];

        each(a, function(v) {
            if (!f || f(v)) {
                o.push(v);
            }
        });

        return o;
    }

    /**
     * Creates a class, subclass or static singleton.
     * More details on this method can be found in the Wiki.
     *
     * @method create
     * @param {String} s Class name, inheritage and prefix.
     * @param {Object} p Collection of methods to add to the class.
     * @param {Object} root Optional root object defaults to the global window object.
     * @example
     * // Creates a basic class
     * tinymce.create('tinymce.somepackage.SomeClass', {
     *     SomeClass: function() {
     *         // Class constructor
     *     },
     *
     *     method: function() {
     *         // Some method
     *     }
     * });
     *
     * // Creates a basic subclass class
     * tinymce.create('tinymce.somepackage.SomeSubClass:tinymce.somepackage.SomeClass', {
     *     SomeSubClass: function() {
     *         // Class constructor
     *         this.parent(); // Call parent constructor
     *     },
     *
     *     method: function() {
     *         // Some method
     *         this.parent(); // Call parent method
     *     },
     *
     *     'static': {
     *         staticMethod: function() {
     *             // Static method
     *         }
     *     }
     * });
     *
     * // Creates a singleton/static class
     * tinymce.create('static tinymce.somepackage.SomeSingletonClass', {
     *     method: function() {
     *         // Some method
     *     }
     * });
     */
    function create(s, p, root) {
        var self = this, sp, ns, cn, scn, c, de = 0;

        // Parse : <prefix> <class>:<super class>
        s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
        cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name

        // Create namespace for new class
        ns = self.createNS(s[3].replace(/\.\w+$/, ''), root);

        // Class already exists
        if (ns[cn]) {
            return;
        }

        // Make pure static class
        if (s[2] == 'static') {
            ns[cn] = p;

            if (this.onCreate) {
                this.onCreate(s[2], s[3], ns[cn]);
            }

            return;
        }

        // Create default constructor
        if (!p[cn]) {
            p[cn] = function() {};
            de = 1;
        }

        // Add constructor and methods
        ns[cn] = p[cn];
        self.extend(ns[cn].prototype, p);

        // Extend
        if (s[5]) {
            sp = self.resolve(s[5]).prototype;
            scn = s[5].match(/\.(\w+)$/i)[1]; // Class name

            // Extend constructor
            c = ns[cn];
            if (de) {
                // Add passthrough constructor
                ns[cn] = function() {
                    return sp[scn].apply(this, arguments);
                };
            } else {
                // Add inherit constructor
                ns[cn] = function() {
                    this.parent = sp[scn];
                    return c.apply(this, arguments);
                };
            }
            ns[cn].prototype[cn] = ns[cn];

            // Add super methods
            self.each(sp, function(f, n) {
                ns[cn].prototype[n] = sp[n];
            });

            // Add overridden methods
            self.each(p, function(f, n) {
                // Extend methods if needed
                if (sp[n]) {
                    ns[cn].prototype[n] = function() {
                        this.parent = sp[n];
                        return f.apply(this, arguments);
                    };
                } else {
                    if (n != cn) {
                        ns[cn].prototype[n] = f;
                    }
                }
            });
        }

        // Add static methods
        /*jshint sub:true*/
        self.each(p['static'], function(f, n) {
            ns[cn][n] = f;
        });
    }

    /**
     * Returns the index of a value in an array, this method will return -1 if the item wasn't found.
     *
     * @method inArray
     * @param {Array} a Array/Object to search for value in.
     * @param {Object} v Value to check for inside the array.
     * @return {Number/String} Index of item inside the array inside an object. Or -1 if it wasn't found.
     * @example
     * // Get index of value in array this will alert 1 since 2 is at that index
     * alert(tinymce.inArray([1,2,3], 2));
     */
    function inArray(a, v) {
        var i, l;

        if (a) {
            for (i = 0, l = a.length; i < l; i++) {
                if (a[i] === v) {
                    return i;
                }
            }
        }

        return -1;
    }

    function extend(obj, ext) {
        var i, l, name, args = arguments, value;

        for (i = 1, l = args.length; i < l; i++) {
            ext = args[i];
            for (name in ext) {
                if (ext.hasOwnProperty(name)) {
                    value = ext[name];

                    if (value !== undefined) {
                        obj[name] = value;
                    }
                }
            }
        }

        return obj;
    }

    /**
     * Executed the specified function for each item in a object tree.
     *
     * @method walk
     * @param {Object} o Object tree to walk though.
     * @param {function} f Function to call for each item.
     * @param {String} n Optional name of collection inside the objects to walk for example childNodes.
     * @param {String} s Optional scope to execute the function in.
     */
    function walk(o, f, n, s) {
        s = s || this;

        if (o) {
            if (n) {
                o = o[n];
            }

            each(o, function(o, i) {
                if (f.call(s, o, i, n) === false) {
                    return false;
                }

                walk(o, f, n, s);
            });
        }
    }

    /**
     * Creates a namespace on a specific object.
     *
     * @method createNS
     * @param {String} n Namespace to create for example a.b.c.d.
     * @param {Object} o Optional object to add namespace to, defaults to window.
     * @return {Object} New namespace object the last item in path.
     * @example
     * // Create some namespace
     * tinymce.createNS('tinymce.somepackage.subpackage');
     *
     * // Add a singleton
     * var tinymce.somepackage.subpackage.SomeSingleton = {
     *     method: function() {
     *         // Some method
     *     }
     * };
     */
    function createNS(n, o) {
        var i, v;

        o = o || window;

        n = n.split('.');
        for (i = 0; i < n.length; i++) {
            v = n[i];

            if (!o[v]) {
                o[v] = {};
            }

            o = o[v];
        }

        return o;
    }

    /**
     * Resolves a string and returns the object from a specific structure.
     *
     * @method resolve
     * @param {String} n Path to resolve for example a.b.c.d.
     * @param {Object} o Optional object to search though, defaults to window.
     * @return {Object} Last object in path or null if it couldn't be resolved.
     * @example
     * // Resolve a path into an object reference
     * var obj = tinymce.resolve('a.b.c.d');
     */
    function resolve(n, o) {
        var i, l;

        o = o || window;

        n = n.split('.');
        for (i = 0, l = n.length; i < l; i++) {
            o = o[n[i]];

            if (!o) {
                break;
            }
        }

        return o;
    }

    /**
     * Splits a string but removes the whitespace before and after each value.
     *
     * @method explode
     * @param {string} s String to split.
     * @param {string} d Delimiter to split by.
     * @example
     * // Split a string into an array with a,b,c
     * var arr = tinymce.explode('a, b,   c');
     */
    function explode(s, d) {
        if (!s || is(s, 'array')) {
            return s;
        }

        return map(s.split(d || ','), trim);
    }

    return {
        trim: trim,
        isArray: isArray,
        is: is,
        toArray: toArray,
        makeMap: makeMap,
        each: each,
        map: map,
        grep: grep,
        inArray: inArray,
        extend: extend,
        create: create,
        walk: walk,
        createNS: createNS,
        resolve: resolve,
        explode: explode
    };
});

// Included from: js/tinymce/classes/Env.js

/**
 * Env.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class contains various environment constants like browser versions etc.
 * Normally you don't want to sniff specific browser versions but sometimes you have
 * to when it's impossible to feature detect. So use this with care.
 *
 * @class tinymce.Env
 * @static
 */
define("tinymce/Env", [], function() {
    var nav = navigator, userAgent = nav.userAgent;
    var opera, webkit, ie, ie11, gecko, mac, iDevice;

    opera = window.opera && window.opera.buildNumber;
    webkit = /WebKit/.test(userAgent);
    ie = !webkit && !opera && (/MSIE/gi).test(userAgent) && (/Explorer/gi).test(nav.appName);
    ie = ie && /MSIE (\w+)\./.exec(userAgent)[1];
    ie11 = userAgent.indexOf('Trident/') != -1 && (userAgent.indexOf('rv:') != -1 || nav.appName.indexOf('Netscape') != -1) ? 11 : false;
    ie = ie || ie11;
    gecko = !webkit && !ie11 && /Gecko/.test(userAgent);
    mac = userAgent.indexOf('Mac') != -1;
    iDevice = /(iPad|iPhone)/.test(userAgent);

    // Is a iPad/iPhone and not on iOS5 sniff the WebKit version since older iOS WebKit versions
    // says it has contentEditable support but there is no visible caret.
    var contentEditable = !iDevice || userAgent.match(/AppleWebKit\/(\d*)/)[1] >= 534;

    return {
        /**
         * Constant that is true if the browser is Opera.
         *
         * @property opera
         * @type Boolean
         * @final
         */
        opera: opera,

        /**
         * Constant that is true if the browser is WebKit (Safari/Chrome).
         *
         * @property webKit
         * @type Boolean
         * @final
         */
        webkit: webkit,

        /**
         * Constant that is more than zero if the browser is IE.
         *
         * @property ie
         * @type Boolean
         * @final
         */
        ie: ie,

        /**
         * Constant that is true if the browser is Gecko.
         *
         * @property gecko
         * @type Boolean
         * @final
         */
        gecko: gecko,

        /**
         * Constant that is true if the os is Mac OS.
         *
         * @property mac
         * @type Boolean
         * @final
         */
        mac: mac,

        /**
         * Constant that is true if the os is iOS.
         *
         * @property iOS
         * @type Boolean
         * @final
         */
        iOS: iDevice,

        /**
         * Constant that is true if the browser supports editing.
         *
         * @property contentEditable
         * @type Boolean
         * @final
         */
        contentEditable: contentEditable,

        /**
         * Transparent image data url.
         *
         * @property transparentSrc
         * @type Boolean
         * @final
         */
        transparentSrc: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",

        /**
         * Returns true/false if the browser can or can't place the caret after a inline block like an image.
         *
         * @property noCaretAfter
         * @type Boolean
         * @final
         */
        caretAfter: ie != 8,

        /**
         * Constant that is true if the browser supports native DOM Ranges. IE 9+.
         *
         * @property range
         * @type Boolean
         */
        range: window.getSelection && "Range" in window,

        /**
         * Returns the IE document mode for non IE browsers this will fake IE 10.
         *
         * @property documentMode
         * @type Number
         */
        documentMode: ie ? (document.documentMode || 7) : 10
    };
});

// Included from: js/tinymce/classes/dom/DomQuery.js

/**
 * DomQuery.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class mimics most of the jQuery API:
 *
 * This is whats currently implemented:
 * - Utility functions
 * - DOM traversial
 * - DOM manipulation
 * - Event binding
 *
 * This is not currently implemented:
 * - Dimension
 * - Ajax
 * - Animation
 * - Advanced chaining
 *
 * @example
 * var $ = tinymce.dom.DomQuery;
 * $('p').attr('attr', 'value').addClass('class');
 *
 * @class tinymce.dom.DomQuery
 */
define("tinymce/dom/DomQuery", [
    "tinymce/dom/EventUtils",
    "tinymce/dom/Sizzle",
    "tinymce/util/Tools",
    "tinymce/Env"
], function(EventUtils, Sizzle, Tools, Env) {
    var doc = document, push = Array.prototype.push, slice = Array.prototype.slice;
    var rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/;
    var Event = EventUtils.Event, undef;

    function isDefined(obj) {
        return typeof obj !== 'undefined';
    }

    function isString(obj) {
        return typeof obj === 'string';
    }

    function createFragment(html, fragDoc) {
        var frag, node, container;

        fragDoc = fragDoc || doc;
        container = fragDoc.createElement('div');
        frag = fragDoc.createDocumentFragment();
        container.innerHTML = html;

        while ((node = container.firstChild)) {
            frag.appendChild(node);
        }

        return frag;
    }

    function domManipulate(targetNodes, sourceItem, callback, reverse) {
        var i;

        if (isString(sourceItem)) {
            sourceItem = createFragment(sourceItem, getElementDocument(targetNodes[0]));
        } else if (sourceItem.length && !sourceItem.nodeType) {
            sourceItem = DomQuery.makeArray(sourceItem);

            if (reverse) {
                for (i = sourceItem.length - 1; i >= 0; i--) {
                    domManipulate(targetNodes, sourceItem[i], callback, reverse);
                }
            } else {
                for (i = 0; i < sourceItem.length; i++) {
                    domManipulate(targetNodes, sourceItem[i], callback, reverse);
                }
            }

            return targetNodes;
        }

        if (sourceItem.nodeType) {
            i = targetNodes.length;
            while (i--) {
                callback.call(targetNodes[i], sourceItem);
            }
        }

        return targetNodes;
    }

    function hasClass(node, className) {
        return node && className && (' ' + node.className + ' ').indexOf(' ' + className + ' ') !== -1;
    }

    function wrap(elements, wrapper, all) {
        var lastParent, newWrapper;

        wrapper = DomQuery(wrapper)[0];

        elements.each(function() {
            var self = this;

            if (!all || lastParent != self.parentNode) {
                lastParent = self.parentNode;
                newWrapper = wrapper.cloneNode(false);
                self.parentNode.insertBefore(newWrapper, self);
                newWrapper.appendChild(self);
            } else {
                newWrapper.appendChild(self);
            }
        });

        return elements;
    }

    var numericCssMap = Tools.makeMap('fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom', ' ');
    var booleanMap = Tools.makeMap('checked compact declare defer disabled ismap multiple nohref noshade nowrap readonly selected', ' ');
    var propFix = {
        'for': 'htmlFor',
        'class': 'className',
        'readonly': 'readOnly'
    };
    var cssFix = {
        float: 'cssFloat'
    };

    var attrHooks = {}, cssHooks = {};

    function DomQuery(selector, context) {
        /*eslint new-cap:0 */
        return new DomQuery.fn.init(selector, context);
    }

    function inArray(item, array) {
        var i;

        if (array.indexOf) {
            return array.indexOf(item);
        }

        i = array.length;
        while (i--) {
            if (array[i] === item) {
                return i;
            }
        }

        return -1;
    }

    var whiteSpaceRegExp = /^\s*|\s*$/g;

    function trim(str) {
        return (str === null || str === undef) ? '' : ("" + str).replace(whiteSpaceRegExp, '');
    }

    function each(obj, callback) {
        var length, key, i, undef, value;

        if (obj) {
            length = obj.length;

            if (length === undef) {
                // Loop object items
                for (key in obj) {
                    if (obj.hasOwnProperty(key)) {
                        value = obj[key];
                        if (callback.call(value, key, value) === false) {
                            break;
                        }
                    }
                }
            } else {
                // Loop array items
                for (i = 0; i < length; i++) {
                    value = obj[i];
                    if (callback.call(value, i, value) === false) {
                        break;
                    }
                }
            }
        }

        return obj;
    }

    function grep(array, callback) {
        var out = [];

        each(array, function(i, item) {
            if (callback(item, i)) {
                out.push(item);
            }
        });

        return out;
    }

    function getElementDocument(element) {
        if (!element) {
            return doc;
        }

        if (element.nodeType == 9) {
            return element;
        }

        return element.ownerDocument;
    }

    DomQuery.fn = DomQuery.prototype = {
        constructor: DomQuery,

        /**
         * Selector for the current set.
         *
         * @property selector
         * @type String
         */
        selector: "",

        /**
         * Context used to create the set.
         *
         * @property context
         * @type Element
         */
        context: null,

        /**
         * Number of items in the current set.
         *
         * @property length
         * @type Number
         */
        length: 0,

        /**
         * Constructs a new DomQuery instance with the specified selector or context.
         *
         * @constructor
         * @method init
         * @param {String/Array/DomQuery} selector Optional CSS selector/Array or array like object or HTML string.
         * @param {Document/Element} context Optional context to search in.
         */
        init: function(selector, context) {
            var self = this, match, node;

            if (!selector) {
                return self;
            }

            if (selector.nodeType) {
                self.context = self[0] = selector;
                self.length = 1;

                return self;
            }

            if (context && context.nodeType) {
                self.context = context;
            } else {
                if (context) {
                    return DomQuery(selector).attr(context);
                } else {
                    self.context = context = document;
                }
            }

            if (isString(selector)) {
                self.selector = selector;

                if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) {
                    match = [null, selector, null];
                } else {
                    match = rquickExpr.exec(selector);
                }

                if (match) {
                    if (match[1]) {
                        node = createFragment(selector, getElementDocument(context)).firstChild;

                        while (node) {
                            push.call(self, node);
                            node = node.nextSibling;
                        }
                    } else {
                        node = getElementDocument(context).getElementById(match[2]);

                        if (!node) {
                            return self;
                        }

                        if (node.id !== match[2]) {
                            return self.find(selector);
                        }

                        self.length = 1;
                        self[0] = node;
                    }
                } else {
                    return DomQuery(context).find(selector);
                }
            } else {
                this.add(selector, false);
            }

            return self;
        },

        /**
         * Converts the current set to an array.
         *
         * @method toArray
         * @param {Array} Array of all nodes in set.
         */
        toArray: function() {
            return Tools.toArray(this);
        },

        /**
         * Adds new nodes to the set.
         *
         * @method add
         * @param {Array/tinymce.dom.DomQuery} items Array of all nodes to add to set.
         * @return {tinymce.dom.DomQuery} New instance with nodes added.
         */
        add: function(items, sort) {
            var self = this, nodes, i;

            if (isString(items)) {
                return self.add(DomQuery(items));
            }

            if (items.nodeType) {
                return self.add([items]);
            }

            if (sort !== false) {
                nodes = DomQuery.unique(self.toArray().concat(DomQuery.makeArray(items)));
                self.length = nodes.length;
                for (i = 0; i < nodes.length; i++) {
                    self[i] = nodes[i];
                }
            } else {
                push.apply(self, DomQuery.makeArray(items));
            }

            return self;
        },

        /**
         * Sets/gets attributes on the elements in the current set.
         *
         * @method attr
         * @param {String/Object} name Name of attribute to get or an object with attributes to set.
         * @param {String} value Optional value to set.
         * @return {tinymce.dom.DomQuery/String} Current set or the specified attribute when only the name is specified.
         */
        attr: function(name, value) {
            var self = this, hook;

            if (typeof name === "object") {
                each(name, function(name, value) {
                    self.attr(name, value);
                });
            } else if (isDefined(value)) {
                this.each(function() {
                    var hook;

                    if (this.nodeType === 1) {
                        hook = attrHooks[name];
                        if (hook && hook.set) {
                            hook.set(this, value);
                            return;
                        }

                        if (value === null) {
                            this.removeAttribute(name, 2);
                        } else {
                            this.setAttribute(name, value, 2);
                        }
                    }
                });
            } else {
                if (self[0] && self[0].nodeType === 1) {
                    hook = attrHooks[name];
                    if (hook && hook.get) {
                        return hook.get(self[0], name);
                    }

                    if (booleanMap[name]) {
                        return self.prop(name) ? name : undef;
                    }

                    value = self[0].getAttribute(name, 2);

                    if (value === null) {
                        value = undef;
                    }
                }

                return value;
            }

            return self;
        },

        /**
         * Removes attributse on the elements in the current set.
         *
         * @method removeAttr
         * @param {String/Object} name Name of attribute to remove.
         * @return {tinymce.dom.DomQuery/String} Current set.
         */
        removeAttr: function(name) {
            return this.attr(name, null);
        },

        /**
         * Sets/gets properties on the elements in the current set.
         *
         * @method attr
         * @param {String/Object} name Name of property to get or an object with properties to set.
         * @param {String} value Optional value to set.
         * @return {tinymce.dom.DomQuery/String} Current set or the specified property when only the name is specified.
         */
        prop: function(name, value) {
            var self = this;

            name = propFix[name] || name;

            if (typeof name === "object") {
                each(name, function(name, value) {
                    self.prop(name, value);
                });
            } else if (isDefined(value)) {
                this.each(function() {
                    if (this.nodeType == 1) {
                        this[name] = value;
                    }
                });
            } else {
                if (self[0] && self[0].nodeType && name in self[0]) {
                    return self[0][name];
                }

                return value;
            }

            return self;
        },

        /**
         * Sets/gets styles on the elements in the current set.
         *
         * @method css
         * @param {String/Object} name Name of style to get or an object with styles to set.
         * @param {String} value Optional value to set.
         * @return {tinymce.dom.DomQuery/String} Current set or the specified style when only the name is specified.
         */
        css: function(name, value) {
            var self = this, elm, hook;

            function camel(name) {
                return name.replace(/-(\D)/g, function(a, b) {
                    return b.toUpperCase();
                });
            }

            function dashed(name) {
                return name.replace(/[A-Z]/g, function(a) {
                    return '-' + a;
                });
            }

            if (typeof name === "object") {
                each(name, function(name, value) {
                    self.css(name, value);
                });
            } else {
                if (isDefined(value)) {
                    name = camel(name);

                    // Default px suffix on these
                    if (typeof(value) === 'number' && !numericCssMap[name]) {
                        value += 'px';
                    }

                    self.each(function() {
                        var style = this.style;

                        hook = cssHooks[name];
                        if (hook && hook.set) {
                            hook.set(this, value);
                            return;
                        }

                        try {
                            this.style[cssFix[name] || name] = value;
                        } catch (ex) {
                            // Ignore
                        }

                        if (value === null || value === '') {
                            if (style.removeProperty) {
                                style.removeProperty(dashed(name));
                            } else {
                                style.removeAttribute(name);
                            }
                        }
                    });
                } else {
                    elm = self[0];

                    hook = cssHooks[name];
                    if (hook && hook.get) {
                        return hook.get(elm);
                    }

                    if (elm.ownerDocument.defaultView) {
                        try {
                            return elm.ownerDocument.defaultView.getComputedStyle(elm, null).getPropertyValue(dashed(name));
                        } catch (ex) {
                            return undef;
                        }
                    } else if (elm.currentStyle) {
                        return elm.currentStyle[camel(name)];
                    }
                }
            }

            return self;
        },

        /**
         * Removes all nodes in set from the document.
         *
         * @method remove
         * @return {tinymce.dom.DomQuery} Current set with the removed nodes.
         */
        remove: function() {
            var self = this, node, i = this.length;

            while (i--) {
                node = self[i];
                Event.clean(node);

                if (node.parentNode) {
                    node.parentNode.removeChild(node);
                }
            }

            return this;
        },

        /**
         * Empties all elements in set.
         *
         * @method empty
         * @return {tinymce.dom.DomQuery} Current set with the empty nodes.
         */
        empty: function() {
            var self = this, node, i = this.length;

            while (i--) {
                node = self[i];
                while (node.firstChild) {
                    node.removeChild(node.firstChild);
                }
            }

            return this;
        },

        /**
         * Sets or gets the HTML of the current set or first set node.
         *
         * @method html
         * @param {String} value Optional innerHTML value to set on each element.
         * @return {tinymce.dom.DomQuery/String} Current set or the innerHTML of the first element.
         */
        html: function(value) {
            var self = this, i;

            if (isDefined(value)) {
                i = self.length;

                try {
                    while (i--) {
                        self[i].innerHTML = value;
                    }
                } catch (ex) {
                    // Workaround for "Unkown runtime error" when DIV is added to P on IE
                    DomQuery(self[i]).empty().append(value);
                }

                return self;
            }

            return self[0] ? self[0].innerHTML : '';
        },

        /**
         * Sets or gets the text of the current set or first set node.
         *
         * @method text
         * @param {String} value Optional innerText value to set on each element.
         * @return {tinymce.dom.DomQuery/String} Current set or the innerText of the first element.
         */
        text: function(value) {
            var self = this, i;

            if (isDefined(value)) {
                i = self.length;
                while (i--) {
                    if ("innerText" in self[i]) {
                        self[i].innerText = value;
                    } else {
                        self[0].textContent = value;
                    }
                }

                return self;
            }

            return self[0] ? (self[0].innerText || self[0].textContent) : '';
        },

        /**
         * Appends the specified node/html or node set to the current set nodes.
         *
         * @method append
         * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to append to each element in set.
         * @return {tinymce.dom.DomQuery} Current set.
         */
        append: function() {
            return domManipulate(this, arguments, function(node) {
                if (this.nodeType === 1) {
                    this.appendChild(node);
                }
            });
        },

        /**
         * Prepends the specified node/html or node set to the current set nodes.
         *
         * @method prepend
         * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to prepend to each element in set.
         * @return {tinymce.dom.DomQuery} Current set.
         */
        prepend: function() {
            return domManipulate(this, arguments, function(node) {
                if (this.nodeType === 1) {
                    this.insertBefore(node, this.firstChild);
                }
            }, true);
        },

        /**
         * Adds the specified elements before current set nodes.
         *
         * @method before
         * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to add before to each element in set.
         * @return {tinymce.dom.DomQuery} Current set.
         */
        before: function() {
            var self = this;

            if (self[0] && self[0].parentNode) {
                return domManipulate(self, arguments, function(node) {
                    this.parentNode.insertBefore(node, this);
                });
            }

            return self;
        },

        /**
         * Adds the specified elements after current set nodes.
         *
         * @method after
         * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to add after to each element in set.
         * @return {tinymce.dom.DomQuery} Current set.
         */
        after: function() {
            var self = this;

            if (self[0] && self[0].parentNode) {
                return domManipulate(self, arguments, function(node) {
                    this.parentNode.insertBefore(node, this.nextSibling);
                }, true);
            }

            return self;
        },

        /**
         * Appends the specified set nodes to the specified selector/instance.
         *
         * @method appendTo
         * @param {String/Element/Array/tinymce.dom.DomQuery} val Item to append the current set to.
         * @return {tinymce.dom.DomQuery} Current set with the appended nodes.
         */
        appendTo: function(val) {
            DomQuery(val).append(this);

            return this;
        },

        /**
         * Prepends the specified set nodes to the specified selector/instance.
         *
         * @method prependTo
         * @param {String/Element/Array/tinymce.dom.DomQuery} val Item to prepend the current set to.
         * @return {tinymce.dom.DomQuery} Current set with the prepended nodes.
         */
        prependTo: function(val) {
            DomQuery(val).prepend(this);

            return this;
        },

        /**
         * Replaces the nodes in set with the specified content.
         *
         * @method replaceWith
         * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to replace nodes with.
         * @return {tinymce.dom.DomQuery} Set with replaced nodes.
         */
        replaceWith: function(content) {
            return this.before(content).remove();
        },

        /**
         * Wraps all elements in set with the specified wrapper.
         *
         * @method wrap
         * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to wrap nodes with.
         * @return {tinymce.dom.DomQuery} Set with wrapped nodes.
         */
        wrap: function(wrapper) {
            return wrap(this, wrapper);
        },

        /**
         * Wraps all nodes in set with the specified wrapper. If the nodes are siblings all of them
         * will be wrapped in the same wrapper.
         *
         * @method wrapAll
         * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to wrap nodes with.
         * @return {tinymce.dom.DomQuery} Set with wrapped nodes.
         */
        wrapAll: function(wrapper) {
            return wrap(this, wrapper, true);
        },

        /**
         * Wraps all elements inner contents in set with the specified wrapper.
         *
         * @method wrapInner
         * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to wrap nodes with.
         * @return {tinymce.dom.DomQuery} Set with wrapped nodes.
         */
        wrapInner: function(wrapper) {
            this.each(function() {
                DomQuery(this).contents().wrapAll(wrapper);
            });

            return this;
        },

        /**
         * Unwraps all elements by removing the parent element of each item in set.
         *
         * @method unwrap
         * @return {tinymce.dom.DomQuery} Set with unwrapped nodes.
         */
        unwrap: function() {
            return this.parent().each(function() {
                DomQuery(this).replaceWith(this.childNodes);
            });
        },

        /**
         * Clones all nodes in set.
         *
         * @method clone
         * @return {tinymce.dom.DomQuery} Set with cloned nodes.
         */
        clone: function() {
            var result = [];

            this.each(function() {
                result.push(this.cloneNode(true));
            });

            return DomQuery(result);
        },

        /**
         * Adds the specified class name to the current set elements.
         *
         * @method addClass
         * @param {String} className Class name to add.
         * @return {tinymce.dom.DomQuery} Current set.
         */
        addClass: function(className) {
            return this.toggleClass(className, true);
        },

        /**
         * Removes the specified class name to the current set elements.
         *
         * @method removeClass
         * @param {String} className Class name to remove.
         * @return {tinymce.dom.DomQuery} Current set.
         */
        removeClass: function(className) {
            return this.toggleClass(className, false);
        },

        /**
         * Toggles the specified class name on the current set elements.
         *
         * @method toggleClass
         * @param {String} className Class name to add/remove.
         * @param {Boolean} state Optional state to toggle on/off.
         * @return {tinymce.dom.DomQuery} Current set.
         */
        toggleClass: function(className, state) {
            var self = this;

            // Functions are not supported
            if (typeof className != 'string') {
                return self;
            }

            if (className.indexOf(' ') !== -1) {
                each(className.split(' '), function() {
                    self.toggleClass(this, state);
                });
            } else {
                self.each(function(index, node) {
                    var existingClassName, classState;

                    classState = hasClass(node, className);
                    if (classState !== state) {
                        existingClassName = node.className;

                        if (classState) {
                            node.className = trim((" " + existingClassName + " ").replace(' ' + className + ' ', ' '));
                        } else {
                            node.className += existingClassName ? ' ' + className : className;
                        }
                    }
                });
            }

            return self;
        },

        /**
         * Returns true/false if the first item in set has the specified class.
         *
         * @method hasClass
         * @param {String} className Class name to check for.
         * @return {Boolean} True/false if the set has the specified class.
         */
        hasClass: function(className) {
            return hasClass(this[0], className);
        },

        /**
         * Executes the callback function for each item DomQuery collection. If you return false in the
         * callback it will break the loop.
         *
         * @method each
         * @param {function} callback Callback function to execute for each item.
         * @return {tinymce.dom.DomQuery} Current set.
         */
        each: function(callback) {
            return each(this, callback);
        },

        /**
         * Binds an event with callback function to the elements in set.
         *
         * @method on
         * @param {String} name Name of the event to bind.
         * @param {function} callback Callback function to execute when the event occurs.
         * @return {tinymce.dom.DomQuery} Current set.
         */
        on: function(name, callback) {
            return this.each(function() {
                Event.bind(this, name, callback);
            });
        },

        /**
         * Unbinds an event with callback function to the elements in set.
         *
         * @method off
         * @param {String} name Optional name of the event to bind.
         * @param {function} callback Optional callback function to execute when the event occurs.
         * @return {tinymce.dom.DomQuery} Current set.
         */
        off: function(name, callback) {
            return this.each(function() {
                Event.unbind(this, name, callback);
            });
        },

        /**
         * Triggers the specified event by name or event object.
         *
         * @method trigger
         * @param {String/Object} name Name of the event to trigger or event object.
         * @return {tinymce.dom.DomQuery} Current set.
         */
        trigger: function(name) {
            return this.each(function() {
                if (typeof name == 'object') {
                    Event.fire(this, name.type, name);
                } else {
                    Event.fire(this, name);
                }
            });
        },

        /**
         * Shows all elements in set.
         *
         * @method show
         * @return {tinymce.dom.DomQuery} Current set.
         */
        show: function() {
            return this.css('display', '');
        },

        /**
         * Hides all elements in set.
         *
         * @method hide
         * @return {tinymce.dom.DomQuery} Current set.
         */
        hide: function() {
            return this.css('display', 'none');
        },

        /**
         * Slices the current set.
         *
         * @method slice
         * @param {Number} start Start index to slice at.
         * @param {Number} end Optional ened index to end slice at.
         * @return {tinymce.dom.DomQuery} Sliced set.
         */
        slice: function() {
            return new DomQuery(slice.apply(this, arguments));
        },

        /**
         * Makes the set equal to the specified index.
         *
         * @method eq
         * @param {Number} index Index to set it equal to.
         * @return {tinymce.dom.DomQuery} Single item set.
         */
        eq: function(index) {
            return index === -1 ? this.slice(index) : this.slice(index, +index + 1);
        },

        /**
         * Makes the set equal to first element in set.
         *
         * @method first
         * @return {tinymce.dom.DomQuery} Single item set.
         */
        first: function() {
            return this.eq(0);
        },

        /**
         * Makes the set equal to last element in set.
         *
         * @method last
         * @return {tinymce.dom.DomQuery} Single item set.
         */
        last: function() {
            return this.eq(-1);
        },

        /**
         * Finds elements by the specified selector for each element in set.
         *
         * @method find
         * @param {String} selector Selector to find elements by.
         * @return {tinymce.dom.DomQuery} Set with matches elements.
         */
        find: function(selector) {
            var i, l, ret = [];

            for (i = 0, l = this.length; i < l; i++) {
                DomQuery.find(selector, this[i], ret);
            }

            return DomQuery(ret);
        },

        /**
         * Filters the current set with the specified selector.
         *
         * @method filter
         * @param {String/function} selector Selector to filter elements by.
         * @return {tinymce.dom.DomQuery} Set with filtered elements.
         */
        filter: function(selector) {
            if (typeof selector == 'function') {
                return DomQuery(grep(this.toArray(), function(item, i) {
                    return selector(i, item);
                }));
            }

            return DomQuery(DomQuery.filter(selector, this.toArray()));
        },

        /**
         * Gets the current node or any partent matching the specified selector.
         *
         * @method closest
         * @param {String/Element/tinymce.dom.DomQuery} selector Selector or element to find.
         * @return {tinymce.dom.DomQuery} Set with closest elements.
         */
        closest: function(selector) {
            var result = [];

            if (selector instanceof DomQuery) {
                selector = selector[0];
            }

            this.each(function(i, node) {
                while (node) {
                    if (typeof selector == 'string' && DomQuery(node).is(selector)) {
                        result.push(node);
                        break;
                    } else if (node == selector) {
                        result.push(node);
                        break;
                    }

                    node = node.parentNode;
                }
            });

            return DomQuery(result);
        },

        /**
         * Returns the offset of the first element in set or sets the top/left css properties of all elements in set.
         *
         * @method offset
         * @param {Object} offset Optional offset object to set on each item.
         * @return {Object/tinymce.dom.DomQuery} Returns the first element offset or the current set if you specified an offset.
         */
        offset: function(offset) {
            var elm, doc, docElm;
            var x = 0, y = 0, pos;

            if (!offset) {
                elm = this[0];

                if (elm) {
                    doc = elm.ownerDocument;
                    docElm = doc.documentElement;

                    if (elm.getBoundingClientRect) {
                        pos = elm.getBoundingClientRect();
                        x = pos.left + (docElm.scrollLeft || doc.body.scrollLeft) - docElm.clientLeft;
                        y = pos.top + (docElm.scrollTop || doc.body.scrollTop) - docElm.clientTop;
                    }
                }

                return {
                    left: x,
                    top: y
                };
            }

            return this.css(offset);
        },

        push: push,
        sort: [].sort,
        splice: [].splice
    };

    // Static members
    Tools.extend(DomQuery, {
        /**
         * Extends the specified object with one or more objects.
         *
         * @static
         * @method extend
         * @param {Object} target Target object to extend with new items.
         * @param {Object..} object Object to extend the target with.
         * @return {Object} Extended input object.
         */
        extend: Tools.extend,

        /**
         * Creates an array out of an array like object.
         *
         * @static
         * @method makeArray
         * @param {Object} object Object to convert to array.
         * @return {Arrau} Array produced from object.
         */
        makeArray: Tools.toArray,

        /**
         * Returns the index of the specified item inside the array.
         *
         * @static
         * @method inArray
         * @param {Object} item Item to look for.
         * @param {Array} array Array to look for item in.
         * @return {Number} Index of the item or -1.
         */
        inArray: inArray,

        /**
         * Returns true/false if the specified object is an array or not.
         *
         * @static
         * @method isArray
         * @param {Object} array Object to check if it's an array or not.
         * @return {Boolean} True/false if the object is an array.
         */
        isArray: Tools.isArray,

        /**
         * Executes the callback function for each item in array/object. If you return false in the
         * callback it will break the loop.
         *
         * @static
         * @method each
         * @param {Object} obj Object to iterate.
         * @param {function} callback Callback function to execute for each item.
         */
        each: each,

        /**
         * Removes whitespace from the beginning and end of a string.
         *
         * @static
         * @method trim
         * @param {String} str String to remove whitespace from.
         * @return {String} New string with removed whitespace.
         */
        trim: trim,

        /**
         * Filters out items from the input array by calling the specified function for each item.
         * If the function returns false the item will be excluded if it returns true it will be included.
         *
         * @static
         * @method grep
         * @param {Array} array Array of items to loop though.
         * @param {function} callback Function to call for each item. Include/exclude depends on it's return value.
         * @return {Array} New array with values imported and filtered based in input.
         * @example
         * // Filter out some items, this will return an array with 4 and 5
         * var items = DomQuery.grep([1, 2, 3, 4, 5], function(v) {return v > 3;});
         */
        grep: grep,

        // Sizzle
        find: Sizzle,
        expr: Sizzle.selectors,
        unique: Sizzle.uniqueSort,
        text: Sizzle.getText,
        contains: Sizzle.contains,
        filter: function(expr, elems, not) {
            if (not) {
                expr = ":not(" + expr + ")";
            }

            if (elems.length === 1) {
                elems = DomQuery.find.matchesSelector(elems[0], expr) ? [elems[0]] : [];
            } else {
                elems = DomQuery.find.matches(expr, elems);
            }

            return elems;
        }
    });

    function dir(el, prop, until) {
        var matched = [], cur = el[prop];

        if (typeof until != 'string' && until instanceof DomQuery) {
            until = until[0];
        }

        while (cur && cur.nodeType !== 9) {
            if (until !== undefined) {
                if (cur === until) {
                    break;
                }

                if (typeof until == 'string' && DomQuery(cur).is(until)) {
                    break;
                }
            }

            if (cur.nodeType === 1) {
                matched.push(cur);
            }

            cur = cur[prop];
        }

        return matched;
    }

    function sibling(node, siblingName, nodeType, until) {
        var result = [];

        if (until instanceof DomQuery) {
            until = until[0];
        }

        for (; node; node = node[siblingName]) {
            if (nodeType && node.nodeType !== nodeType) {
                continue;
            }

            if (until !== undefined) {
                if (node === until) {
                    break;
                }

                if (typeof until == 'string' && DomQuery(node).is(until)) {
                    break;
                }
            }

            result.push(node);
        }

        return result;
    }

    function firstSibling(node, siblingName, nodeType) {
        for (node = node[siblingName]; node; node = node[siblingName]) {
            if (node.nodeType == nodeType) {
                return node;
            }
        }

        return null;
    }

    each({
        /**
         * Returns a new collection with the parent of each item in current collection matching the optional selector.
         *
         * @method parent
         * @param {String} selector Selector to match parents agains.
         * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents.
         */
        parent: function(node) {
            var parent = node.parentNode;

            return parent && parent.nodeType !== 11 ? parent : null;
        },

        /**
         * Returns a new collection with the all the parents of each item in current collection matching the optional selector.
         *
         * @method parents
         * @param {String} selector Selector to match parents agains.
         * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents.
         */
        parents: function(node) {
            return dir(node, "parentNode");
        },

        /**
         * Returns a new collection with next sibling of each item in current collection matching the optional selector.
         *
         * @method next
         * @param {String} selector Selector to match the next element against.
         * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
         */
        next: function(node) {
            return firstSibling(node, 'nextSibling', 1);
        },

        /**
         * Returns a new collection with previous sibling of each item in current collection matching the optional selector.
         *
         * @method prev
         * @param {String} selector Selector to match the previous element against.
         * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
         */
        prev: function(node) {
            return firstSibling(node, 'previousSibling', 1);
        },

        /**
         * Returns all child elements matching the optional selector.
         *
         * @method children
         * @param {String} selector Selector to match the elements against.
         * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
         */
        children: function(node) {
            return sibling(node.firstChild, 'nextSibling', 1);
        },

        /**
         * Returns all child nodes matching the optional selector.
         *
         * @method contents
         * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
         */
        contents: function(node) {
            return Tools.toArray((node.nodeName === "iframe" ? node.contentDocument || node.contentWindow.document : node).childNodes);
        }
    }, function(name, fn) {
        DomQuery.fn[name] = function(selector) {
            var self = this, result = [];

            self.each(function() {
                var nodes = fn.call(result, this, selector, result);

                if (nodes) {
                    if (DomQuery.isArray(nodes)) {
                        result.push.apply(result, nodes);
                    } else {
                        result.push(nodes);
                    }
                }
            });

            // If traversing on multiple elements we might get the same elements twice
            if (this.length > 1) {
                result = DomQuery.unique(result);

                if (name.indexOf('parents') === 0) {
                    result = result.reverse();
                }
            }

            result = DomQuery(result);

            if (selector) {
                return result.filter(selector);
            }

            return result;
        };
    });

    each({
        /**
         * Returns a new collection with the all the parents until the matching selector/element
         * of each item in current collection matching the optional selector.
         *
         * @method parentsUntil
         * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element.
         * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents.
         */
        parentsUntil: function(node, until) {
            return dir(node, "parentNode", until);
        },

        /**
         * Returns a new collection with all next siblings of each item in current collection matching the optional selector.
         *
         * @method nextUntil
         * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element.
         * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
         */
        nextUntil: function(node, until) {
            return sibling(node, 'nextSibling', 1, until).slice(1);
        },

        /**
         * Returns a new collection with all previous siblings of each item in current collection matching the optional selector.
         *
         * @method prevUntil
         * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element.
         * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
         */
        prevUntil: function(node, until) {
            return sibling(node, 'previousSibling', 1, until).slice(1);
        }
    }, function(name, fn) {
        DomQuery.fn[name] = function(selector, filter) {
            var self = this, result = [];

            self.each(function() {
                var nodes = fn.call(result, this, selector, result);

                if (nodes) {
                    if (DomQuery.isArray(nodes)) {
                        result.push.apply(result, nodes);
                    } else {
                        result.push(nodes);
                    }
                }
            });

            // If traversing on multiple elements we might get the same elements twice
            if (this.length > 1) {
                result = DomQuery.unique(result);

                if (name.indexOf('parents') === 0 || name === 'prevUntil') {
                    result = result.reverse();
                }
            }

            result = DomQuery(result);

            if (filter) {
                return result.filter(filter);
            }

            return result;
        };
    });

    /**
     * Returns true/false if the current set items matches the selector.
     *
     * @method is
     * @param {String} selector Selector to match the elements against.
     * @return {Boolean} True/false if the current set matches the selector.
     */
    DomQuery.fn.is = function(selector) {
        return !!selector && this.filter(selector).length > 0;
    };

    DomQuery.fn.init.prototype = DomQuery.fn;

    DomQuery.overrideDefaults = function(callback) {
        var defaults;

        function jQuerySub(selector, context) {
            defaults = defaults || callback();

            if (arguments.length === 0) {
                selector = defaults.element;
            }

            if (!context) {
                context = defaults.context;
            }

            return new jQuerySub.fn.init(selector, context);
        }

        DomQuery.extend(jQuerySub, this);

        return jQuerySub;
    };

    function appendHooks(targetHooks, prop, hooks) {
        each(hooks, function(name, func) {
            targetHooks[name] = targetHooks[name] || {};
            targetHooks[name][prop] = func;
        });
    }

    if (Env.ie && Env.ie < 8) {
        appendHooks(attrHooks, 'get', {
            maxlength: function(elm) {
                var value = elm.maxLength;

                if (value === 0x7fffffff) {
                    return undef;
                }

                return value;
            },

            size: function(elm) {
                var value = elm.size;

                if (value === 20) {
                    return undef;
                }

                return value;
            },

            'class': function(elm) {
                return elm.className;
            },

            style: function(elm) {
                var value = elm.style.cssText;

                if (value.length === 0) {
                    return undef;
                }

                return value;
            }
        });

        appendHooks(attrHooks, 'set', {
            'class': function(elm, value) {
                elm.className = value;
            },

            style: function(elm, value) {
                elm.style.cssText = value;
            }
        });
    }

    if (Env.ie && Env.ie < 9) {
        cssFix.float = 'styleFloat';

        appendHooks(cssHooks, 'set', {
            opacity: function(elm, value) {
                var style = elm.style;

                if (value === null || value === '') {
                    style.removeAttribute('filter');
                } else {
                    style.zoom = 1;
                    style.filter = 'alpha(opacity=' + (value * 100) + ')';
                }
            }
        });
    }

    DomQuery.attrHooks = attrHooks;
    DomQuery.cssHooks = cssHooks;

    return DomQuery;
});

// Included from: js/tinymce/classes/html/Styles.js

/**
 * Styles.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class is used to parse CSS styles it also compresses styles to reduce the output size.
 *
 * @example
 * var Styles = new tinymce.html.Styles({
 *    url_converter: function(url) {
 *       return url;
 *    }
 * });
 *
 * styles = Styles.parse('border: 1px solid red');
 * styles.color = 'red';
 *
 * console.log(new tinymce.html.StyleSerializer().serialize(styles));
 *
 * @class tinymce.html.Styles
 * @version 3.4
 */
define("tinymce/html/Styles", [], function() {
    return function(settings, schema) {
        /*jshint maxlen:255 */
        /*eslint max-len:0 */
        var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
            urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
            styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
            trimRightRegExp = /\s+$/,
            undef, i, encodingLookup = {}, encodingItems, validStyles, invalidStyles, invisibleChar = '\uFEFF';

        settings = settings || {};

        if (schema) {
            validStyles = schema.getValidStyles();
            invalidStyles = schema.getInvalidStyles();
        }

        encodingItems = ('\\" \\\' \\; \\: ; : ' + invisibleChar).split(' ');
        for (i = 0; i < encodingItems.length; i++) {
            encodingLookup[encodingItems[i]] = invisibleChar + i;
            encodingLookup[invisibleChar + i] = encodingItems[i];
        }

        function toHex(match, r, g, b) {
            function hex(val) {
                val = parseInt(val, 10).toString(16);

                return val.length > 1 ? val : '0' + val; // 0 -> 00
            }

            return '#' + hex(r) + hex(g) + hex(b);
        }

        return {
            /**
             * Parses the specified RGB color value and returns a hex version of that color.
             *
             * @method toHex
             * @param {String} color RGB string value like rgb(1,2,3)
             * @return {String} Hex version of that RGB value like #FF00FF.
             */
            toHex: function(color) {
                return color.replace(rgbRegExp, toHex);
            },

            /**
             * Parses the specified style value into an object collection. This parser will also
             * merge and remove any redundant items that browsers might have added. It will also convert non hex
             * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
             *
             * @method parse
             * @param {String} css Style value to parse for example: border:1px solid red;.
             * @return {Object} Object representation of that style like {border: '1px solid red'}
             */
            parse: function(css) {
                var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter;
                var urlConverterScope = settings.url_converter_scope || this;

                function compress(prefix, suffix, noJoin) {
                    var top, right, bottom, left;

                    top = styles[prefix + '-top' + suffix];
                    if (!top) {
                        return;
                    }

                    right = styles[prefix + '-right' + suffix];
                    if (!right) {
                        return;
                    }

                    bottom = styles[prefix + '-bottom' + suffix];
                    if (!bottom) {
                        return;
                    }

                    left = styles[prefix + '-left' + suffix];
                    if (!left) {
                        return;
                    }

                    var box = [top, right, bottom, left];
                    i = box.length - 1;
                    while (i--) {
                        if (box[i] !== box[i + 1]) {
                            break;
                        }
                    }

                    if (i > -1 && noJoin) {
                        return;
                    }

                    styles[prefix + suffix] = i == -1 ? box[0] : box.join(' ');
                    delete styles[prefix + '-top' + suffix];
                    delete styles[prefix + '-right' + suffix];
                    delete styles[prefix + '-bottom' + suffix];
                    delete styles[prefix + '-left' + suffix];
                }

                /**
                 * Checks if the specific style can be compressed in other words if all border-width are equal.
                 */
                function canCompress(key) {
                    var value = styles[key], i;

                    if (!value) {
                        return;
                    }

                    value = value.split(' ');
                    i = value.length;
                    while (i--) {
                        if (value[i] !== value[0]) {
                            return false;
                        }
                    }

                    styles[key] = value[0];

                    return true;
                }

                /**
                 * Compresses multiple styles into one style.
                 */
                function compress2(target, a, b, c) {
                    if (!canCompress(a)) {
                        return;
                    }

                    if (!canCompress(b)) {
                        return;
                    }

                    if (!canCompress(c)) {
                        return;
                    }

                    // Compress
                    styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
                    delete styles[a];
                    delete styles[b];
                    delete styles[c];
                }

                // Encodes the specified string by replacing all \" \' ; : with _<num>
                function encode(str) {
                    isEncoded = true;

                    return encodingLookup[str];
                }

                // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
                // It will also decode the \" \' if keep_slashes is set to fale or omitted
                function decode(str, keep_slashes) {
                    if (isEncoded) {
                        str = str.replace(/\uFEFF[0-9]/g, function(str) {
                            return encodingLookup[str];
                        });
                    }

                    if (!keep_slashes) {
                        str = str.replace(/\\([\'\";:])/g, "$1");
                    }

                    return str;
                }

                function processUrl(match, url, url2, url3, str, str2) {
                    str = str || str2;

                    if (str) {
                        str = decode(str);

                        // Force strings into single quote format
                        return "'" + str.replace(/\'/g, "\\'") + "'";
                    }

                    url = decode(url || url2 || url3);

                    if (!settings.allow_script_urls) {
                        var scriptUrl = url.replace(/[\s\r\n]+/, '');

                        if (/(java|vb)script:/i.test(scriptUrl)) {
                            return "";
                        }

                        if (!settings.allow_svg_data_urls && /^data:image\/svg/i.test(scriptUrl)) {
                            return "";
                        }
                    }

                    // Convert the URL to relative/absolute depending on config
                    if (urlConverter) {
                        url = urlConverter.call(urlConverterScope, url, 'style');
                    }

                    // Output new URL format
                    return "url('" + url.replace(/\'/g, "\\'") + "')";
                }

                if (css) {
                    css = css.replace(/[\u0000-\u001F]/g, '');

                    // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
                    css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
                        return str.replace(/[;:]/g, encode);
                    });

                    // Parse styles
                    while ((matches = styleRegExp.exec(css))) {
                        name = matches[1].replace(trimRightRegExp, '').toLowerCase();
                        value = matches[2].replace(trimRightRegExp, '');

                        // Decode escaped sequences like \65 -> e
                        /*jshint loopfunc:true*/
                        /*eslint no-loop-func:0 */
                        value = value.replace(/\\[0-9a-f]+/g, function(e) {
                            return String.fromCharCode(parseInt(e.substr(1), 16));
                        });

                        if (name && value.length > 0) {
                            // Don't allow behavior name or expression/comments within the values
                            if (!settings.allow_script_urls && (name == "behavior" || /expression\s*\(|\/\*|\*\//.test(value))) {
                                continue;
                            }

                            // Opera will produce 700 instead of bold in their style values
                            if (name === 'font-weight' && value === '700') {
                                value = 'bold';
                            } else if (name === 'color' || name === 'background-color') { // Lowercase colors like RED
                                value = value.toLowerCase();
                            }

                            // Convert RGB colors to HEX
                            value = value.replace(rgbRegExp, toHex);

                            // Convert URLs and force them into url('value') format
                            value = value.replace(urlOrStrRegExp, processUrl);
                            styles[name] = isEncoded ? decode(value, true) : value;
                        }

                        styleRegExp.lastIndex = matches.index + matches[0].length;
                    }
                    // Compress the styles to reduce it's size for example IE will expand styles
                    compress("border", "", true);
                    compress("border", "-width");
                    compress("border", "-color");
                    compress("border", "-style");
                    compress("padding", "");
                    compress("margin", "");
                    compress2('border', 'border-width', 'border-style', 'border-color');

                    // Remove pointless border, IE produces these
                    if (styles.border === 'medium none') {
                        delete styles.border;
                    }

                    // IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p>
                    // So lets asume it shouldn't be there
                    if (styles['border-image'] === 'none') {
                        delete styles['border-image'];
                    }
                }

                return styles;
            },

            /**
             * Serializes the specified style object into a string.
             *
             * @method serialize
             * @param {Object} styles Object to serialize as string for example: {border: '1px solid red'}
             * @param {String} elementName Optional element name, if specified only the styles that matches the schema will be serialized.
             * @return {String} String representation of the style object for example: border: 1px solid red.
             */
            serialize: function(styles, elementName) {
                var css = '', name, value;

                function serializeStyles(name) {
                    var styleList, i, l, value;

                    styleList = validStyles[name];
                    if (styleList) {
                        for (i = 0, l = styleList.length; i < l; i++) {
                            name = styleList[i];
                            value = styles[name];

                            if (value !== undef && value.length > 0) {
                                css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
                            }
                        }
                    }
                }

                function isValid(name, elementName) {
                    var styleMap;

                    styleMap = invalidStyles['*'];
                    if (styleMap && styleMap[name]) {
                        return false;
                    }

                    styleMap = invalidStyles[elementName];
                    if (styleMap && styleMap[name]) {
                        return false;
                    }

                    return true;
                }

                // Serialize styles according to schema
                if (elementName && validStyles) {
                    // Serialize global styles and element specific styles
                    serializeStyles('*');
                    serializeStyles(elementName);
                } else {
                    // Output the styles in the order they are inside the object
                    for (name in styles) {
                        value = styles[name];

                        if (value !== undef && value.length > 0) {
                            if (!invalidStyles || isValid(name, elementName)) {
                                css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
                            }
                        }
                    }
                }

                return css;
            }
        };
    };
});

// Included from: js/tinymce/classes/dom/TreeWalker.js

/**
 * TreeWalker.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * TreeWalker class enables you to walk the DOM in a linear manner.
 *
 * @class tinymce.dom.TreeWalker
 * @example
 * var walker = new tinymce.dom.TreeWalker(startNode);
 *
 * do {
 *     console.log(walker.current());
 * } while (walker.next());
 */
define("tinymce/dom/TreeWalker", [], function() {
    /**
     * Constructs a new TreeWalker instance.
     *
     * @constructor
     * @method TreeWalker
     * @param {Node} startNode Node to start walking from.
     * @param {node} rootNode Optional root node to never walk out of.
     */
    return function(startNode, rootNode) {
        var node = startNode;

        function findSibling(node, startName, siblingName, shallow) {
            var sibling, parent;

            if (node) {
                // Walk into nodes if it has a start
                if (!shallow && node[startName]) {
                    return node[startName];
                }

                // Return the sibling if it has one
                if (node != rootNode) {
                    sibling = node[siblingName];
                    if (sibling) {
                        return sibling;
                    }

                    // Walk up the parents to look for siblings
                    for (parent = node.parentNode; parent && parent != rootNode; parent = parent.parentNode) {
                        sibling = parent[siblingName];
                        if (sibling) {
                            return sibling;
                        }
                    }
                }
            }
        }

        /**
         * Returns the current node.
         *
         * @method current
         * @return {Node} Current node where the walker is.
         */
        this.current = function() {
            return node;
        };

        /**
         * Walks to the next node in tree.
         *
         * @method next
         * @return {Node} Current node where the walker is after moving to the next node.
         */
        this.next = function(shallow) {
            node = findSibling(node, 'firstChild', 'nextSibling', shallow);
            return node;
        };

        /**
         * Walks to the previous node in tree.
         *
         * @method prev
         * @return {Node} Current node where the walker is after moving to the previous node.
         */
        this.prev = function(shallow) {
            node = findSibling(node, 'lastChild', 'previousSibling', shallow);
            return node;
        };
    };
});

// Included from: js/tinymce/classes/dom/Range.js

/**
 * Range.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

define("tinymce/dom/Range", [
    "tinymce/util/Tools"
], function(Tools) {
    // Range constructor
    function Range(dom) {
        var self = this,
            doc = dom.doc,
            EXTRACT = 0,
            CLONE = 1,
            DELETE = 2,
            TRUE = true,
            FALSE = false,
            START_OFFSET = 'startOffset',
            START_CONTAINER = 'startContainer',
            END_CONTAINER = 'endContainer',
            END_OFFSET = 'endOffset',
            extend = Tools.extend,
            nodeIndex = dom.nodeIndex;

        function createDocumentFragment() {
            return doc.createDocumentFragment();
        }

        function setStart(n, o) {
            _setEndPoint(TRUE, n, o);
        }

        function setEnd(n, o) {
            _setEndPoint(FALSE, n, o);
        }

        function setStartBefore(n) {
            setStart(n.parentNode, nodeIndex(n));
        }

        function setStartAfter(n) {
            setStart(n.parentNode, nodeIndex(n) + 1);
        }

        function setEndBefore(n) {
            setEnd(n.parentNode, nodeIndex(n));
        }

        function setEndAfter(n) {
            setEnd(n.parentNode, nodeIndex(n) + 1);
        }

        function collapse(ts) {
            if (ts) {
                self[END_CONTAINER] = self[START_CONTAINER];
                self[END_OFFSET] = self[START_OFFSET];
            } else {
                self[START_CONTAINER] = self[END_CONTAINER];
                self[START_OFFSET] = self[END_OFFSET];
            }

            self.collapsed = TRUE;
        }

        function selectNode(n) {
            setStartBefore(n);
            setEndAfter(n);
        }

        function selectNodeContents(n) {
            setStart(n, 0);
            setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
        }

        function compareBoundaryPoints(h, r) {
            var sc = self[START_CONTAINER], so = self[START_OFFSET], ec = self[END_CONTAINER], eo = self[END_OFFSET],
            rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;

            // Check START_TO_START
            if (h === 0) {
                return _compareBoundaryPoints(sc, so, rsc, rso);
            }

            // Check START_TO_END
            if (h === 1) {
                return _compareBoundaryPoints(ec, eo, rsc, rso);
            }

            // Check END_TO_END
            if (h === 2) {
                return _compareBoundaryPoints(ec, eo, rec, reo);
            }

            // Check END_TO_START
            if (h === 3) {
                return _compareBoundaryPoints(sc, so, rec, reo);
            }
        }

        function deleteContents() {
            _traverse(DELETE);
        }

        function extractContents() {
            return _traverse(EXTRACT);
        }

        function cloneContents() {
            return _traverse(CLONE);
        }

        function insertNode(n) {
            var startContainer = this[START_CONTAINER],
                startOffset = this[START_OFFSET], nn, o;

            // Node is TEXT_NODE or CDATA
            if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
                if (!startOffset) {
                    // At the start of text
                    startContainer.parentNode.insertBefore(n, startContainer);
                } else if (startOffset >= startContainer.nodeValue.length) {
                    // At the end of text
                    dom.insertAfter(n, startContainer);
                } else {
                    // Middle, need to split
                    nn = startContainer.splitText(startOffset);
                    startContainer.parentNode.insertBefore(n, nn);
                }
            } else {
                // Insert element node
                if (startContainer.childNodes.length > 0) {
                    o = startContainer.childNodes[startOffset];
                }

                if (o) {
                    startContainer.insertBefore(n, o);
                } else {
                    if (startContainer.nodeType == 3) {
                        dom.insertAfter(n, startContainer);
                    } else {
                        startContainer.appendChild(n);
                    }
                }
            }
        }

        function surroundContents(n) {
            var f = self.extractContents();

            self.insertNode(n);
            n.appendChild(f);
            self.selectNode(n);
        }

        function cloneRange() {
            return extend(new Range(dom), {
                startContainer: self[START_CONTAINER],
                startOffset: self[START_OFFSET],
                endContainer: self[END_CONTAINER],
                endOffset: self[END_OFFSET],
                collapsed: self.collapsed,
                commonAncestorContainer: self.commonAncestorContainer
            });
        }

        // Private methods

        function _getSelectedNode(container, offset) {
            var child;

            if (container.nodeType == 3 /* TEXT_NODE */) {
                return container;
            }

            if (offset < 0) {
                return container;
            }

            child = container.firstChild;
            while (child && offset > 0) {
                --offset;
                child = child.nextSibling;
            }

            if (child) {
                return child;
            }

            return container;
        }

        function _isCollapsed() {
            return (self[START_CONTAINER] == self[END_CONTAINER] && self[START_OFFSET] == self[END_OFFSET]);
        }

        function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
            var c, offsetC, n, cmnRoot, childA, childB;

            // In the first case the boundary-points have the same container. A is before B
            // if its offset is less than the offset of B, A is equal to B if its offset is
            // equal to the offset of B, and A is after B if its offset is greater than the
            // offset of B.
            if (containerA == containerB) {
                if (offsetA == offsetB) {
                    return 0; // equal
                }

                if (offsetA < offsetB) {
                    return -1; // before
                }

                return 1; // after
            }

            // In the second case a child node C of the container of A is an ancestor
            // container of B. In this case, A is before B if the offset of A is less than or
            // equal to the index of the child node C and A is after B otherwise.
            c = containerB;
            while (c && c.parentNode != containerA) {
                c = c.parentNode;
            }

            if (c) {
                offsetC = 0;
                n = containerA.firstChild;

                while (n != c && offsetC < offsetA) {
                    offsetC++;
                    n = n.nextSibling;
                }

                if (offsetA <= offsetC) {
                    return -1; // before
                }

                return 1; // after
            }

            // In the third case a child node C of the container of B is an ancestor container
            // of A. In this case, A is before B if the index of the child node C is less than
            // the offset of B and A is after B otherwise.
            c = containerA;
            while (c && c.parentNode != containerB) {
                c = c.parentNode;
            }

            if (c) {
                offsetC = 0;
                n = containerB.firstChild;

                while (n != c && offsetC < offsetB) {
                    offsetC++;
                    n = n.nextSibling;
                }

                if (offsetC < offsetB) {
                    return -1; // before
                }

                return 1; // after
            }

            // In the fourth case, none of three other cases hold: the containers of A and B
            // are siblings or descendants of sibling nodes. In this case, A is before B if
            // the container of A is before the container of B in a pre-order traversal of the
            // Ranges' context tree and A is after B otherwise.
            cmnRoot = dom.findCommonAncestor(containerA, containerB);
            childA = containerA;

            while (childA && childA.parentNode != cmnRoot) {
                childA = childA.parentNode;
            }

            if (!childA) {
                childA = cmnRoot;
            }

            childB = containerB;
            while (childB && childB.parentNode != cmnRoot) {
                childB = childB.parentNode;
            }

            if (!childB) {
                childB = cmnRoot;
            }

            if (childA == childB) {
                return 0; // equal
            }

            n = cmnRoot.firstChild;
            while (n) {
                if (n == childA) {
                    return -1; // before
                }

                if (n == childB) {
                    return 1; // after
                }

                n = n.nextSibling;
            }
        }

        function _setEndPoint(st, n, o) {
            var ec, sc;

            if (st) {
                self[START_CONTAINER] = n;
                self[START_OFFSET] = o;
            } else {
                self[END_CONTAINER] = n;
                self[END_OFFSET] = o;
            }

            // If one boundary-point of a Range is set to have a root container
            // other than the current one for the Range, the Range is collapsed to
            // the new position. This enforces the restriction that both boundary-
            // points of a Range must have the same root container.
            ec = self[END_CONTAINER];
            while (ec.parentNode) {
                ec = ec.parentNode;
            }

            sc = self[START_CONTAINER];
            while (sc.parentNode) {
                sc = sc.parentNode;
            }

            if (sc == ec) {
                // The start position of a Range is guaranteed to never be after the
                // end position. To enforce this restriction, if the start is set to
                // be at a position after the end, the Range is collapsed to that
                // position.
                if (_compareBoundaryPoints(self[START_CONTAINER], self[START_OFFSET], self[END_CONTAINER], self[END_OFFSET]) > 0) {
                    self.collapse(st);
                }
            } else {
                self.collapse(st);
            }

            self.collapsed = _isCollapsed();
            self.commonAncestorContainer = dom.findCommonAncestor(self[START_CONTAINER], self[END_CONTAINER]);
        }

        function _traverse(how) {
            var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;

            if (self[START_CONTAINER] == self[END_CONTAINER]) {
                return _traverseSameContainer(how);
            }

            for (c = self[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
                if (p == self[START_CONTAINER]) {
                    return _traverseCommonStartContainer(c, how);
                }

                ++endContainerDepth;
            }

            for (c = self[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
                if (p == self[END_CONTAINER]) {
                    return _traverseCommonEndContainer(c, how);
                }

                ++startContainerDepth;
            }

            depthDiff = startContainerDepth - endContainerDepth;

            startNode = self[START_CONTAINER];
            while (depthDiff > 0) {
                startNode = startNode.parentNode;
                depthDiff--;
            }

            endNode = self[END_CONTAINER];
            while (depthDiff < 0) {
                endNode = endNode.parentNode;
                depthDiff++;
            }

            // ascend the ancestor hierarchy until we have a common parent.
            for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
                startNode = sp;
                endNode = ep;
            }

            return _traverseCommonAncestors(startNode, endNode, how);
        }

        function _traverseSameContainer(how) {
            var frag, s, sub, n, cnt, sibling, xferNode, start, len;

            if (how != DELETE) {
                frag = createDocumentFragment();
            }

            // If selection is empty, just return the fragment
            if (self[START_OFFSET] == self[END_OFFSET]) {
                return frag;
            }

            // Text node needs special case handling
            if (self[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
                // get the substring
                s = self[START_CONTAINER].nodeValue;
                sub = s.substring(self[START_OFFSET], self[END_OFFSET]);

                // set the original text node to its new value
                if (how != CLONE) {
                    n = self[START_CONTAINER];
                    start = self[START_OFFSET];
                    len = self[END_OFFSET] - self[START_OFFSET];

                    if (start === 0 && len >= n.nodeValue.length - 1) {
                        n.parentNode.removeChild(n);
                    } else {
                        n.deleteData(start, len);
                    }

                    // Nothing is partially selected, so collapse to start point
                    self.collapse(TRUE);
                }

                if (how == DELETE) {
                    return;
                }

                if (sub.length > 0) {
                    frag.appendChild(doc.createTextNode(sub));
                }

                return frag;
            }

            // Copy nodes between the start/end offsets.
            n = _getSelectedNode(self[START_CONTAINER], self[START_OFFSET]);
            cnt = self[END_OFFSET] - self[START_OFFSET];

            while (n && cnt > 0) {
                sibling = n.nextSibling;
                xferNode = _traverseFullySelected(n, how);

                if (frag) {
                    frag.appendChild(xferNode);
                }

                --cnt;
                n = sibling;
            }

            // Nothing is partially selected, so collapse to start point
            if (how != CLONE) {
                self.collapse(TRUE);
            }

            return frag;
        }

        function _traverseCommonStartContainer(endAncestor, how) {
            var frag, n, endIdx, cnt, sibling, xferNode;

            if (how != DELETE) {
                frag = createDocumentFragment();
            }

            n = _traverseRightBoundary(endAncestor, how);

            if (frag) {
                frag.appendChild(n);
            }

            endIdx = nodeIndex(endAncestor);
            cnt = endIdx - self[START_OFFSET];

            if (cnt <= 0) {
                // Collapse to just before the endAncestor, which
                // is partially selected.
                if (how != CLONE) {
                    self.setEndBefore(endAncestor);
                    self.collapse(FALSE);
                }

                return frag;
            }

            n = endAncestor.previousSibling;
            while (cnt > 0) {
                sibling = n.previousSibling;
                xferNode = _traverseFullySelected(n, how);

                if (frag) {
                    frag.insertBefore(xferNode, frag.firstChild);
                }

                --cnt;
                n = sibling;
            }

            // Collapse to just before the endAncestor, which
            // is partially selected.
            if (how != CLONE) {
                self.setEndBefore(endAncestor);
                self.collapse(FALSE);
            }

            return frag;
        }

        function _traverseCommonEndContainer(startAncestor, how) {
            var frag, startIdx, n, cnt, sibling, xferNode;

            if (how != DELETE) {
                frag = createDocumentFragment();
            }

            n = _traverseLeftBoundary(startAncestor, how);
            if (frag) {
                frag.appendChild(n);
            }

            startIdx = nodeIndex(startAncestor);
            ++startIdx; // Because we already traversed it

            cnt = self[END_OFFSET] - startIdx;
            n = startAncestor.nextSibling;
            while (n && cnt > 0) {
                sibling = n.nextSibling;
                xferNode = _traverseFullySelected(n, how);

                if (frag) {
                    frag.appendChild(xferNode);
                }

                --cnt;
                n = sibling;
            }

            if (how != CLONE) {
                self.setStartAfter(startAncestor);
                self.collapse(TRUE);
            }

            return frag;
        }

        function _traverseCommonAncestors(startAncestor, endAncestor, how) {
            var n, frag, startOffset, endOffset, cnt, sibling, nextSibling;

            if (how != DELETE) {
                frag = createDocumentFragment();
            }

            n = _traverseLeftBoundary(startAncestor, how);
            if (frag) {
                frag.appendChild(n);
            }

            startOffset = nodeIndex(startAncestor);
            endOffset = nodeIndex(endAncestor);
            ++startOffset;

            cnt = endOffset - startOffset;
            sibling = startAncestor.nextSibling;

            while (cnt > 0) {
                nextSibling = sibling.nextSibling;
                n = _traverseFullySelected(sibling, how);

                if (frag) {
                    frag.appendChild(n);
                }

                sibling = nextSibling;
                --cnt;
            }

            n = _traverseRightBoundary(endAncestor, how);

            if (frag) {
                frag.appendChild(n);
            }

            if (how != CLONE) {
                self.setStartAfter(startAncestor);
                self.collapse(TRUE);
            }

            return frag;
        }

        function _traverseRightBoundary(root, how) {
            var next = _getSelectedNode(self[END_CONTAINER], self[END_OFFSET] - 1), parent, clonedParent;
            var prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != self[END_CONTAINER];

            if (next == root) {
                return _traverseNode(next, isFullySelected, FALSE, how);
            }

            parent = next.parentNode;
            clonedParent = _traverseNode(parent, FALSE, FALSE, how);

            while (parent) {
                while (next) {
                    prevSibling = next.previousSibling;
                    clonedChild = _traverseNode(next, isFullySelected, FALSE, how);

                    if (how != DELETE) {
                        clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
                    }

                    isFullySelected = TRUE;
                    next = prevSibling;
                }

                if (parent == root) {
                    return clonedParent;
                }

                next = parent.previousSibling;
                parent = parent.parentNode;

                clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);

                if (how != DELETE) {
                    clonedGrandParent.appendChild(clonedParent);
                }

                clonedParent = clonedGrandParent;
            }
        }

        function _traverseLeftBoundary(root, how) {
            var next = _getSelectedNode(self[START_CONTAINER], self[START_OFFSET]), isFullySelected = next != self[START_CONTAINER];
            var parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;

            if (next == root) {
                return _traverseNode(next, isFullySelected, TRUE, how);
            }

            parent = next.parentNode;
            clonedParent = _traverseNode(parent, FALSE, TRUE, how);

            while (parent) {
                while (next) {
                    nextSibling = next.nextSibling;
                    clonedChild = _traverseNode(next, isFullySelected, TRUE, how);

                    if (how != DELETE) {
                        clonedParent.appendChild(clonedChild);
                    }

                    isFullySelected = TRUE;
                    next = nextSibling;
                }

                if (parent == root) {
                    return clonedParent;
                }

                next = parent.nextSibling;
                parent = parent.parentNode;

                clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);

                if (how != DELETE) {
                    clonedGrandParent.appendChild(clonedParent);
                }

                clonedParent = clonedGrandParent;
            }
        }

        function _traverseNode(n, isFullySelected, isLeft, how) {
            var txtValue, newNodeValue, oldNodeValue, offset, newNode;

            if (isFullySelected) {
                return _traverseFullySelected(n, how);
            }

            if (n.nodeType == 3 /* TEXT_NODE */) {
                txtValue = n.nodeValue;

                if (isLeft) {
                    offset = self[START_OFFSET];
                    newNodeValue = txtValue.substring(offset);
                    oldNodeValue = txtValue.substring(0, offset);
                } else {
                    offset = self[END_OFFSET];
                    newNodeValue = txtValue.substring(0, offset);
                    oldNodeValue = txtValue.substring(offset);
                }

                if (how != CLONE) {
                    n.nodeValue = oldNodeValue;
                }

                if (how == DELETE) {
                    return;
                }

                newNode = dom.clone(n, FALSE);
                newNode.nodeValue = newNodeValue;

                return newNode;
            }

            if (how == DELETE) {
                return;
            }

            return dom.clone(n, FALSE);
        }

        function _traverseFullySelected(n, how) {
            if (how != DELETE) {
                return how == CLONE ? dom.clone(n, TRUE) : n;
            }

            n.parentNode.removeChild(n);
        }

        function toStringIE() {
            return dom.create('body', null, cloneContents()).outerText;
        }

        extend(self, {
            // Inital states
            startContainer: doc,
            startOffset: 0,
            endContainer: doc,
            endOffset: 0,
            collapsed: TRUE,
            commonAncestorContainer: doc,

            // Range constants
            START_TO_START: 0,
            START_TO_END: 1,
            END_TO_END: 2,
            END_TO_START: 3,

            // Public methods
            setStart: setStart,
            setEnd: setEnd,
            setStartBefore: setStartBefore,
            setStartAfter: setStartAfter,
            setEndBefore: setEndBefore,
            setEndAfter: setEndAfter,
            collapse: collapse,
            selectNode: selectNode,
            selectNodeContents: selectNodeContents,
            compareBoundaryPoints: compareBoundaryPoints,
            deleteContents: deleteContents,
            extractContents: extractContents,
            cloneContents: cloneContents,
            insertNode: insertNode,
            surroundContents: surroundContents,
            cloneRange: cloneRange,
            toStringIE: toStringIE
        });

        return self;
    }

    // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
    Range.prototype.toString = function() {
        return this.toStringIE();
    };

    return Range;
});

// Included from: js/tinymce/classes/html/Entities.js

/**
 * Entities.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/*jshint bitwise:false */
/*eslint no-bitwise:0 */

/**
 * Entity encoder class.
 *
 * @class tinymce.html.Entities
 * @static
 * @version 3.4
 */
define("tinymce/html/Entities", [
    "tinymce/util/Tools"
], function(Tools) {
    var makeMap = Tools.makeMap;

    var namedEntities, baseEntities, reverseEntities,
        attrsCharsRegExp = /[&<>\"\u0060\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
        textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
        rawCharsRegExp = /[<>&\"\']/g,
        entityRegExp = /&(#x|#)?([\w]+);/g,
        asciiMap = {
            128: "\u20AC", 130: "\u201A", 131: "\u0192", 132: "\u201E", 133: "\u2026", 134: "\u2020",
            135: "\u2021", 136: "\u02C6", 137: "\u2030", 138: "\u0160", 139: "\u2039", 140: "\u0152",
            142: "\u017D", 145: "\u2018", 146: "\u2019", 147: "\u201C", 148: "\u201D", 149: "\u2022",
            150: "\u2013", 151: "\u2014", 152: "\u02DC", 153: "\u2122", 154: "\u0161", 155: "\u203A",
            156: "\u0153", 158: "\u017E", 159: "\u0178"
        };

    // Raw entities
    baseEntities = {
        '\"': '&quot;', // Needs to be escaped since the YUI compressor would otherwise break the code
        "'": '&#39;',
        '<': '&lt;',
        '>': '&gt;',
        '&': '&amp;',
        '\u0060': '&#96;'
    };

    // Reverse lookup table for raw entities
    reverseEntities = {
        '&lt;': '<',
        '&gt;': '>',
        '&amp;': '&',
        '&quot;': '"',
        '&apos;': "'"
    };

    // Decodes text by using the browser
    function nativeDecode(text) {
        var elm;

        elm = document.createElement("div");
        elm.innerHTML = text;

        return elm.textContent || elm.innerText || text;
    }

    // Build a two way lookup table for the entities
    function buildEntitiesLookup(items, radix) {
        var i, chr, entity, lookup = {};

        if (items) {
            items = items.split(',');
            radix = radix || 10;

            // Build entities lookup table
            for (i = 0; i < items.length; i += 2) {
                chr = String.fromCharCode(parseInt(items[i], radix));

                // Only add non base entities
                if (!baseEntities[chr]) {
                    entity = '&' + items[i + 1] + ';';
                    lookup[chr] = entity;
                    lookup[entity] = chr;
                }
            }

            return lookup;
        }
    }

    // Unpack entities lookup where the numbers are in radix 32 to reduce the size
    namedEntities = buildEntitiesLookup(
        '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
        '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
        '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
        '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
        '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
        '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
        '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
        '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
        '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
        '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
        'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
        'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
        't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
        'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
        'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
        '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
        '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
        '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
        '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
        '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
        'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
        'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
        'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
        '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
        '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);

    var Entities = {
        /**
         * Encodes the specified string using raw entities. This means only the required XML base entities will be endoded.
         *
         * @method encodeRaw
         * @param {String} text Text to encode.
         * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
         * @return {String} Entity encoded text.
         */
        encodeRaw: function(text, attr) {
            return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
                return baseEntities[chr] || chr;
            });
        },

        /**
         * Encoded the specified text with both the attributes and text entities. This function will produce larger text contents
         * since it doesn't know if the context is within a attribute or text node. This was added for compatibility
         * and is exposed as the DOMUtils.encode function.
         *
         * @method encodeAllRaw
         * @param {String} text Text to encode.
         * @return {String} Entity encoded text.
         */
        encodeAllRaw: function(text) {
            return ('' + text).replace(rawCharsRegExp, function(chr) {
                return baseEntities[chr] || chr;
            });
        },

        /**
         * Encodes the specified string using numeric entities. The core entities will be
         * encoded as named ones but all non lower ascii characters will be encoded into numeric entities.
         *
         * @method encodeNumeric
         * @param {String} text Text to encode.
         * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
         * @return {String} Entity encoded text.
         */
        encodeNumeric: function(text, attr) {
            return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
                // Multi byte sequence convert it to a single entity
                if (chr.length > 1) {
                    return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
                }

                return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
            });
        },

        /**
         * Encodes the specified string using named entities. The core entities will be encoded
         * as named ones but all non lower ascii characters will be encoded into named entities.
         *
         * @method encodeNamed
         * @param {String} text Text to encode.
         * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
         * @param {Object} entities Optional parameter with entities to use.
         * @return {String} Entity encoded text.
         */
        encodeNamed: function(text, attr, entities) {
            entities = entities || namedEntities;

            return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
                return baseEntities[chr] || entities[chr] || chr;
            });
        },

        /**
         * Returns an encode function based on the name(s) and it's optional entities.
         *
         * @method getEncodeFunc
         * @param {String} name Comma separated list of encoders for example named,numeric.
         * @param {String} entities Optional parameter with entities to use instead of the built in set.
         * @return {function} Encode function to be used.
         */
        getEncodeFunc: function(name, entities) {
            entities = buildEntitiesLookup(entities) || namedEntities;

            function encodeNamedAndNumeric(text, attr) {
                return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
                    return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
                });
            }

            function encodeCustomNamed(text, attr) {
                return Entities.encodeNamed(text, attr, entities);
            }

            // Replace + with , to be compatible with previous TinyMCE versions
            name = makeMap(name.replace(/\+/g, ','));

            // Named and numeric encoder
            if (name.named && name.numeric) {
                return encodeNamedAndNumeric;
            }

            // Named encoder
            if (name.named) {
                // Custom names
                if (entities) {
                    return encodeCustomNamed;
                }

                return Entities.encodeNamed;
            }

            // Numeric
            if (name.numeric) {
                return Entities.encodeNumeric;
            }

            // Raw encoder
            return Entities.encodeRaw;
        },

        /**
         * Decodes the specified string, this will replace entities with raw UTF characters.
         *
         * @method decode
         * @param {String} text Text to entity decode.
         * @return {String} Entity decoded string.
         */
        decode: function(text) {
            return text.replace(entityRegExp, function(all, numeric, value) {
                if (numeric) {
                    value = parseInt(value, numeric.length === 2 ? 16 : 10);

                    // Support upper UTF
                    if (value > 0xFFFF) {
                        value -= 0x10000;

                        return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
                    } else {
                        return asciiMap[value] || String.fromCharCode(value);
                    }
                }

                return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
            });
        }
    };

    return Entities;
});

// Included from: js/tinymce/classes/dom/StyleSheetLoader.js

/**
 * StyleSheetLoader.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class handles loading of external stylesheets and fires events when these are loaded.
 *
 * @class tinymce.dom.StyleSheetLoader
 * @private
 */
define("tinymce/dom/StyleSheetLoader", [], function() {
    "use strict";

    return function(document, settings) {
        var idCount = 0, loadedStates = {}, maxLoadTime;

        settings = settings || {};
        maxLoadTime = settings.maxLoadTime || 5000;

        function appendToHead(node) {
            document.getElementsByTagName('head')[0].appendChild(node);
        }

        /**
         * Loads the specified css style sheet file and call the loadedCallback once it's finished loading.
         *
         * @method load
         * @param {String} url Url to be loaded.
         * @param {Function} loadedCallback Callback to be executed when loaded.
         * @param {Function} errorCallback Callback to be executed when failed loading.
         */
        function load(url, loadedCallback, errorCallback) {
            var link, style, startTime, state;

            function passed() {
                var callbacks = state.passed, i = callbacks.length;

                while (i--) {
                    callbacks[i]();
                }

                state.status = 2;
                state.passed = [];
                state.failed = [];
            }

            function failed() {
                var callbacks = state.failed, i = callbacks.length;

                while (i--) {
                    callbacks[i]();
                }

                state.status = 3;
                state.passed = [];
                state.failed = [];
            }

            // Sniffs for older WebKit versions that have the link.onload but a broken one
            function isOldWebKit() {
                var webKitChunks = navigator.userAgent.match(/WebKit\/(\d*)/);
                return !!(webKitChunks && webKitChunks[1] < 536);
            }

            // Calls the waitCallback until the test returns true or the timeout occurs
            function wait(testCallback, waitCallback) {
                if (!testCallback()) {
                    // Wait for timeout
                    if ((new Date().getTime()) - startTime < maxLoadTime) {
                        window.setTimeout(waitCallback, 0);
                    } else {
                        failed();
                    }
                }
            }

            // Workaround for WebKit that doesn't properly support the onload event for link elements
            // Or WebKit that fires the onload event before the StyleSheet is added to the document
            function waitForWebKitLinkLoaded() {
                wait(function() {
                    var styleSheets = document.styleSheets, styleSheet, i = styleSheets.length, owner;

                    while (i--) {
                        styleSheet = styleSheets[i];
                        owner = styleSheet.ownerNode ? styleSheet.ownerNode : styleSheet.owningElement;
                        if (owner && owner.id === link.id) {
                            passed();
                            return true;
                        }
                    }
                }, waitForWebKitLinkLoaded);
            }

            // Workaround for older Geckos that doesn't have any onload event for StyleSheets
            function waitForGeckoLinkLoaded() {
                wait(function() {
                    try {
                        // Accessing the cssRules will throw an exception until the CSS file is loaded
                        var cssRules = style.sheet.cssRules;
                        passed();
                        return !!cssRules;
                    } catch (ex) {
                        // Ignore
                    }
                }, waitForGeckoLinkLoaded);
            }

            if (!loadedStates[url]) {
                state = {
                    passed: [],
                    failed: []
                };

                loadedStates[url] = state;
            } else {
                state = loadedStates[url];
            }

            if (loadedCallback) {
                state.passed.push(loadedCallback);
            }

            if (errorCallback) {
                state.failed.push(errorCallback);
            }

            // Is loading wait for it to pass
            if (state.status == 1) {
                return;
            }

            // Has finished loading and was success
            if (state.status == 2) {
                passed();
                return;
            }

            // Has finished loading and was a failure
            if (state.status == 3) {
                failed();
                return;
            }

            // Start loading
            state.status = 1;
            link = document.createElement('link');
            link.rel = 'stylesheet';
            link.type = 'text/css';
            link.id = 'u' + (idCount++);
            link.async = false;
            link.defer = false;
            startTime = new Date().getTime();

            // Feature detect onload on link element and sniff older webkits since it has an broken onload event
            if ("onload" in link && !isOldWebKit()) {
                link.onload = waitForWebKitLinkLoaded;
                link.onerror = failed;
            } else {
                // Sniff for old Firefox that doesn't support the onload event on link elements
                // TODO: Remove this in the future when everyone uses modern browsers
                if (navigator.userAgent.indexOf("Firefox") > 0) {
                    style = document.createElement('style');
                    style.textContent = '@import "' + url + '"';
                    waitForGeckoLinkLoaded();
                    appendToHead(style);
                    return;
                } else {
                    // Use the id owner on older webkits
                    waitForWebKitLinkLoaded();
                }
            }

            appendToHead(link);
            link.href = url;
        }

        this.load = load;
    };
});

// Included from: js/tinymce/classes/dom/DOMUtils.js

/**
 * DOMUtils.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Utility class for various DOM manipulation and retrieval functions.
 *
 * @class tinymce.dom.DOMUtils
 * @example
 * // Add a class to an element by id in the page
 * tinymce.DOM.addClass('someid', 'someclass');
 *
 * // Add a class to an element by id inside the editor
 * tinymce.activeEditor.dom.addClass('someid', 'someclass');
 */
define("tinymce/dom/DOMUtils", [
    "tinymce/dom/Sizzle",
    "tinymce/dom/DomQuery",
    "tinymce/html/Styles",
    "tinymce/dom/EventUtils",
    "tinymce/dom/TreeWalker",
    "tinymce/dom/Range",
    "tinymce/html/Entities",
    "tinymce/Env",
    "tinymce/util/Tools",
    "tinymce/dom/StyleSheetLoader"
], function(Sizzle, $, Styles, EventUtils, TreeWalker, Range, Entities, Env, Tools, StyleSheetLoader) {
    // Shorten names
    var each = Tools.each, is = Tools.is, grep = Tools.grep, trim = Tools.trim;
    var isIE = Env.ie;
    var simpleSelectorRe = /^([a-z0-9],?)+$/i;
    var whiteSpaceRegExp = /^[ \t\r\n]*$/;

    function setupAttrHooks(domUtils, settings) {
        var attrHooks = {}, keepValues = settings.keep_values, keepUrlHook;

        keepUrlHook = {
            set: function($elm, value, name) {
                if (settings.url_converter) {
                    value = settings.url_converter.call(settings.url_converter_scope || domUtils, value, name, $elm[0]);
                }

                $elm.attr('data-mce-' + name, value).attr(name, value);
            },

            get: function($elm, name) {
                return $elm.attr('data-mce-' + name) || $elm.attr(name);
            }
        };

        attrHooks = {
            style: {
                set: function($elm, value) {
                    if (value !== null && typeof value === 'object') {
                        $elm.css(value);
                        return;
                    }

                    if (keepValues) {
                        $elm.attr('data-mce-style', value);
                    }

                    $elm.attr('style', value);
                },

                get: function($elm) {
                    var value = $elm.attr('data-mce-style') || $elm.attr('style');

                    value = domUtils.serializeStyle(domUtils.parseStyle(value), $elm[0].nodeName);

                    return value;
                }
            }
        };

        if (keepValues) {
            attrHooks.href = attrHooks.src = keepUrlHook;
        }

        return attrHooks;
    }

    /**
     * Constructs a new DOMUtils instance. Consult the Wiki for more details on settings etc for this class.
     *
     * @constructor
     * @method DOMUtils
     * @param {Document} d Document reference to bind the utility class to.
     * @param {settings} s Optional settings collection.
     */
    function DOMUtils(doc, settings) {
        var self = this, blockElementsMap;

        self.doc = doc;
        self.win = window;
        self.files = {};
        self.counter = 0;
        self.stdMode = !isIE || doc.documentMode >= 8;
        self.boxModel = !isIE || doc.compatMode == "CSS1Compat" || self.stdMode;
        self.styleSheetLoader = new StyleSheetLoader(doc);
        self.boundEvents = [];
        self.settings = settings = settings || {};
        self.schema = settings.schema;
        self.styles = new Styles({
            url_converter: settings.url_converter,
            url_converter_scope: settings.url_converter_scope
        }, settings.schema);

        self.fixDoc(doc);
        self.events = settings.ownEvents ? new EventUtils(settings.proxy) : EventUtils.Event;
        self.attrHooks = setupAttrHooks(self, settings);
        blockElementsMap = settings.schema ? settings.schema.getBlockElements() : {};
        self.$ = $.overrideDefaults(function() {
            return {
                context: doc,
                element: self.getRoot()
            };
        });

        /**
         * Returns true/false if the specified element is a block element or not.
         *
         * @method isBlock
         * @param {Node/String} node Element/Node to check.
         * @return {Boolean} True/False state if the node is a block element or not.
         */
        self.isBlock = function(node) {
            // Fix for #5446
            if (!node) {
                return false;
            }

            // This function is called in module pattern style since it might be executed with the wrong this scope
            var type = node.nodeType;

            // If it's a node then check the type and use the nodeName
            if (type) {
                return !!(type === 1 && blockElementsMap[node.nodeName]);
            }

            return !!blockElementsMap[node];
        };
    }

    DOMUtils.prototype = {
        $$: function(elm) {
            if (typeof elm == 'string') {
                elm = this.get(elm);
            }

            return this.$(elm);
        },

        root: null,

        fixDoc: function(doc) {
            var settings = this.settings, name;

            if (isIE && settings.schema) {
                // Add missing HTML 4/5 elements to IE
                ('abbr article aside audio canvas ' +
                'details figcaption figure footer ' +
                'header hgroup mark menu meter nav ' +
                'output progress section summary ' +
                'time video').replace(/\w+/g, function(name) {
                    doc.createElement(name);
                });

                // Create all custom elements
                for (name in settings.schema.getCustomElements()) {
                    doc.createElement(name);
                }
            }
        },

        clone: function(node, deep) {
            var self = this, clone, doc;

            // TODO: Add feature detection here in the future
            if (!isIE || node.nodeType !== 1 || deep) {
                return node.cloneNode(deep);
            }

            doc = self.doc;

            // Make a HTML5 safe shallow copy
            if (!deep) {
                clone = doc.createElement(node.nodeName);

                // Copy attribs
                each(self.getAttribs(node), function(attr) {
                    self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
                });

                return clone;
            }

            return clone.firstChild;
        },

        /**
         * Returns the root node of the document. This is normally the body but might be a DIV. Parents like getParent will not
         * go above the point of this root node.
         *
         * @method getRoot
         * @return {Element} Root element for the utility class.
         */
        getRoot: function() {
            var self = this;

            return self.settings.root_element || self.doc.body;
        },

        /**
         * Returns the viewport of the window.
         *
         * @method getViewPort
         * @param {Window} win Optional window to get viewport of.
         * @return {Object} Viewport object with fields x, y, w and h.
         */
        getViewPort: function(win) {
            var doc, rootElm;

            win = !win ? this.win : win;
            doc = win.document;
            rootElm = this.boxModel ? doc.documentElement : doc.body;

            // Returns viewport size excluding scrollbars
            return {
                x: win.pageXOffset || rootElm.scrollLeft,
                y: win.pageYOffset || rootElm.scrollTop,
                w: win.innerWidth || rootElm.clientWidth,
                h: win.innerHeight || rootElm.clientHeight
            };
        },

        /**
         * Returns the rectangle for a specific element.
         *
         * @method getRect
         * @param {Element/String} elm Element object or element ID to get rectangle from.
         * @return {object} Rectangle for specified element object with x, y, w, h fields.
         */
        getRect: function(elm) {
            var self = this, pos, size;

            elm = self.get(elm);
            pos = self.getPos(elm);
            size = self.getSize(elm);

            return {
                x: pos.x, y: pos.y,
                w: size.w, h: size.h
            };
        },

        /**
         * Returns the size dimensions of the specified element.
         *
         * @method getSize
         * @param {Element/String} elm Element object or element ID to get rectangle from.
         * @return {object} Rectangle for specified element object with w, h fields.
         */
        getSize: function(elm) {
            var self = this, w, h;

            elm = self.get(elm);
            w = self.getStyle(elm, 'width');
            h = self.getStyle(elm, 'height');

            // Non pixel value, then force offset/clientWidth
            if (w.indexOf('px') === -1) {
                w = 0;
            }

            // Non pixel value, then force offset/clientWidth
            if (h.indexOf('px') === -1) {
                h = 0;
            }

            return {
                w: parseInt(w, 10) || elm.offsetWidth || elm.clientWidth,
                h: parseInt(h, 10) || elm.offsetHeight || elm.clientHeight
            };
        },

        /**
         * Returns a node by the specified selector function. This function will
         * loop through all parent nodes and call the specified function for each node.
         * If the function then returns true indicating that it has found what it was looking for, the loop execution will then end
         * and the node it found will be returned.
         *
         * @method getParent
         * @param {Node/String} node DOM node to search parents on or ID string.
         * @param {function} selector Selection function or CSS selector to execute on each node.
         * @param {Node} root Optional root element, never go below this point.
         * @return {Node} DOM Node or null if it wasn't found.
         */
        getParent: function(node, selector, root) {
            return this.getParents(node, selector, root, false);
        },

        /**
         * Returns a node list of all parents matching the specified selector function or pattern.
         * If the function then returns true indicating that it has found what it was looking for and that node will be collected.
         *
         * @method getParents
         * @param {Node/String} node DOM node to search parents on or ID string.
         * @param {function} selector Selection function to execute on each node or CSS pattern.
         * @param {Node} root Optional root element, never go below this point.
         * @return {Array} Array of nodes or null if it wasn't found.
         */
        getParents: function(node, selector, root, collect) {
            var self = this, selectorVal, result = [];

            node = self.get(node);
            collect = collect === undefined;

            // Default root on inline mode
            root = root || (self.getRoot().nodeName != 'BODY' ? self.getRoot().parentNode : null);

            // Wrap node name as func
            if (is(selector, 'string')) {
                selectorVal = selector;

                if (selector === '*') {
                    selector = function(node) {
                        return node.nodeType == 1;
                    };
                } else {
                    selector = function(node) {
                        return self.is(node, selectorVal);
                    };
                }
            }

            while (node) {
                if (node == root || !node.nodeType || node.nodeType === 9) {
                    break;
                }

                if (!selector || selector(node)) {
                    if (collect) {
                        result.push(node);
                    } else {
                        return node;
                    }
                }

                node = node.parentNode;
            }

            return collect ? result : null;
        },

        /**
         * Returns the specified element by ID or the input element if it isn't a string.
         *
         * @method get
         * @param {String/Element} n Element id to look for or element to just pass though.
         * @return {Element} Element matching the specified id or null if it wasn't found.
         */
        get: function(elm) {
            var name;

            if (elm && this.doc && typeof(elm) == 'string') {
                name = elm;
                elm = this.doc.getElementById(elm);

                // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
                if (elm && elm.id !== name) {
                    return this.doc.getElementsByName(name)[1];
                }
            }

            return elm;
        },

        /**
         * Returns the next node that matches selector or function
         *
         * @method getNext
         * @param {Node} node Node to find siblings from.
         * @param {String/function} selector Selector CSS expression or function.
         * @return {Node} Next node item matching the selector or null if it wasn't found.
         */
        getNext: function(node, selector) {
            return this._findSib(node, selector, 'nextSibling');
        },

        /**
         * Returns the previous node that matches selector or function
         *
         * @method getPrev
         * @param {Node} node Node to find siblings from.
         * @param {String/function} selector Selector CSS expression or function.
         * @return {Node} Previous node item matching the selector or null if it wasn't found.
         */
        getPrev: function(node, selector) {
            return this._findSib(node, selector, 'previousSibling');
        },

        // #ifndef jquery

        /**
         * Selects specific elements by a CSS level 3 pattern. For example "div#a1 p.test".
         * This function is optimized for the most common patterns needed in TinyMCE but it also performs well enough
         * on more complex patterns.
         *
         * @method select
         * @param {String} selector CSS level 3 pattern to select/find elements by.
         * @param {Object} scope Optional root element/scope element to search in.
         * @return {Array} Array with all matched elements.
         * @example
         * // Adds a class to all paragraphs in the currently active editor
         * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
         *
         * // Adds a class to all spans that have the test class in the currently active editor
         * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('span.test'), 'someclass')
         */
        select: function(selector, scope) {
            var self = this;

            /*eslint new-cap:0 */
            return Sizzle(selector, self.get(scope) || self.settings.root_element || self.doc, []);
        },

        /**
         * Returns true/false if the specified element matches the specified css pattern.
         *
         * @method is
         * @param {Node/NodeList} elm DOM node to match or an array of nodes to match.
         * @param {String} selector CSS pattern to match the element against.
         */
        is: function(elm, selector) {
            var i;

            // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
            if (elm.length === undefined) {
                // Simple all selector
                if (selector === '*') {
                    return elm.nodeType == 1;
                }

                // Simple selector just elements
                if (simpleSelectorRe.test(selector)) {
                    selector = selector.toLowerCase().split(/,/);
                    elm = elm.nodeName.toLowerCase();

                    for (i = selector.length - 1; i >= 0; i--) {
                        if (selector[i] == elm) {
                            return true;
                        }
                    }

                    return false;
                }
            }

            // Is non element
            if (elm.nodeType && elm.nodeType != 1) {
                return false;
            }

            var elms = elm.nodeType ? [elm] : elm;

            /*eslint new-cap:0 */
            return Sizzle(selector, elms[0].ownerDocument || elms[0], null, elms).length > 0;
        },

        // #endif

        /**
         * Adds the specified element to another element or elements.
         *
         * @method add
         * @param {String/Element/Array} parentElm Element id string, DOM node element or array of ids or elements to add to.
         * @param {String/Element} name Name of new element to add or existing element to add.
         * @param {Object} attrs Optional object collection with arguments to add to the new element(s).
         * @param {String} html Optional inner HTML contents to add for each element.
         * @return {Element/Array} Element that got created, or an array of created elements if multiple input elements
         * were passed in.
         * @example
         * // Adds a new paragraph to the end of the active editor
         * tinymce.activeEditor.dom.add(tinymce.activeEditor.getBody(), 'p', {title: 'my title'}, 'Some content');
         */
        add: function(parentElm, name, attrs, html, create) {
            var self = this;

            return this.run(parentElm, function(parentElm) {
                var newElm;

                newElm = is(name, 'string') ? self.doc.createElement(name) : name;
                self.setAttribs(newElm, attrs);

                if (html) {
                    if (html.nodeType) {
                        newElm.appendChild(html);
                    } else {
                        self.setHTML(newElm, html);
                    }
                }

                return !create ? parentElm.appendChild(newElm) : newElm;
            });
        },

        /**
         * Creates a new element.
         *
         * @method create
         * @param {String} name Name of new element.
         * @param {Object} attrs Optional object name/value collection with element attributes.
         * @param {String} html Optional HTML string to set as inner HTML of the element.
         * @return {Element} HTML DOM node element that got created.
         * @example
         * // Adds an element where the caret/selection is in the active editor
         * var el = tinymce.activeEditor.dom.create('div', {id: 'test', 'class': 'myclass'}, 'some content');
         * tinymce.activeEditor.selection.setNode(el);
         */
        create: function(name, attrs, html) {
            return this.add(this.doc.createElement(name), name, attrs, html, 1);
        },

        /**
         * Creates HTML string for element. The element will be closed unless an empty inner HTML string is passed in.
         *
         * @method createHTML
         * @param {String} name Name of new element.
         * @param {Object} attrs Optional object name/value collection with element attributes.
         * @param {String} html Optional HTML string to set as inner HTML of the element.
         * @return {String} String with new HTML element, for example: <a href="#">test</a>.
         * @example
         * // Creates a html chunk and inserts it at the current selection/caret location
         * tinymce.activeEditor.selection.setContent(tinymce.activeEditor.dom.createHTML('a', {href: 'test.html'}, 'some line'));
         */
        createHTML: function(name, attrs, html) {
            var outHtml = '', key;

            outHtml += '<' + name;

            for (key in attrs) {
                if (attrs.hasOwnProperty(key) && attrs[key] !== null && typeof attrs[key] != 'undefined') {
                    outHtml += ' ' + key + '="' + this.encode(attrs[key]) + '"';
                }
            }

            // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
            if (typeof(html) != "undefined") {
                return outHtml + '>' + html + '</' + name + '>';
            }

            return outHtml + ' />';
        },

        /**
         * Creates a document fragment out of the specified HTML string.
         *
         * @method createFragment
         * @param {String} html Html string to create fragment from.
         * @return {DocumentFragment} Document fragment node.
         */
        createFragment: function(html) {
            var frag, node, doc = this.doc, container;

            container = doc.createElement("div");
            frag = doc.createDocumentFragment();

            if (html) {
                container.innerHTML = html;
            }

            while ((node = container.firstChild)) {
                frag.appendChild(node);
            }

            return frag;
        },

        /**
         * Removes/deletes the specified element(s) from the DOM.
         *
         * @method remove
         * @param {String/Element/Array} node ID of element or DOM element object or array containing multiple elements/ids.
         * @param {Boolean} keepChildren Optional state to keep children or not. If set to true all children will be
         * placed at the location of the removed element.
         * @return {Element/Array} HTML DOM element that got removed, or an array of removed elements if multiple input elements
         * were passed in.
         * @example
         * // Removes all paragraphs in the active editor
         * tinymce.activeEditor.dom.remove(tinymce.activeEditor.dom.select('p'));
         *
         * // Removes an element by id in the document
         * tinymce.DOM.remove('mydiv');
         */
        remove: function(node, keepChildren) {
            node = this.$$(node);

            if (keepChildren) {
                node.each(function() {
                    var child;

                    while ((child = this.firstChild)) {
                        if (child.nodeType == 3 && child.data.length === 0) {
                            this.removeChild(child);
                        } else {
                            this.parentNode.insertBefore(child, this);
                        }
                    }
                }).remove();
            } else {
                node.remove();
            }

            return node.length > 1 ? node.toArray() : node[0];
        },

        /**
         * Sets the CSS style value on a HTML element. The name can be a camelcase string
         * or the CSS style name like background-color.
         *
         * @method setStyle
         * @param {String/Element/Array} n HTML element/Element ID or Array of elements/ids to set CSS style value on.
         * @param {String} na Name of the style value to set.
         * @param {String} v Value to set on the style.
         * @example
         * // Sets a style value on all paragraphs in the currently active editor
         * tinymce.activeEditor.dom.setStyle(tinymce.activeEditor.dom.select('p'), 'background-color', 'red');
         *
         * // Sets a style value to an element by id in the current document
         * tinymce.DOM.setStyle('mydiv', 'background-color', 'red');
         */
        setStyle: function(elm, name, value) {
            elm = this.$$(elm).css(name, value);

            if (this.settings.update_styles) {
                elm.attr('data-mce-style', null);
            }
        },

        /**
         * Returns the current style or runtime/computed value of an element.
         *
         * @method getStyle
         * @param {String/Element} elm HTML element or element id string to get style from.
         * @param {String} name Style name to return.
         * @param {Boolean} computed Computed style.
         * @return {String} Current style or computed style value of an element.
         */
        getStyle: function(elm, name, computed) {
            elm = this.$$(elm);

            if (computed) {
                return elm.css(name);
            }

            // Camelcase it, if needed
            name = name.replace(/-(\D)/g, function(a, b) {
                return b.toUpperCase();
            });

            if (name == 'float') {
                name = isIE ? 'styleFloat' : 'cssFloat';
            }

            return elm[0] && elm[0].style ? elm[0].style[name] : undefined;
        },

        /**
         * Sets multiple styles on the specified element(s).
         *
         * @method setStyles
         * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set styles on.
         * @param {Object} o Name/Value collection of style items to add to the element(s).
         * @example
         * // Sets styles on all paragraphs in the currently active editor
         * tinymce.activeEditor.dom.setStyles(tinymce.activeEditor.dom.select('p'), {'background-color': 'red', 'color': 'green'});
         *
         * // Sets styles to an element by id in the current document
         * tinymce.DOM.setStyles('mydiv', {'background-color': 'red', 'color': 'green'});
         */
        setStyles: function(elm, styles) {
            this.$$(elm).css(styles);
        },

        /**
         * Removes all attributes from an element or elements.
         *
         * @method removeAllAttribs
         * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to remove attributes from.
         */
        removeAllAttribs: function(e) {
            return this.run(e, function(e) {
                var i, attrs = e.attributes;
                for (i = attrs.length - 1; i >= 0; i--) {
                    e.removeAttributeNode(attrs.item(i));
                }
            });
        },

        /**
         * Sets the specified attribute of an element or elements.
         *
         * @method setAttrib
         * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attribute on.
         * @param {String} n Name of attribute to set.
         * @param {String} v Value to set on the attribute - if this value is falsy like null, 0 or '' it will remove the attribute instead.
         * @example
         * // Sets class attribute on all paragraphs in the active editor
         * tinymce.activeEditor.dom.setAttrib(tinymce.activeEditor.dom.select('p'), 'class', 'myclass');
         *
         * // Sets class attribute on a specific element in the current page
         * tinymce.dom.setAttrib('mydiv', 'class', 'myclass');
         */
        setAttrib: function(elm, name, value) {
            var self = this, originalValue, hook, settings = self.settings;

            if (value === '') {
                value = null;
            }

            elm = self.$$(elm);
            originalValue = elm.attr(name);

            hook = self.attrHooks[name];
            if (hook && hook.set) {
                hook.set(elm, value, name);
            } else {
                elm.attr(name, value);
            }

            if (originalValue != value && settings.onSetAttrib) {
                settings.onSetAttrib({
                    attrElm: elm,
                    attrName: name,
                    attrValue: value
                });
            }
        },

        /**
         * Sets two or more specified attributes of an element or elements.
         *
         * @method setAttribs
         * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set attributes on.
         * @param {Object} attrs Name/Value collection of attribute items to add to the element(s).
         * @example
         * // Sets class and title attributes on all paragraphs in the active editor
         * tinymce.activeEditor.dom.setAttribs(tinymce.activeEditor.dom.select('p'), {'class': 'myclass', title: 'some title'});
         *
         * // Sets class and title attributes on a specific element in the current page
         * tinymce.DOM.setAttribs('mydiv', {'class': 'myclass', title: 'some title'});
         */
        setAttribs: function(elm, attrs) {
            var self = this;

            self.$$(elm).each(function(i, node) {
                each(attrs, function(value, name) {
                    self.setAttrib(node, name, value);
                });
            });
        },

        /**
         * Returns the specified attribute by name.
         *
         * @method getAttrib
         * @param {String/Element} elm Element string id or DOM element to get attribute from.
         * @param {String} name Name of attribute to get.
         * @param {String} defaultVal Optional default value to return if the attribute didn't exist.
         * @return {String} Attribute value string, default value or null if the attribute wasn't found.
         */
        getAttrib: function(elm, name, defaultVal) {
            var self = this, hook, value;

            elm = self.$$(elm);

            hook = self.attrHooks[name];
            if (hook && hook.get) {
                value = hook.get(elm, name);
            } else {
                value = elm.attr(name);
            }

            if (typeof value == 'undefined') {
                value = defaultVal || '';
            }

            return value;
        },

        /**
         * Returns the absolute x, y position of a node. The position will be returned in an object with x, y fields.
         *
         * @method getPos
         * @param {Element/String} elm HTML element or element id to get x, y position from.
         * @param {Element} rootElm Optional root element to stop calculations at.
         * @return {object} Absolute position of the specified element object with x, y fields.
         */
        getPos: function(elm, rootElm) {
            var self = this, x = 0, y = 0, offsetParent, doc = self.doc, pos;

            elm = self.get(elm);
            rootElm = rootElm || doc.body;

            if (elm) {
                // Use getBoundingClientRect if it exists since it's faster than looping offset nodes
                if (rootElm === doc.body && elm.getBoundingClientRect) {
                    pos = elm.getBoundingClientRect();
                    rootElm = self.boxModel ? doc.documentElement : doc.body;

                    // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
                    // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
                    x = pos.left + (doc.documentElement.scrollLeft || doc.body.scrollLeft) - rootElm.clientLeft;
                    y = pos.top + (doc.documentElement.scrollTop || doc.body.scrollTop) - rootElm.clientTop;

                    return {x: x, y: y};
                }

                offsetParent = elm;
                while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
                    x += offsetParent.offsetLeft || 0;
                    y += offsetParent.offsetTop || 0;
                    offsetParent = offsetParent.offsetParent;
                }

                offsetParent = elm.parentNode;
                while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
                    x -= offsetParent.scrollLeft || 0;
                    y -= offsetParent.scrollTop || 0;
                    offsetParent = offsetParent.parentNode;
                }
            }

            return {x: x, y: y};
        },

        /**
         * Parses the specified style value into an object collection. This parser will also
         * merge and remove any redundant items that browsers might have added. It will also convert non-hex
         * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
         *
         * @method parseStyle
         * @param {String} cssText Style value to parse, for example: border:1px solid red;.
         * @return {Object} Object representation of that style, for example: {border: '1px solid red'}
         */
        parseStyle: function(cssText) {
            return this.styles.parse(cssText);
        },

        /**
         * Serializes the specified style object into a string.
         *
         * @method serializeStyle
         * @param {Object} styles Object to serialize as string, for example: {border: '1px solid red'}
         * @param {String} name Optional element name.
         * @return {String} String representation of the style object, for example: border: 1px solid red.
         */
        serializeStyle: function(styles, name) {
            return this.styles.serialize(styles, name);
        },

        /**
         * Adds a style element at the top of the document with the specified cssText content.
         *
         * @method addStyle
         * @param {String} cssText CSS Text style to add to top of head of document.
         */
        addStyle: function(cssText) {
            var self = this, doc = self.doc, head, styleElm;

            // Prevent inline from loading the same styles twice
            if (self !== DOMUtils.DOM && doc === document) {
                var addedStyles = DOMUtils.DOM.addedStyles;

                addedStyles = addedStyles || [];
                if (addedStyles[cssText]) {
                    return;
                }

                addedStyles[cssText] = true;
                DOMUtils.DOM.addedStyles = addedStyles;
            }

            // Create style element if needed
            styleElm = doc.getElementById('mceDefaultStyles');
            if (!styleElm) {
                styleElm = doc.createElement('style');
                styleElm.id = 'mceDefaultStyles';
                styleElm.type = 'text/css';

                head = doc.getElementsByTagName('head')[0];
                if (head.firstChild) {
                    head.insertBefore(styleElm, head.firstChild);
                } else {
                    head.appendChild(styleElm);
                }
            }

            // Append style data to old or new style element
            if (styleElm.styleSheet) {
                styleElm.styleSheet.cssText += cssText;
            } else {
                styleElm.appendChild(doc.createTextNode(cssText));
            }
        },

        /**
         * Imports/loads the specified CSS file into the document bound to the class.
         *
         * @method loadCSS
         * @param {String} u URL to CSS file to load.
         * @example
         * // Loads a CSS file dynamically into the current document
         * tinymce.DOM.loadCSS('somepath/some.css');
         *
         * // Loads a CSS file into the currently active editor instance
         * tinymce.activeEditor.dom.loadCSS('somepath/some.css');
         *
         * // Loads a CSS file into an editor instance by id
         * tinymce.get('someid').dom.loadCSS('somepath/some.css');
         *
         * // Loads multiple CSS files into the current document
         * tinymce.DOM.loadCSS('somepath/some.css,somepath/someother.css');
         */
        loadCSS: function(url) {
            var self = this, doc = self.doc, head;

            // Prevent inline from loading the same CSS file twice
            if (self !== DOMUtils.DOM && doc === document) {
                DOMUtils.DOM.loadCSS(url);
                return;
            }

            if (!url) {
                url = '';
            }

            head = doc.getElementsByTagName('head')[0];

            each(url.split(','), function(url) {
                var link;

                if (self.files[url]) {
                    return;
                }

                self.files[url] = true;
                link = self.create('link', {rel: 'stylesheet', href: url});

                // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
                // This fix seems to resolve that issue by recalcing the document once a stylesheet finishes loading
                // It's ugly but it seems to work fine.
                if (isIE && doc.documentMode && doc.recalc) {
                    link.onload = function() {
                        if (doc.recalc) {
                            doc.recalc();
                        }

                        link.onload = null;
                    };
                }

                head.appendChild(link);
            });
        },

        /**
         * Adds a class to the specified element or elements.
         *
         * @method addClass
         * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs.
         * @param {String} cls Class name to add to each element.
         * @return {String/Array} String with new class value or array with new class values for all elements.
         * @example
         * // Adds a class to all paragraphs in the active editor
         * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'myclass');
         *
         * // Adds a class to a specific element in the current page
         * tinymce.DOM.addClass('mydiv', 'myclass');
         */
        addClass: function(elm, cls) {
            this.$$(elm).addClass(cls);
        },

        /**
         * Removes a class from the specified element or elements.
         *
         * @method removeClass
         * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs.
         * @param {String} cls Class name to remove from each element.
         * @return {String/Array} String of remaining class name(s), or an array of strings if multiple input elements
         * were passed in.
         * @example
         * // Removes a class from all paragraphs in the active editor
         * tinymce.activeEditor.dom.removeClass(tinymce.activeEditor.dom.select('p'), 'myclass');
         *
         * // Removes a class from a specific element in the current page
         * tinymce.DOM.removeClass('mydiv', 'myclass');
         */
        removeClass: function(elm, cls) {
            this.toggleClass(elm, cls, false);
        },

        /**
         * Returns true if the specified element has the specified class.
         *
         * @method hasClass
         * @param {String/Element} n HTML element or element id string to check CSS class on.
         * @param {String} c CSS class to check for.
         * @return {Boolean} true/false if the specified element has the specified class.
         */
        hasClass: function(elm, cls) {
            return this.$$(elm).hasClass(cls);
        },

        /**
         * Toggles the specified class on/off.
         *
         * @method toggleClass
         * @param {Element} elm Element to toggle class on.
         * @param {[type]} cls Class to toggle on/off.
         * @param {[type]} state Optional state to set.
         */
        toggleClass: function(elm, cls, state) {
            this.$$(elm).toggleClass(cls, state).each(function() {
                if (this.className === '') {
                    $(this).attr('class', null);
                }
            });
        },

        /**
         * Shows the specified element(s) by ID by setting the "display" style.
         *
         * @method show
         * @param {String/Element/Array} elm ID of DOM element or DOM element or array with elements or IDs to show.
         */
        show: function(elm) {
            this.$$(elm).show();
        },

        /**
         * Hides the specified element(s) by ID by setting the "display" style.
         *
         * @method hide
         * @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to hide.
         * @example
         * // Hides an element by id in the document
         * tinymce.DOM.hide('myid');
         */
        hide: function(elm) {
            this.$$(elm).hide();
        },

        /**
         * Returns true/false if the element is hidden or not by checking the "display" style.
         *
         * @method isHidden
         * @param {String/Element} e Id or element to check display state on.
         * @return {Boolean} true/false if the element is hidden or not.
         */
        isHidden: function(elm) {
            return this.$$(elm).css('display') == 'none';
        },

        /**
         * Returns a unique id. This can be useful when generating elements on the fly.
         * This method will not check if the element already exists.
         *
         * @method uniqueId
         * @param {String} prefix Optional prefix to add in front of all ids - defaults to "mce_".
         * @return {String} Unique id.
         */
        uniqueId: function(prefix) {
            return (!prefix ? 'mce_' : prefix) + (this.counter++);
        },

        /**
         * Sets the specified HTML content inside the element or elements. The HTML will first be processed. This means
         * URLs will get converted, hex color values fixed etc. Check processHTML for details.
         *
         * @method setHTML
         * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set HTML inside of.
         * @param {String} h HTML content to set as inner HTML of the element.
         * @example
         * // Sets the inner HTML of all paragraphs in the active editor
         * tinymce.activeEditor.dom.setHTML(tinymce.activeEditor.dom.select('p'), 'some inner html');
         *
         * // Sets the inner HTML of an element by id in the document
         * tinymce.DOM.setHTML('mydiv', 'some inner html');
         */
        setHTML: function(elm, html) {
            elm = this.$$(elm);

            if (isIE) {
                elm.each(function(i, target) {
                    if (target.canHaveHTML === false) {
                        return;
                    }

                    // Remove all child nodes, IE keeps empty text nodes in DOM
                    while (target.firstChild) {
                        target.removeChild(target.firstChild);
                    }

                    try {
                        // IE will remove comments from the beginning
                        // unless you padd the contents with something
                        target.innerHTML = '<br>' + html;
                        target.removeChild(target.firstChild);
                    } catch (ex) {
                        // IE sometimes produces an unknown runtime error on innerHTML if it's a div inside a p
                        $('<div>').html('<br>' + html).contents().slice(1).appendTo(target);
                    }

                    return html;
                });
            } else {
                elm.html(html);
            }
        },

        /**
         * Returns the outer HTML of an element.
         *
         * @method getOuterHTML
         * @param {String/Element} elm Element ID or element object to get outer HTML from.
         * @return {String} Outer HTML string.
         * @example
         * tinymce.DOM.getOuterHTML(editorElement);
         * tinymce.activeEditor.getOuterHTML(tinymce.activeEditor.getBody());
         */
        getOuterHTML: function(elm) {
            elm = this.get(elm);
            return elm.nodeType == 1 ? elm.outerHTML : $('<div>').append($(elm).clone()).html();
        },

        /**
         * Sets the specified outer HTML on an element or elements.
         *
         * @method setOuterHTML
         * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set outer HTML on.
         * @param {Object} html HTML code to set as outer value for the element.
         * @param {Document} doc Optional document scope to use in this process - defaults to the document of the DOM class.
         * @example
         * // Sets the outer HTML of all paragraphs in the active editor
         * tinymce.activeEditor.dom.setOuterHTML(tinymce.activeEditor.dom.select('p'), '<div>some html</div>');
         *
         * // Sets the outer HTML of an element by id in the document
         * tinymce.DOM.setOuterHTML('mydiv', '<div>some html</div>');
         */
        setOuterHTML: function(elm, html) {
            var self = this;

            self.$$(elm).each(function() {
                try {
                    this.outerHTML = html;
                } catch (ex) {
                    // OuterHTML for IE it sometimes produces an "unknown runtime error"
                    self.remove($(this).html(html), true);
                }
            });
        },

        /**
         * Entity decodes a string. This method decodes any HTML entities, such as &aring;.
         *
         * @method decode
         * @param {String} s String to decode entities on.
         * @return {String} Entity decoded string.
         */
        decode: Entities.decode,

        /**
         * Entity encodes a string. This method encodes the most common entities, such as <>"&.
         *
         * @method encode
         * @param {String} text String to encode with entities.
         * @return {String} Entity encoded string.
         */
        encode: Entities.encodeAllRaw,

        /**
         * Inserts an element after the reference element.
         *
         * @method insertAfter
         * @param {Element} node Element to insert after the reference.
         * @param {Element/String/Array} reference_node Reference element, element id or array of elements to insert after.
         * @return {Element/Array} Element that got added or an array with elements.
         */
        insertAfter: function(node, referenceNode) {
            referenceNode = this.get(referenceNode);

            return this.run(node, function(node) {
                var parent, nextSibling;

                parent = referenceNode.parentNode;
                nextSibling = referenceNode.nextSibling;

                if (nextSibling) {
                    parent.insertBefore(node, nextSibling);
                } else {
                    parent.appendChild(node);
                }

                return node;
            });
        },

        /**
         * Replaces the specified element or elements with the new element specified. The new element will
         * be cloned if multiple input elements are passed in.
         *
         * @method replace
         * @param {Element} newElm New element to replace old ones with.
         * @param {Element/String/Array} oldELm Element DOM node, element id or array of elements or ids to replace.
         * @param {Boolean} k Optional keep children state, if set to true child nodes from the old object will be added to new ones.
         */
        replace: function(newElm, oldElm, keepChildren) {
            var self = this;

            return self.run(oldElm, function(oldElm) {
                if (is(oldElm, 'array')) {
                    newElm = newElm.cloneNode(true);
                }

                if (keepChildren) {
                    each(grep(oldElm.childNodes), function(node) {
                        newElm.appendChild(node);
                    });
                }

                return oldElm.parentNode.replaceChild(newElm, oldElm);
            });
        },

        /**
         * Renames the specified element and keeps its attributes and children.
         *
         * @method rename
         * @param {Element} elm Element to rename.
         * @param {String} name Name of the new element.
         * @return {Element} New element or the old element if it needed renaming.
         */
        rename: function(elm, name) {
            var self = this, newElm;

            if (elm.nodeName != name.toUpperCase()) {
                // Rename block element
                newElm = self.create(name);

                // Copy attribs to new block
                each(self.getAttribs(elm), function(attrNode) {
                    self.setAttrib(newElm, attrNode.nodeName, self.getAttrib(elm, attrNode.nodeName));
                });

                // Replace block
                self.replace(newElm, elm, 1);
            }

            return newElm || elm;
        },

        /**
         * Find the common ancestor of two elements. This is a shorter method than using the DOM Range logic.
         *
         * @method findCommonAncestor
         * @param {Element} a Element to find common ancestor of.
         * @param {Element} b Element to find common ancestor of.
         * @return {Element} Common ancestor element of the two input elements.
         */
        findCommonAncestor: function(a, b) {
            var ps = a, pe;

            while (ps) {
                pe = b;

                while (pe && ps != pe) {
                    pe = pe.parentNode;
                }

                if (ps == pe) {
                    break;
                }

                ps = ps.parentNode;
            }

            if (!ps && a.ownerDocument) {
                return a.ownerDocument.documentElement;
            }

            return ps;
        },

        /**
         * Parses the specified RGB color value and returns a hex version of that color.
         *
         * @method toHex
         * @param {String} rgbVal RGB string value like rgb(1,2,3)
         * @return {String} Hex version of that RGB value like #FF00FF.
         */
        toHex: function(rgbVal) {
            return this.styles.toHex(Tools.trim(rgbVal));
        },

        /**
         * Executes the specified function on the element by id or dom element node or array of elements/id.
         *
         * @method run
         * @param {String/Element/Array} Element ID or DOM element object or array with ids or elements.
         * @param {function} f Function to execute for each item.
         * @param {Object} s Optional scope to execute the function in.
         * @return {Object/Array} Single object, or an array of objects if multiple input elements were passed in.
         */
        run: function(elm, func, scope) {
            var self = this, result;

            if (typeof(elm) === 'string') {
                elm = self.get(elm);
            }

            if (!elm) {
                return false;
            }

            scope = scope || this;
            if (!elm.nodeType && (elm.length || elm.length === 0)) {
                result = [];

                each(elm, function(elm, i) {
                    if (elm) {
                        if (typeof(elm) == 'string') {
                            elm = self.get(elm);
                        }

                        result.push(func.call(scope, elm, i));
                    }
                });

                return result;
            }

            return func.call(scope, elm);
        },

        /**
         * Returns a NodeList with attributes for the element.
         *
         * @method getAttribs
         * @param {HTMLElement/string} elm Element node or string id to get attributes from.
         * @return {NodeList} NodeList with attributes.
         */
        getAttribs: function(elm) {
            var attrs;

            elm = this.get(elm);

            if (!elm) {
                return [];
            }

            if (isIE) {
                attrs = [];

                // Object will throw exception in IE
                if (elm.nodeName == 'OBJECT') {
                    return elm.attributes;
                }

                // IE doesn't keep the selected attribute if you clone option elements
                if (elm.nodeName === 'OPTION' && this.getAttrib(elm, 'selected')) {
                    attrs.push({specified: 1, nodeName: 'selected'});
                }

                // It's crazy that this is faster in IE but it's because it returns all attributes all the time
                var attrRegExp = /<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi;
                elm.cloneNode(false).outerHTML.replace(attrRegExp, '').replace(/[\w:\-]+/gi, function(a) {
                    attrs.push({specified: 1, nodeName: a});
                });

                return attrs;
            }

            return elm.attributes;
        },

        /**
         * Returns true/false if the specified node is to be considered empty or not.
         *
         * @example
         * tinymce.DOM.isEmpty(node, {img: true});
         * @method isEmpty
         * @param {Object} elements Optional name/value object with elements that are automatically treated as non-empty elements.
         * @return {Boolean} true/false if the node is empty or not.
         */
        isEmpty: function(node, elements) {
            var self = this, i, attributes, type, walker, name, brCount = 0;

            node = node.firstChild;
            if (node) {
                walker = new TreeWalker(node, node.parentNode);
                elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;

                do {
                    type = node.nodeType;

                    if (type === 1) {
                        // Ignore bogus elements
                        if (node.getAttribute('data-mce-bogus')) {
                            continue;
                        }

                        // Keep empty elements like <img />
                        name = node.nodeName.toLowerCase();
                        if (elements && elements[name]) {
                            // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
                            if (name === 'br') {
                                brCount++;
                                continue;
                            }

                            return false;
                        }

                        // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
                        attributes = self.getAttribs(node);
                        i = attributes.length;
                        while (i--) {
                            name = attributes[i].nodeName;
                            if (name === "name" || name === 'data-mce-bookmark') {
                                return false;
                            }
                        }
                    }

                    // Keep comment nodes
                    if (type == 8) {
                        return false;
                    }

                    // Keep non whitespace text nodes
                    if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) {
                        return false;
                    }
                } while ((node = walker.next()));
            }

            return brCount <= 1;
        },

        /**
         * Creates a new DOM Range object. This will use the native DOM Range API if it's
         * available. If it's not, it will fall back to the custom TinyMCE implementation.
         *
         * @method createRng
         * @return {DOMRange} DOM Range object.
         * @example
         * var rng = tinymce.DOM.createRng();
         * alert(rng.startContainer + "," + rng.startOffset);
         */
        createRng: function() {
            var doc = this.doc;

            return doc.createRange ? doc.createRange() : new Range(this);
        },

        /**
         * Returns the index of the specified node within its parent.
         *
         * @method nodeIndex
         * @param {Node} node Node to look for.
         * @param {boolean} normalized Optional true/false state if the index is what it would be after a normalization.
         * @return {Number} Index of the specified node.
         */
        nodeIndex: function(node, normalized) {
            var idx = 0, lastNodeType, nodeType;

            if (node) {
                for (lastNodeType = node.nodeType, node = node.previousSibling; node; node = node.previousSibling) {
                    nodeType = node.nodeType;

                    // Normalize text nodes
                    if (normalized && nodeType == 3) {
                        if (nodeType == lastNodeType || !node.nodeValue.length) {
                            continue;
                        }
                    }
                    idx++;
                    lastNodeType = nodeType;
                }
            }

            return idx;
        },

        /**
         * Splits an element into two new elements and places the specified split
         * element or elements between the new ones. For example splitting the paragraph at the bold element in
         * this example <p>abc<b>abc</b>123</p> would produce <p>abc</p><b>abc</b><p>123</p>.
         *
         * @method split
         * @param {Element} parentElm Parent element to split.
         * @param {Element} splitElm Element to split at.
         * @param {Element} replacementElm Optional replacement element to replace the split element with.
         * @return {Element} Returns the split element or the replacement element if that is specified.
         */
        split: function(parentElm, splitElm, replacementElm) {
            var self = this, r = self.createRng(), bef, aft, pa;

            // W3C valid browsers tend to leave empty nodes to the left/right side of the contents - this makes sense
            // but we don't want that in our code since it serves no purpose for the end user
            // For example splitting this html at the bold element:
            //   <p>text 1<span><b>CHOP</b></span>text 2</p>
            // would produce:
            //   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
            // this function will then trim off empty edges and produce:
            //   <p>text 1</p><b>CHOP</b><p>text 2</p>
            function trimNode(node) {
                var i, children = node.childNodes, type = node.nodeType;

                function surroundedBySpans(node) {
                    var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
                    var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
                    return previousIsSpan && nextIsSpan;
                }

                if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') {
                    return;
                }

                for (i = children.length - 1; i >= 0; i--) {
                    trimNode(children[i]);
                }

                if (type != 9) {
                    // Keep non whitespace text nodes
                    if (type == 3 && node.nodeValue.length > 0) {
                        // If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
                        // Also keep text nodes with only spaces if surrounded by spans.
                        // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
                        var trimmedLength = trim(node.nodeValue).length;
                        if (!self.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) {
                            return;
                        }
                    } else if (type == 1) {
                        // If the only child is a bookmark then move it up
                        children = node.childNodes;

                        // TODO fix this complex if
                        if (children.length == 1 && children[0] && children[0].nodeType == 1 &&
                            children[0].getAttribute('data-mce-type') == 'bookmark') {
                            node.parentNode.insertBefore(children[0], node);
                        }

                        // Keep non empty elements or img, hr etc
                        if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) {
                            return;
                        }
                    }

                    self.remove(node);
                }

                return node;
            }

            if (parentElm && splitElm) {
                // Get before chunk
                r.setStart(parentElm.parentNode, self.nodeIndex(parentElm));
                r.setEnd(splitElm.parentNode, self.nodeIndex(splitElm));
                bef = r.extractContents();

                // Get after chunk
                r = self.createRng();
                r.setStart(splitElm.parentNode, self.nodeIndex(splitElm) + 1);
                r.setEnd(parentElm.parentNode, self.nodeIndex(parentElm) + 1);
                aft = r.extractContents();

                // Insert before chunk
                pa = parentElm.parentNode;
                pa.insertBefore(trimNode(bef), parentElm);

                // Insert middle chunk
                if (replacementElm) {
                    pa.replaceChild(replacementElm, splitElm);
                } else {
                    pa.insertBefore(splitElm, parentElm);
                }

                // Insert after chunk
                pa.insertBefore(trimNode(aft), parentElm);
                self.remove(parentElm);

                return replacementElm || splitElm;
            }
        },

        /**
         * Adds an event handler to the specified object.
         *
         * @method bind
         * @param {Element/Document/Window/Array} target Target element to bind events to.
         * handler to or an array of elements/ids/documents.
         * @param {String} name Name of event handler to add, for example: click.
         * @param {function} func Function to execute when the event occurs.
         * @param {Object} scope Optional scope to execute the function in.
         * @return {function} Function callback handler the same as the one passed in.
         */
        bind: function(target, name, func, scope) {
            var self = this;

            if (Tools.isArray(target)) {
                var i = target.length;

                while (i--) {
                    target[i] = self.bind(target[i], name, func, scope);
                }

                return target;
            }

            // Collect all window/document events bound by editor instance
            if (self.settings.collect && (target === self.doc || target === self.win)) {
                self.boundEvents.push([target, name, func, scope]);
            }

            return self.events.bind(target, name, func, scope || self);
        },

        /**
         * Removes the specified event handler by name and function from an element or collection of elements.
         *
         * @method unbind
         * @param {Element/Document/Window/Array} target Target element to unbind events on.
         * @param {String} name Event handler name, for example: "click"
         * @param {function} func Function to remove.
         * @return {bool/Array} Bool state of true if the handler was removed, or an array of states if multiple input elements
         * were passed in.
         */
        unbind: function(target, name, func) {
            var self = this, i;

            if (Tools.isArray(target)) {
                i = target.length;

                while (i--) {
                    target[i] = self.unbind(target[i], name, func);
                }

                return target;
            }

            // Remove any bound events matching the input
            if (self.boundEvents && (target === self.doc || target === self.win)) {
                i = self.boundEvents.length;

                while (i--) {
                    var item = self.boundEvents[i];

                    if (target == item[0] && (!name || name == item[1]) && (!func || func == item[2])) {
                        this.events.unbind(item[0], item[1], item[2]);
                    }
                }
            }

            return this.events.unbind(target, name, func);
        },

        /**
         * Fires the specified event name with object on target.
         *
         * @method fire
         * @param {Node/Document/Window} target Target element or object to fire event on.
         * @param {String} name Name of the event to fire.
         * @param {Object} evt Event object to send.
         * @return {Event} Event object.
         */
        fire: function(target, name, evt) {
            return this.events.fire(target, name, evt);
        },

        // Returns the content editable state of a node
        getContentEditable: function(node) {
            var contentEditable;

            // Check type
            if (!node || node.nodeType != 1) {
                return null;
            }

            // Check for fake content editable
            contentEditable = node.getAttribute("data-mce-contenteditable");
            if (contentEditable && contentEditable !== "inherit") {
                return contentEditable;
            }

            // Check for real content editable
            return node.contentEditable !== "inherit" ? node.contentEditable : null;
        },

        getContentEditableParent: function(node) {
            var root = this.getRoot(), state = null;

            for (; node && node !== root; node = node.parentNode) {
                state = this.getContentEditable(node);

                if (state !== null) {
                    break;
                }
            }

            return state;
        },

        /**
         * Destroys all internal references to the DOM to solve IE leak issues.
         *
         * @method destroy
         */
        destroy: function() {
            var self = this;

            // Unbind all events bound to window/document by editor instance
            if (self.boundEvents) {
                var i = self.boundEvents.length;

                while (i--) {
                    var item = self.boundEvents[i];
                    this.events.unbind(item[0], item[1], item[2]);
                }

                self.boundEvents = null;
            }

            // Restore sizzle document to window.document
            // Since the current document might be removed producing "Permission denied" on IE see #6325
            if (Sizzle.setDocument) {
                Sizzle.setDocument();
            }

            self.win = self.doc = self.root = self.events = self.frag = null;
        },

        isChildOf: function(node, parent) {
            while (node) {
                if (parent === node) {
                    return true;
                }

                node = node.parentNode;
            }

            return false;
        },

        // #ifdef debug

        dumpRng: function(r) {
            return (
                'startContainer: ' + r.startContainer.nodeName +
                ', startOffset: ' + r.startOffset +
                ', endContainer: ' + r.endContainer.nodeName +
                ', endOffset: ' + r.endOffset
            );
        },

        // #endif

        _findSib: function(node, selector, name) {
            var self = this, func = selector;

            if (node) {
                // If expression make a function of it using is
                if (typeof(func) == 'string') {
                    func = function(node) {
                        return self.is(node, selector);
                    };
                }

                // Loop all siblings
                for (node = node[name]; node; node = node[name]) {
                    if (func(node)) {
                        return node;
                    }
                }
            }

            return null;
        }
    };

    /**
     * Instance of DOMUtils for the current document.
     *
     * @static
     * @property DOM
     * @type tinymce.dom.DOMUtils
     * @example
     * // Example of how to add a class to some element by id
     * tinymce.DOM.addClass('someid', 'someclass');
     */
    DOMUtils.DOM = new DOMUtils(document);

    return DOMUtils;
});

// Included from: js/tinymce/classes/dom/ScriptLoader.js

/**
 * ScriptLoader.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/*globals console*/

/**
 * This class handles asynchronous/synchronous loading of JavaScript files it will execute callbacks
 * when various items gets loaded. This class is useful to load external JavaScript files.
 *
 * @class tinymce.dom.ScriptLoader
 * @example
 * // Load a script from a specific URL using the global script loader
 * tinymce.ScriptLoader.load('somescript.js');
 *
 * // Load a script using a unique instance of the script loader
 * var scriptLoader = new tinymce.dom.ScriptLoader();
 *
 * scriptLoader.load('somescript.js');
 *
 * // Load multiple scripts
 * var scriptLoader = new tinymce.dom.ScriptLoader();
 *
 * scriptLoader.add('somescript1.js');
 * scriptLoader.add('somescript2.js');
 * scriptLoader.add('somescript3.js');
 *
 * scriptLoader.loadQueue(function() {
 *    alert('All scripts are now loaded.');
 * });
 */
define("tinymce/dom/ScriptLoader", [
    "tinymce/dom/DOMUtils",
    "tinymce/util/Tools"
], function(DOMUtils, Tools) {
    var DOM = DOMUtils.DOM;
    var each = Tools.each, grep = Tools.grep;

    function ScriptLoader() {
        var QUEUED = 0,
            LOADING = 1,
            LOADED = 2,
            states = {},
            queue = [],
            scriptLoadedCallbacks = {},
            queueLoadedCallbacks = [],
            loading = 0,
            undef;

        /**
         * Loads a specific script directly without adding it to the load queue.
         *
         * @method load
         * @param {String} url Absolute URL to script to add.
         * @param {function} callback Optional callback function to execute ones this script gets loaded.
         * @param {Object} scope Optional scope to execute callback in.
         */
        function loadScript(url, callback) {
            var dom = DOM, elm, id;

            // Execute callback when script is loaded
            function done() {
                dom.remove(id);

                if (elm) {
                    elm.onreadystatechange = elm.onload = elm = null;
                }

                callback();
            }

            function error() {
                /*eslint no-console:0 */

                // Report the error so it's easier for people to spot loading errors
                if (typeof(console) !== "undefined" && console.log) {
                    console.log("Failed to load: " + url);
                }

                // We can't mark it as done if there is a load error since
                // A) We don't want to produce 404 errors on the server and
                // B) the onerror event won't fire on all browsers.
                // done();
            }

            id = dom.uniqueId();

            // Create new script element
            elm = document.createElement('script');
            elm.id = id;
            elm.type = 'text/javascript';
            elm.src = url;

            // Seems that onreadystatechange works better on IE 10 onload seems to fire incorrectly
            if ("onreadystatechange" in elm) {
                elm.onreadystatechange = function() {
                    if (/loaded|complete/.test(elm.readyState)) {
                        done();
                    }
                };
            } else {
                elm.onload = done;
            }

            // Add onerror event will get fired on some browsers but not all of them
            elm.onerror = error;

            // Add script to document
            (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
        }

        /**
         * Returns true/false if a script has been loaded or not.
         *
         * @method isDone
         * @param {String} url URL to check for.
         * @return {Boolean} true/false if the URL is loaded.
         */
        this.isDone = function(url) {
            return states[url] == LOADED;
        };

        /**
         * Marks a specific script to be loaded. This can be useful if a script got loaded outside
         * the script loader or to skip it from loading some script.
         *
         * @method markDone
         * @param {string} u Absolute URL to the script to mark as loaded.
         */
        this.markDone = function(url) {
            states[url] = LOADED;
        };

        /**
         * Adds a specific script to the load queue of the script loader.
         *
         * @method add
         * @param {String} url Absolute URL to script to add.
         * @param {function} callback Optional callback function to execute ones this script gets loaded.
         * @param {Object} scope Optional scope to execute callback in.
         */
        this.add = this.load = function(url, callback, scope) {
            var state = states[url];

            // Add url to load queue
            if (state == undef) {
                queue.push(url);
                states[url] = QUEUED;
            }

            if (callback) {
                // Store away callback for later execution
                if (!scriptLoadedCallbacks[url]) {
                    scriptLoadedCallbacks[url] = [];
                }

                scriptLoadedCallbacks[url].push({
                    func: callback,
                    scope: scope || this
                });
            }
        };

        /**
         * Starts the loading of the queue.
         *
         * @method loadQueue
         * @param {function} callback Optional callback to execute when all queued items are loaded.
         * @param {Object} scope Optional scope to execute the callback in.
         */
        this.loadQueue = function(callback, scope) {
            this.loadScripts(queue, callback, scope);
        };

        /**
         * Loads the specified queue of files and executes the callback ones they are loaded.
         * This method is generally not used outside this class but it might be useful in some scenarios.
         *
         * @method loadScripts
         * @param {Array} scripts Array of queue items to load.
         * @param {function} callback Optional callback to execute ones all items are loaded.
         * @param {Object} scope Optional scope to execute callback in.
         */
        this.loadScripts = function(scripts, callback, scope) {
            var loadScripts;

            function execScriptLoadedCallbacks(url) {
                // Execute URL callback functions
                each(scriptLoadedCallbacks[url], function(callback) {
                    callback.func.call(callback.scope);
                });

                scriptLoadedCallbacks[url] = undef;
            }

            queueLoadedCallbacks.push({
                func: callback,
                scope: scope || this
            });

            loadScripts = function() {
                var loadingScripts = grep(scripts);

                // Current scripts has been handled
                scripts.length = 0;

                // Load scripts that needs to be loaded
                each(loadingScripts, function(url) {
                    // Script is already loaded then execute script callbacks directly
                    if (states[url] == LOADED) {
                        execScriptLoadedCallbacks(url);
                        return;
                    }

                    // Is script not loading then start loading it
                    if (states[url] != LOADING) {
                        states[url] = LOADING;
                        loading++;

                        loadScript(url, function() {
                            states[url] = LOADED;
                            loading--;

                            execScriptLoadedCallbacks(url);

                            // Load more scripts if they where added by the recently loaded script
                            loadScripts();
                        });
                    }
                });

                // No scripts are currently loading then execute all pending queue loaded callbacks
                if (!loading) {
                    each(queueLoadedCallbacks, function(callback) {
                        callback.func.call(callback.scope);
                    });

                    queueLoadedCallbacks.length = 0;
                }
            };

            loadScripts();
        };
    }

    ScriptLoader.ScriptLoader = new ScriptLoader();

    return ScriptLoader;
});

// Included from: js/tinymce/classes/AddOnManager.js

/**
 * AddOnManager.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class handles the loading of themes/plugins or other add-ons and their language packs.
 *
 * @class tinymce.AddOnManager
 */
define("tinymce/AddOnManager", [
    "tinymce/dom/ScriptLoader",
    "tinymce/util/Tools"
], function(ScriptLoader, Tools) {
    var each = Tools.each;

    function AddOnManager() {
        var self = this;

        self.items = [];
        self.urls = {};
        self.lookup = {};
    }

    AddOnManager.prototype = {
        /**
         * Returns the specified add on by the short name.
         *
         * @method get
         * @param {String} name Add-on to look for.
         * @return {tinymce.Theme/tinymce.Plugin} Theme or plugin add-on instance or undefined.
         */
        get: function(name) {
            if (this.lookup[name]) {
                return this.lookup[name].instance;
            } else {
                return undefined;
            }
        },

        dependencies: function(name) {
            var result;

            if (this.lookup[name]) {
                result = this.lookup[name].dependencies;
            }

            return result || [];
        },

        /**
         * Loads a language pack for the specified add-on.
         *
         * @method requireLangPack
         * @param {String} name Short name of the add-on.
         * @param {String} languages Optional comma or space separated list of languages to check if it matches the name.
         */
        requireLangPack: function(name, languages) {
            var language = AddOnManager.language;

            if (language && AddOnManager.languageLoad !== false) {
                if (languages) {
                    languages = ',' + languages + ',';

                    // Load short form sv.js or long form sv_SE.js
                    if (languages.indexOf(',' + language.substr(0, 2) + ',') != -1) {
                        language = language.substr(0, 2);
                    } else if (languages.indexOf(',' + language + ',') == -1) {
                        return;
                    }
                }

                ScriptLoader.ScriptLoader.add(this.urls[name] + '/langs/' + language + '.js');
            }
        },

        /**
         * Adds a instance of the add-on by it's short name.
         *
         * @method add
         * @param {String} id Short name/id for the add-on.
         * @param {tinymce.Theme/tinymce.Plugin} addOn Theme or plugin to add.
         * @return {tinymce.Theme/tinymce.Plugin} The same theme or plugin instance that got passed in.
         * @example
         * // Create a simple plugin
         * tinymce.create('tinymce.plugins.TestPlugin', {
         *   TestPlugin: function(ed, url) {
         *   ed.on('click', function(e) {
         *      ed.windowManager.alert('Hello World!');
         *   });
         *   }
         * });
         *
         * // Register plugin using the add method
         * tinymce.PluginManager.add('test', tinymce.plugins.TestPlugin);
         *
         * // Initialize TinyMCE
         * tinymce.init({
         *  ...
         *  plugins: '-test' // Init the plugin but don't try to load it
         * });
         */
        add: function(id, addOn, dependencies) {
            this.items.push(addOn);
            this.lookup[id] = {instance: addOn, dependencies: dependencies};

            return addOn;
        },

        createUrl: function(baseUrl, dep) {
            if (typeof dep === "object") {
                return dep;
            } else {
                return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
            }
        },

        /**
         * Add a set of components that will make up the add-on. Using the url of the add-on name as the base url.
         * This should be used in development mode.  A new compressor/javascript munger process will ensure that the
         * components are put together into the plugin.js file and compressed correctly.
         *
         * @method addComponents
         * @param {String} pluginName name of the plugin to load scripts from (will be used to get the base url for the plugins).
         * @param {Array} scripts Array containing the names of the scripts to load.
         */
        addComponents: function(pluginName, scripts) {
            var pluginUrl = this.urls[pluginName];

            each(scripts, function(script) {
                ScriptLoader.ScriptLoader.add(pluginUrl + "/" + script);
            });
        },

        /**
         * Loads an add-on from a specific url.
         *
         * @method load
         * @param {String} name Short name of the add-on that gets loaded.
         * @param {String} addOnUrl URL to the add-on that will get loaded.
         * @param {function} callback Optional callback to execute ones the add-on is loaded.
         * @param {Object} scope Optional scope to execute the callback in.
         * @example
         * // Loads a plugin from an external URL
         * tinymce.PluginManager.load('myplugin', '/some/dir/someplugin/plugin.js');
         *
         * // Initialize TinyMCE
         * tinymce.init({
         *  ...
         *  plugins: '-myplugin' // Don't try to load it again
         * });
         */
        load: function(name, addOnUrl, callback, scope) {
            var self = this, url = addOnUrl;

            function loadDependencies() {
                var dependencies = self.dependencies(name);

                each(dependencies, function(dep) {
                    var newUrl = self.createUrl(addOnUrl, dep);

                    self.load(newUrl.resource, newUrl, undefined, undefined);
                });

                if (callback) {
                    if (scope) {
                        callback.call(scope);
                    } else {
                        callback.call(ScriptLoader);
                    }
                }
            }

            if (self.urls[name]) {
                return;
            }

            if (typeof addOnUrl === "object") {
                url = addOnUrl.prefix + addOnUrl.resource + addOnUrl.suffix;
            }

            if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) {
                url = AddOnManager.baseURL + '/' + url;
            }

            self.urls[name] = url.substring(0, url.lastIndexOf('/'));

            if (self.lookup[name]) {
                loadDependencies();
            } else {
                ScriptLoader.ScriptLoader.add(url, loadDependencies, scope);
            }
        }
    };

    AddOnManager.PluginManager = new AddOnManager();
    AddOnManager.ThemeManager = new AddOnManager();

    return AddOnManager;
});

/**
 * TinyMCE theme class.
 *
 * @class tinymce.Theme
 */

/**
 * This method is responsible for rendering/generating the overall user interface with toolbars, buttons, iframe containers etc.
 *
 * @method renderUI
 * @param {Object} obj Object parameter containing the targetNode DOM node that will be replaced visually with an editor instance.
 * @return {Object} an object with items like iframeContainer, editorContainer, sizeContainer, deltaWidth, deltaHeight.
 */

/**
 * Plugin base class, this is a pseudo class that describes how a plugin is to be created for TinyMCE. The methods below are all optional.
 *
 * @class tinymce.Plugin
 * @example
 * tinymce.PluginManager.add('example', function(editor, url) {
 *     // Add a button that opens a window
 *     editor.addButton('example', {
 *         text: 'My button',
 *         icon: false,
 *         onclick: function() {
 *             // Open window
 *             editor.windowManager.open({
 *                 title: 'Example plugin',
 *                 body: [
 *                     {type: 'textbox', name: 'title', label: 'Title'}
 *                 ],
 *                 onsubmit: function(e) {
 *                     // Insert content when the window form is submitted
 *                     editor.insertContent('Title: ' + e.data.title);
 *                 }
 *             });
 *         }
 *     });
 *
 *     // Adds a menu item to the tools menu
 *     editor.addMenuItem('example', {
 *         text: 'Example plugin',
 *         context: 'tools',
 *         onclick: function() {
 *             // Open window with a specific url
 *             editor.windowManager.open({
 *                 title: 'TinyMCE site',
 *                 url: 'http://www.tinymce.com',
 *                 width: 800,
 *                 height: 600,
 *                 buttons: [{
 *                     text: 'Close',
 *                     onclick: 'close'
 *                 }]
 *             });
 *         }
 *     });
 * });
 */

// Included from: js/tinymce/classes/dom/RangeUtils.js

/**
 * RangeUtils.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class contains a few utility methods for ranges.
 *
 * @class tinymce.dom.RangeUtils
 * @private
 */
define("tinymce/dom/RangeUtils", [
    "tinymce/util/Tools",
    "tinymce/dom/TreeWalker"
], function(Tools, TreeWalker) {
    var each = Tools.each;

    function getEndChild(container, index) {
        var childNodes = container.childNodes;

        index--;

        if (index > childNodes.length - 1) {
            index = childNodes.length - 1;
        } else if (index < 0) {
            index = 0;
        }

        return childNodes[index] || container;
    }

    function RangeUtils(dom) {
        /**
         * Walks the specified range like object and executes the callback for each sibling collection it finds.
         *
         * @method walk
         * @param {Object} rng Range like object.
         * @param {function} callback Callback function to execute for each sibling collection.
         */
        this.walk = function(rng, callback) {
            var startContainer = rng.startContainer,
                startOffset = rng.startOffset,
                endContainer = rng.endContainer,
                endOffset = rng.endOffset,
                ancestor, startPoint,
                endPoint, node, parent, siblings, nodes;

            // Handle table cell selection the table plugin enables
            // you to fake select table cells and perform formatting actions on them
            nodes = dom.select('td.mce-item-selected,th.mce-item-selected');
            if (nodes.length > 0) {
                each(nodes, function(node) {
                    callback([node]);
                });

                return;
            }

            /**
             * Excludes start/end text node if they are out side the range
             *
             * @private
             * @param {Array} nodes Nodes to exclude items from.
             * @return {Array} Array with nodes excluding the start/end container if needed.
             */
            function exclude(nodes) {
                var node;

                // First node is excluded
                node = nodes[0];
                if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
                    nodes.splice(0, 1);
                }

                // Last node is excluded
                node = nodes[nodes.length - 1];
                if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
                    nodes.splice(nodes.length - 1, 1);
                }

                return nodes;
            }

            /**
             * Collects siblings
             *
             * @private
             * @param {Node} node Node to collect siblings from.
             * @param {String} name Name of the sibling to check for.
             * @return {Array} Array of collected siblings.
             */
            function collectSiblings(node, name, end_node) {
                var siblings = [];

                for (; node && node != end_node; node = node[name]) {
                    siblings.push(node);
                }

                return siblings;
            }

            /**
             * Find an end point this is the node just before the common ancestor root.
             *
             * @private
             * @param {Node} node Node to start at.
             * @param {Node} root Root/ancestor element to stop just before.
             * @return {Node} Node just before the root element.
             */
            function findEndPoint(node, root) {
                do {
                    if (node.parentNode == root) {
                        return node;
                    }

                    node = node.parentNode;
                } while (node);
            }

            function walkBoundary(start_node, end_node, next) {
                var siblingName = next ? 'nextSibling' : 'previousSibling';

                for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
                    parent = node.parentNode;
                    siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);

                    if (siblings.length) {
                        if (!next) {
                            siblings.reverse();
                        }

                        callback(exclude(siblings));
                    }
                }
            }

            // If index based start position then resolve it
            if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
                startContainer = startContainer.childNodes[startOffset];
            }

            // If index based end position then resolve it
            if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
                endContainer = getEndChild(endContainer, endOffset);
            }

            // Same container
            if (startContainer == endContainer) {
                return callback(exclude([startContainer]));
            }

            // Find common ancestor and end points
            ancestor = dom.findCommonAncestor(startContainer, endContainer);

            // Process left side
            for (node = startContainer; node; node = node.parentNode) {
                if (node === endContainer) {
                    return walkBoundary(startContainer, ancestor, true);
                }

                if (node === ancestor) {
                    break;
                }
            }

            // Process right side
            for (node = endContainer; node; node = node.parentNode) {
                if (node === startContainer) {
                    return walkBoundary(endContainer, ancestor);
                }

                if (node === ancestor) {
                    break;
                }
            }

            // Find start/end point
            startPoint = findEndPoint(startContainer, ancestor) || startContainer;
            endPoint = findEndPoint(endContainer, ancestor) || endContainer;

            // Walk left leaf
            walkBoundary(startContainer, startPoint, true);

            // Walk the middle from start to end point
            siblings = collectSiblings(
                startPoint == startContainer ? startPoint : startPoint.nextSibling,
                'nextSibling',
                endPoint == endContainer ? endPoint.nextSibling : endPoint
            );

            if (siblings.length) {
                callback(exclude(siblings));
            }

            // Walk right leaf
            walkBoundary(endContainer, endPoint);
        };

        /**
         * Splits the specified range at it's start/end points.
         *
         * @private
         * @param {Range/RangeObject} rng Range to split.
         * @return {Object} Range position object.
         */
        this.split = function(rng) {
            var startContainer = rng.startContainer,
                startOffset = rng.startOffset,
                endContainer = rng.endContainer,
                endOffset = rng.endOffset;

            function splitText(node, offset) {
                return node.splitText(offset);
            }

            // Handle single text node
            if (startContainer == endContainer && startContainer.nodeType == 3) {
                if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
                    endContainer = splitText(startContainer, startOffset);
                    startContainer = endContainer.previousSibling;

                    if (endOffset > startOffset) {
                        endOffset = endOffset - startOffset;
                        startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
                        endOffset = endContainer.nodeValue.length;
                        startOffset = 0;
                    } else {
                        endOffset = 0;
                    }
                }
            } else {
                // Split startContainer text node if needed
                if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
                    startContainer = splitText(startContainer, startOffset);
                    startOffset = 0;
                }

                // Split endContainer text node if needed
                if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
                    endContainer = splitText(endContainer, endOffset).previousSibling;
                    endOffset = endContainer.nodeValue.length;
                }
            }

            return {
                startContainer: startContainer,
                startOffset: startOffset,
                endContainer: endContainer,
                endOffset: endOffset
            };
        };

        /**
         * Normalizes the specified range by finding the closest best suitable caret location.
         *
         * @private
         * @param {Range} rng Range to normalize.
         * @return {Boolean} True/false if the specified range was normalized or not.
         */
        this.normalize = function(rng) {
            var normalized, collapsed;

            function normalizeEndPoint(start) {
                var container, offset, walker, body = dom.getRoot(), node, nonEmptyElementsMap;
                var directionLeft, isAfterNode;

                function hasBrBeforeAfter(node, left) {
                    var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);

                    while ((node = walker[left ? 'prev' : 'next']())) {
                        if (node.nodeName === "BR") {
                            return true;
                        }
                    }
                }

                function isPrevNode(node, name) {
                    return node.previousSibling && node.previousSibling.nodeName == name;
                }

                // Walks the dom left/right to find a suitable text node to move the endpoint into
                // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
                function findTextNodeRelative(left, startNode) {
                    var walker, lastInlineElement, parentBlockContainer;

                    startNode = startNode || container;
                    parentBlockContainer = dom.getParent(startNode.parentNode, dom.isBlock) || body;

                    // Lean left before the BR element if it's the only BR within a block element. Gecko bug: #6680
                    // This: <p><br>|</p> becomes <p>|<br></p>
                    if (left && startNode.nodeName == 'BR' && isAfterNode && dom.isEmpty(parentBlockContainer)) {
                        container = startNode.parentNode;
                        offset = dom.nodeIndex(startNode);
                        normalized = true;
                        return;
                    }

                    // Walk left until we hit a text node we can move to or a block/br/img
                    walker = new TreeWalker(startNode, parentBlockContainer);
                    while ((node = walker[left ? 'prev' : 'next']())) {
                        // Break if we hit a non content editable node
                        if (dom.getContentEditableParent(node) === "false") {
                            return;
                        }

                        // Found text node that has a length
                        if (node.nodeType === 3 && node.nodeValue.length > 0) {
                            container = node;
                            offset = left ? node.nodeValue.length : 0;
                            normalized = true;
                            return;
                        }

                        // Break if we find a block or a BR/IMG/INPUT etc
                        if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
                            return;
                        }

                        lastInlineElement = node;
                    }

                    // Only fetch the last inline element when in caret mode for now
                    if (collapsed && lastInlineElement) {
                        container = lastInlineElement;
                        normalized = true;
                        offset = 0;
                    }
                }

                container = rng[(start ? 'start' : 'end') + 'Container'];
                offset = rng[(start ? 'start' : 'end') + 'Offset'];
                isAfterNode = container.nodeType == 1 && offset === container.childNodes.length;
                nonEmptyElementsMap = dom.schema.getNonEmptyElements();
                directionLeft = start;

                if (container.nodeType == 1 && offset > container.childNodes.length - 1) {
                    directionLeft = false;
                }

                // If the container is a document move it to the body element
                if (container.nodeType === 9) {
                    container = dom.getRoot();
                    offset = 0;
                }

                // If the container is body try move it into the closest text node or position
                if (container === body) {
                    // If start is before/after a image, table etc
                    if (directionLeft) {
                        node = container.childNodes[offset > 0 ? offset - 1 : 0];
                        if (node) {
                            if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
                                return;
                            }
                        }
                    }

                    // Resolve the index
                    if (container.hasChildNodes()) {
                        offset = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1);
                        container = container.childNodes[offset];
                        offset = 0;

                        // Don't walk into elements that doesn't have any child nodes like a IMG
                        if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
                            // Walk the DOM to find a text node to place the caret at or a BR
                            node = container;
                            walker = new TreeWalker(container, body);

                            do {
                                // Found a text node use that position
                                if (node.nodeType === 3 && node.nodeValue.length > 0) {
                                    offset = directionLeft ? 0 : node.nodeValue.length;
                                    container = node;
                                    normalized = true;
                                    break;
                                }

                                // Found a BR/IMG element that we can place the caret before
                                if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
                                    offset = dom.nodeIndex(node);
                                    container = node.parentNode;

                                    // Put caret after image when moving the end point
                                    if (node.nodeName ==  "IMG" && !directionLeft) {
                                        offset++;
                                    }

                                    normalized = true;
                                    break;
                                }
                            } while ((node = (directionLeft ? walker.next() : walker.prev())));
                        }
                    }
                }

                // Lean the caret to the left if possible
                if (collapsed) {
                    // So this: <b>x</b><i>|x</i>
                    // Becomes: <b>x|</b><i>x</i>
                    // Seems that only gecko has issues with this
                    if (container.nodeType === 3 && offset === 0) {
                        findTextNodeRelative(true);
                    }

                    // Lean left into empty inline elements when the caret is before a BR
                    // So this: <i><b></b><i>|<br></i>
                    // Becomes: <i><b>|</b><i><br></i>
                    // Seems that only gecko has issues with this.
                    // Special edge case for <p><a>x</a>|<br></p> since we don't want <p><a>x|</a><br></p>
                    if (container.nodeType === 1) {
                        node = container.childNodes[offset];

                        // Offset is after the containers last child
                        // then use the previous child for normalization
                        if (!node) {
                            node = container.childNodes[offset - 1];
                        }

                        if (node && node.nodeName === 'BR' && !isPrevNode(node, 'A') &&
                            !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
                            findTextNodeRelative(true, node);
                        }
                    }
                }

                // Lean the start of the selection right if possible
                // So this: x[<b>x]</b>
                // Becomes: x<b>[x]</b>
                if (directionLeft && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
                    findTextNodeRelative(false);
                }

                // Set endpoint if it was normalized
                if (normalized) {
                    rng['set' + (start ? 'Start' : 'End')](container, offset);
                }
            }

            collapsed = rng.collapsed;

            normalizeEndPoint(true);

            if (!collapsed) {
                normalizeEndPoint();
            }

            // If it was collapsed then make sure it still is
            if (normalized && collapsed) {
                rng.collapse(true);
            }

            return normalized;
        };
    }

    /**
     * Compares two ranges and checks if they are equal.
     *
     * @static
     * @method compareRanges
     * @param {DOMRange} rng1 First range to compare.
     * @param {DOMRange} rng2 First range to compare.
     * @return {Boolean} true/false if the ranges are equal.
     */
    RangeUtils.compareRanges = function(rng1, rng2) {
        if (rng1 && rng2) {
            // Compare native IE ranges
            if (rng1.item || rng1.duplicate) {
                // Both are control ranges and the selected element matches
                if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) {
                    return true;
                }

                // Both are text ranges and the range matches
                if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) {
                    return true;
                }
            } else {
                // Compare w3c ranges
                return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
            }
        }

        return false;
    };

    return RangeUtils;
});

// Included from: js/tinymce/classes/NodeChange.js

/**
 * NodeChange.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class handles the nodechange event dispatching both manual and though selection change events.
 *
 * @class tinymce.NodeChange
 * @private
 */
define("tinymce/NodeChange", [
    "tinymce/dom/RangeUtils"
], function(RangeUtils) {
    return function(editor) {
        var lastRng, lastPath = [];

        /**
         * Returns true/false if the current element path has been changed or not.
         *
         * @private
         * @return {Boolean} True if the element path is the same false if it's not.
         */
        function isSameElementPath(startElm) {
            var i, currentPath;

            currentPath = editor.$(startElm).parentsUntil(editor.getBody()).add(startElm);
            if (currentPath.length === lastPath.length) {
                for (i = currentPath.length; i >= 0; i--) {
                    if (currentPath[i] !== lastPath[i]) {
                        break;
                    }
                }

                if (i === -1) {
                    lastPath = currentPath;
                    return true;
                }
            }

            lastPath = currentPath;

            return false;
        }

        // Gecko doesn't support the "selectionchange" event
        if (!('onselectionchange' in editor.getDoc())) {
            editor.on('NodeChange Click MouseUp KeyUp', function(e) {
                var nativeRng, fakeRng;

                // Since DOM Ranges mutate on modification
                // of the DOM we need to clone it's contents
                nativeRng = editor.selection.getRng();
                fakeRng = {
                    startContainer: nativeRng.startContainer,
                    startOffset: nativeRng.startOffset,
                    endContainer: nativeRng.endContainer,
                    endOffset: nativeRng.endOffset
                };

                // Always treat nodechange as a selectionchange since applying
                // formatting to the current range wouldn't update the range but it's parent
                if (e.type == 'nodechange' || !RangeUtils.compareRanges(fakeRng, lastRng)) {
                    editor.fire('SelectionChange');
                }

                lastRng = fakeRng;
            });
        }

        // IE has a bug where it fires a selectionchange on right click that has a range at the start of the body
        // When the contextmenu event fires the selection is located at the right location
        editor.on('contextmenu', function() {
            editor.fire('SelectionChange');
        });

        editor.on('SelectionChange', function() {
            var startElm = editor.selection.getStart();

            // Selection change might fire when focus is lost so check if the start is still within the body
            if (!isSameElementPath(startElm) && editor.dom.isChildOf(startElm, editor.getBody())) {
                editor.nodeChanged({selectionChange: true});
            }
        });

        /**
         * Distpaches out a onNodeChange event to all observers. This method should be called when you
         * need to update the UI states or element path etc.
         *
         * @method nodeChanged
         * @param {Object} args Optional args to pass to NodeChange event handlers.
         */
        this.nodeChanged = function(args) {
            var selection = editor.selection, node, parents, root;

            // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
            if (editor.initialized && !editor.settings.disable_nodechange && !editor.settings.readonly) {
                // Get start node
                root = editor.getBody();
                node = selection.getStart() || root;
                node = node.ownerDocument != editor.getDoc() ? editor.getBody() : node;

                // Edge case for <p>|<img></p>
                if (node.nodeName == 'IMG' && selection.isCollapsed()) {
                    node = node.parentNode;
                }

                // Get parents and add them to object
                parents = [];
                editor.dom.getParent(node, function(node) {
                    if (node === root) {
                        return true;
                    }

                    parents.push(node);
                });

                args = args || {};
                args.element = node;
                args.parents = parents;

                editor.fire('NodeChange', args);
            }
        };
    };
});

// Included from: js/tinymce/classes/html/Node.js

/**
 * Node.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class is a minimalistic implementation of a DOM like node used by the DomParser class.
 *
 * @example
 * var node = new tinymce.html.Node('strong', 1);
 * someRoot.append(node);
 *
 * @class tinymce.html.Node
 * @version 3.4
 */
define("tinymce/html/Node", [], function() {
    var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
        '#text': 3,
        '#comment': 8,
        '#cdata': 4,
        '#pi': 7,
        '#doctype': 10,
        '#document-fragment': 11
    };

    // Walks the tree left/right
    function walk(node, root_node, prev) {
        var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';

        // Walk into nodes if it has a start
        if (node[startName]) {
            return node[startName];
        }

        // Return the sibling if it has one
        if (node !== root_node) {
            sibling = node[siblingName];

            if (sibling) {
                return sibling;
            }

            // Walk up the parents to look for siblings
            for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
                sibling = parent[siblingName];

                if (sibling) {
                    return sibling;
                }
            }
        }
    }

    /**
     * Constructs a new Node instance.
     *
     * @constructor
     * @method Node
     * @param {String} name Name of the node type.
     * @param {Number} type Numeric type representing the node.
     */
    function Node(name, type) {
        this.name = name;
        this.type = type;

        if (type === 1) {
            this.attributes = [];
            this.attributes.map = {};
        }
    }

    Node.prototype = {
        /**
         * Replaces the current node with the specified one.
         *
         * @example
         * someNode.replace(someNewNode);
         *
         * @method replace
         * @param {tinymce.html.Node} node Node to replace the current node with.
         * @return {tinymce.html.Node} The old node that got replaced.
         */
        replace: function(node) {
            var self = this;

            if (node.parent) {
                node.remove();
            }

            self.insert(node, self);
            self.remove();

            return self;
        },

        /**
         * Gets/sets or removes an attribute by name.
         *
         * @example
         * someNode.attr("name", "value"); // Sets an attribute
         * console.log(someNode.attr("name")); // Gets an attribute
         * someNode.attr("name", null); // Removes an attribute
         *
         * @method attr
         * @param {String} name Attribute name to set or get.
         * @param {String} value Optional value to set.
         * @return {String/tinymce.html.Node} String or undefined on a get operation or the current node on a set operation.
         */
        attr: function(name, value) {
            var self = this, attrs, i, undef;

            if (typeof name !== "string") {
                for (i in name) {
                    self.attr(i, name[i]);
                }

                return self;
            }

            if ((attrs = self.attributes)) {
                if (value !== undef) {
                    // Remove attribute
                    if (value === null) {
                        if (name in attrs.map) {
                            delete attrs.map[name];

                            i = attrs.length;
                            while (i--) {
                                if (attrs[i].name === name) {
                                    attrs = attrs.splice(i, 1);
                                    return self;
                                }
                            }
                        }

                        return self;
                    }

                    // Set attribute
                    if (name in attrs.map) {
                        // Set attribute
                        i = attrs.length;
                        while (i--) {
                            if (attrs[i].name === name) {
                                attrs[i].value = value;
                                break;
                            }
                        }
                    } else {
                        attrs.push({name: name, value: value});
                    }

                    attrs.map[name] = value;

                    return self;
                } else {
                    return attrs.map[name];
                }
            }
        },

        /**
         * Does a shallow clones the node into a new node. It will also exclude id attributes since
         * there should only be one id per document.
         *
         * @example
         * var clonedNode = node.clone();
         *
         * @method clone
         * @return {tinymce.html.Node} New copy of the original node.
         */
        clone: function() {
            var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;

            // Clone element attributes
            if ((selfAttrs = self.attributes)) {
                cloneAttrs = [];
                cloneAttrs.map = {};

                for (i = 0, l = selfAttrs.length; i < l; i++) {
                    selfAttr = selfAttrs[i];

                    // Clone everything except id
                    if (selfAttr.name !== 'id') {
                        cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
                        cloneAttrs.map[selfAttr.name] = selfAttr.value;
                    }
                }

                clone.attributes = cloneAttrs;
            }

            clone.value = self.value;
            clone.shortEnded = self.shortEnded;

            return clone;
        },

        /**
         * Wraps the node in in another node.
         *
         * @example
         * node.wrap(wrapperNode);
         *
         * @method wrap
         */
        wrap: function(wrapper) {
            var self = this;

            self.parent.insert(wrapper, self);
            wrapper.append(self);

            return self;
        },

        /**
         * Unwraps the node in other words it removes the node but keeps the children.
         *
         * @example
         * node.unwrap();
         *
         * @method unwrap
         */
        unwrap: function() {
            var self = this, node, next;

            for (node = self.firstChild; node; ) {
                next = node.next;
                self.insert(node, self, true);
                node = next;
            }

            self.remove();
        },

        /**
         * Removes the node from it's parent.
         *
         * @example
         * node.remove();
         *
         * @method remove
         * @return {tinymce.html.Node} Current node that got removed.
         */
        remove: function() {
            var self = this, parent = self.parent, next = self.next, prev = self.prev;

            if (parent) {
                if (parent.firstChild === self) {
                    parent.firstChild = next;

                    if (next) {
                        next.prev = null;
                    }
                } else {
                    prev.next = next;
                }

                if (parent.lastChild === self) {
                    parent.lastChild = prev;

                    if (prev) {
                        prev.next = null;
                    }
                } else {
                    next.prev = prev;
                }

                self.parent = self.next = self.prev = null;
            }

            return self;
        },

        /**
         * Appends a new node as a child of the current node.
         *
         * @example
         * node.append(someNode);
         *
         * @method append
         * @param {tinymce.html.Node} node Node to append as a child of the current one.
         * @return {tinymce.html.Node} The node that got appended.
         */
        append: function(node) {
            var self = this, last;

            if (node.parent) {
                node.remove();
            }

            last = self.lastChild;
            if (last) {
                last.next = node;
                node.prev = last;
                self.lastChild = node;
            } else {
                self.lastChild = self.firstChild = node;
            }

            node.parent = self;

            return node;
        },

        /**
         * Inserts a node at a specific position as a child of the current node.
         *
         * @example
         * parentNode.insert(newChildNode, oldChildNode);
         *
         * @method insert
         * @param {tinymce.html.Node} node Node to insert as a child of the current node.
         * @param {tinymce.html.Node} ref_node Reference node to set node before/after.
         * @param {Boolean} before Optional state to insert the node before the reference node.
         * @return {tinymce.html.Node} The node that got inserted.
         */
        insert: function(node, ref_node, before) {
            var parent;

            if (node.parent) {
                node.remove();
            }

            parent = ref_node.parent || this;

            if (before) {
                if (ref_node === parent.firstChild) {
                    parent.firstChild = node;
                } else {
                    ref_node.prev.next = node;
                }

                node.prev = ref_node.prev;
                node.next = ref_node;
                ref_node.prev = node;
            } else {
                if (ref_node === parent.lastChild) {
                    parent.lastChild = node;
                } else {
                    ref_node.next.prev = node;
                }

                node.next = ref_node.next;
                node.prev = ref_node;
                ref_node.next = node;
            }

            node.parent = parent;

            return node;
        },

        /**
         * Get all children by name.
         *
         * @method getAll
         * @param {String} name Name of the child nodes to collect.
         * @return {Array} Array with child nodes matchin the specified name.
         */
        getAll: function(name) {
            var self = this, node, collection = [];

            for (node = self.firstChild; node; node = walk(node, self)) {
                if (node.name === name) {
                    collection.push(node);
                }
            }

            return collection;
        },

        /**
         * Removes all children of the current node.
         *
         * @method empty
         * @return {tinymce.html.Node} The current node that got cleared.
         */
        empty: function() {
            var self = this, nodes, i, node;

            // Remove all children
            if (self.firstChild) {
                nodes = [];

                // Collect the children
                for (node = self.firstChild; node; node = walk(node, self)) {
                    nodes.push(node);
                }

                // Remove the children
                i = nodes.length;
                while (i--) {
                    node = nodes[i];
                    node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
                }
            }

            self.firstChild = self.lastChild = null;

            return self;
        },

        /**
         * Returns true/false if the node is to be considered empty or not.
         *
         * @example
         * node.isEmpty({img: true});
         * @method isEmpty
         * @param {Object} elements Name/value object with elements that are automatically treated as non empty elements.
         * @return {Boolean} true/false if the node is empty or not.
         */
        isEmpty: function(elements) {
            var self = this, node = self.firstChild, i, name;

            if (node) {
                do {
                    if (node.type === 1) {
                        // Ignore bogus elements
                        if (node.attributes.map['data-mce-bogus']) {
                            continue;
                        }

                        // Keep empty elements like <img />
                        if (elements[node.name]) {
                            return false;
                        }

                        // Keep elements with data attributes or name attribute like <a name="1"></a>
                        i = node.attributes.length;
                        while (i--) {
                            name = node.attributes[i].name;
                            if (name === "name" || name.indexOf('data-mce-') === 0) {
                                return false;
                            }
                        }
                    }

                    // Keep comments
                    if (node.type === 8) {
                        return false;
                    }

                    // Keep non whitespace text nodes
                    if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) {
                        return false;
                    }
                } while ((node = walk(node, self)));
            }

            return true;
        },

        /**
         * Walks to the next or previous node and returns that node or null if it wasn't found.
         *
         * @method walk
         * @param {Boolean} prev Optional previous node state defaults to false.
         * @return {tinymce.html.Node} Node that is next to or previous of the current node.
         */
        walk: function(prev) {
            return walk(this, null, prev);
        }
    };

    /**
     * Creates a node of a specific type.
     *
     * @static
     * @method create
     * @param {String} name Name of the node type to create for example "b" or "#text".
     * @param {Object} attrs Name/value collection of attributes that will be applied to elements.
     */
    Node.create = function(name, attrs) {
        var node, attrName;

        // Create node
        node = new Node(name, typeLookup[name] || 1);

        // Add attributes if needed
        if (attrs) {
            for (attrName in attrs) {
                node.attr(attrName, attrs[attrName]);
            }
        }

        return node;
    };

    return Node;
});

// Included from: js/tinymce/classes/html/Schema.js

/**
 * Schema.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Schema validator class.
 *
 * @class tinymce.html.Schema
 * @example
 *  if (tinymce.activeEditor.schema.isValidChild('p', 'span'))
 *    alert('span is valid child of p.');
 *
 *  if (tinymce.activeEditor.schema.getElementRule('p'))
 *    alert('P is a valid element.');
 *
 * @class tinymce.html.Schema
 * @version 3.4
 */
define("tinymce/html/Schema", [
    "tinymce/util/Tools"
], function(Tools) {
    var mapCache = {};
    var makeMap = Tools.makeMap, each = Tools.each, extend = Tools.extend, explode = Tools.explode, inArray = Tools.inArray;

    function split(items, delim) {
        return items ? items.split(delim || ' ') : [];
    }

    /**
     * Builds a schema lookup table
     *
     * @private
     * @param {String} type html4, html5 or html5-strict schema type.
     * @return {Object} Schema lookup table.
     */
    function compileSchema(type) {
        var schema = {}, globalAttributes, blockContent;
        var phrasingContent, flowContent, html4BlockContent, html4PhrasingContent;

        function add(name, attributes, children) {
            var ni, i, attributesOrder, args = arguments;

            function arrayToMap(array) {
                var map = {}, i, l;

                for (i = 0, l = array.length; i < l; i++) {
                    map[array[i]] = {};
                }

                return map;
            }

            children = children || [];
            attributes = attributes || "";

            if (typeof(children) === "string") {
                children = split(children);
            }

            // Split string children
            for (i = 3; i < args.length; i++) {
                if (typeof(args[i]) === "string") {
                    args[i] = split(args[i]);
                }

                children.push.apply(children, args[i]);
            }

            name = split(name);
            ni = name.length;
            while (ni--) {
                attributesOrder = [].concat(globalAttributes, split(attributes));
                schema[name[ni]] = {
                    attributes: arrayToMap(attributesOrder),
                    attributesOrder: attributesOrder,
                    children: arrayToMap(children)
                };
            }
        }

        function addAttrs(name, attributes) {
            var ni, schemaItem, i, l;

            name = split(name);
            ni = name.length;
            attributes = split(attributes);
            while (ni--) {
                schemaItem = schema[name[ni]];
                for (i = 0, l = attributes.length; i < l; i++) {
                    schemaItem.attributes[attributes[i]] = {};
                    schemaItem.attributesOrder.push(attributes[i]);
                }
            }
        }

        // Use cached schema
        if (mapCache[type]) {
            return mapCache[type];
        }

        // Attributes present on all elements
        globalAttributes = split("id accesskey class dir lang style tabindex title");

        // Event attributes can be opt-in/opt-out
        /*eventAttributes = split("onabort onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncuechange " +
                "ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended " +
                "onerror onfocus oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart " +
                "onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay onplaying onprogress onratechange " +
                "onreset onscroll onseeked onseeking onseeking onselect onshow onstalled onsubmit onsuspend ontimeupdate onvolumechange " +
                "onwaiting"
        );*/

        // Block content elements
        blockContent = split(
            "address blockquote div dl fieldset form h1 h2 h3 h4 h5 h6 hr menu ol p pre table ul"
        );

        // Phrasing content elements from the HTML5 spec (inline)
        phrasingContent = split(
            "a abbr b bdo br button cite code del dfn em embed i iframe img input ins kbd " +
            "label map noscript object q s samp script select small span strong sub sup " +
            "textarea u var #text #comment"
        );

        // Add HTML5 items to globalAttributes, blockContent, phrasingContent
        if (type != "html4") {
            globalAttributes.push.apply(globalAttributes, split("contenteditable contextmenu draggable dropzone " +
                "hidden spellcheck translate"));
            blockContent.push.apply(blockContent, split("article aside details dialog figure header footer hgroup section nav"));
            phrasingContent.push.apply(phrasingContent, split("audio canvas command datalist mark meter output progress time wbr " +
                "video ruby bdi keygen"));
        }

        // Add HTML4 elements unless it's html5-strict
        if (type != "html5-strict") {
            globalAttributes.push("xml:lang");

            html4PhrasingContent = split("acronym applet basefont big font strike tt");
            phrasingContent.push.apply(phrasingContent, html4PhrasingContent);

            each(html4PhrasingContent, function(name) {
                add(name, "", phrasingContent);
            });

            html4BlockContent = split("center dir isindex noframes");
            blockContent.push.apply(blockContent, html4BlockContent);

            // Flow content elements from the HTML5 spec (block+inline)
            flowContent = [].concat(blockContent, phrasingContent);

            each(html4BlockContent, function(name) {
                add(name, "", flowContent);
            });
        }

        // Flow content elements from the HTML5 spec (block+inline)
        flowContent = flowContent || [].concat(blockContent, phrasingContent);

        // HTML4 base schema TODO: Move HTML5 specific attributes to HTML5 specific if statement
        // Schema items <element name>, <specific attributes>, <children ..>
        add("html", "manifest", "head body");
        add("head", "", "base command link meta noscript script style title");
        add("title hr noscript br");
        add("base", "href target");
        add("link", "href rel media hreflang type sizes hreflang");
        add("meta", "name http-equiv content charset");
        add("style", "media type scoped");
        add("script", "src async defer type charset");
        add("body", "onafterprint onbeforeprint onbeforeunload onblur onerror onfocus " +
                "onhashchange onload onmessage onoffline ononline onpagehide onpageshow " +
                "onpopstate onresize onscroll onstorage onunload", flowContent);
        add("address dt dd div caption", "", flowContent);
        add("h1 h2 h3 h4 h5 h6 pre p abbr code var samp kbd sub sup i b u bdo span legend em strong small s cite dfn", "", phrasingContent);
        add("blockquote", "cite", flowContent);
        add("ol", "reversed start type", "li");
        add("ul", "", "li");
        add("li", "value", flowContent);
        add("dl", "", "dt dd");
        add("a", "href target rel media hreflang type", phrasingContent);
        add("q", "cite", phrasingContent);
        add("ins del", "cite datetime", flowContent);
        add("img", "src alt usemap ismap width height");
        add("iframe", "src name width height", flowContent);
        add("embed", "src type width height");
        add("object", "data type typemustmatch name usemap form width height", flowContent, "param");
        add("param", "name value");
        add("map", "name", flowContent, "area");
        add("area", "alt coords shape href target rel media hreflang type");
        add("table", "border", "caption colgroup thead tfoot tbody tr" + (type == "html4" ? " col" : ""));
        add("colgroup", "span", "col");
        add("col", "span");
        add("tbody thead tfoot", "", "tr");
        add("tr", "", "td th");
        add("td", "colspan rowspan headers", flowContent);
        add("th", "colspan rowspan headers scope abbr", flowContent);
        add("form", "accept-charset action autocomplete enctype method name novalidate target", flowContent);
        add("fieldset", "disabled form name", flowContent, "legend");
        add("label", "form for", phrasingContent);
        add("input", "accept alt autocomplete checked dirname disabled form formaction formenctype formmethod formnovalidate " +
                "formtarget height list max maxlength min multiple name pattern readonly required size src step type value width"
        );
        add("button", "disabled form formaction formenctype formmethod formnovalidate formtarget name type value",
            type == "html4" ? flowContent : phrasingContent);
        add("select", "disabled form multiple name required size", "option optgroup");
        add("optgroup", "disabled label", "option");
        add("option", "disabled label selected value");
        add("textarea", "cols dirname disabled form maxlength name readonly required rows wrap");
        add("menu", "type label", flowContent, "li");
        add("noscript", "", flowContent);

        // Extend with HTML5 elements
        if (type != "html4") {
            add("wbr");
            add("ruby", "", phrasingContent, "rt rp");
            add("figcaption", "", flowContent);
            add("mark rt rp summary bdi", "", phrasingContent);
            add("canvas", "width height", flowContent);
            add("video", "src crossorigin poster preload autoplay mediagroup loop " +
                "muted controls width height buffered", flowContent, "track source");
            add("audio", "src crossorigin preload autoplay mediagroup loop muted controls buffered volume", flowContent, "track source");
            add("source", "src type media");
            add("track", "kind src srclang label default");
            add("datalist", "", phrasingContent, "option");
            add("article section nav aside header footer", "", flowContent);
            add("hgroup", "", "h1 h2 h3 h4 h5 h6");
            add("figure", "", flowContent, "figcaption");
            add("time", "datetime", phrasingContent);
            add("dialog", "open", flowContent);
            add("command", "type label icon disabled checked radiogroup command");
            add("output", "for form name", phrasingContent);
            add("progress", "value max", phrasingContent);
            add("meter", "value min max low high optimum", phrasingContent);
            add("details", "open", flowContent, "summary");
            add("keygen", "autofocus challenge disabled form keytype name");
        }

        // Extend with HTML4 attributes unless it's html5-strict
        if (type != "html5-strict") {
            addAttrs("script", "language xml:space");
            addAttrs("style", "xml:space");
            addAttrs("object", "declare classid code codebase codetype archive standby align border hspace vspace");
            addAttrs("embed", "align name hspace vspace");
            addAttrs("param", "valuetype type");
            addAttrs("a", "charset name rev shape coords");
            addAttrs("br", "clear");
            addAttrs("applet", "codebase archive code object alt name width height align hspace vspace");
            addAttrs("img", "name longdesc align border hspace vspace");
            addAttrs("iframe", "longdesc frameborder marginwidth marginheight scrolling align");
            addAttrs("font basefont", "size color face");
            addAttrs("input", "usemap align");
            addAttrs("select", "onchange");
            addAttrs("textarea");
            addAttrs("h1 h2 h3 h4 h5 h6 div p legend caption", "align");
            addAttrs("ul", "type compact");
            addAttrs("li", "type");
            addAttrs("ol dl menu dir", "compact");
            addAttrs("pre", "width xml:space");
            addAttrs("hr", "align noshade size width");
            addAttrs("isindex", "prompt");
            addAttrs("table", "summary width frame rules cellspacing cellpadding align bgcolor");
            addAttrs("col", "width align char charoff valign");
            addAttrs("colgroup", "width align char charoff valign");
            addAttrs("thead", "align char charoff valign");
            addAttrs("tr", "align char charoff valign bgcolor");
            addAttrs("th", "axis align char charoff valign nowrap bgcolor width height");
            addAttrs("form", "accept");
            addAttrs("td", "abbr axis scope align char charoff valign nowrap bgcolor width height");
            addAttrs("tfoot", "align char charoff valign");
            addAttrs("tbody", "align char charoff valign");
            addAttrs("area", "nohref");
            addAttrs("body", "background bgcolor text link vlink alink");
        }

        // Extend with HTML5 attributes unless it's html4
        if (type != "html4") {
            addAttrs("input button select textarea", "autofocus");
            addAttrs("input textarea", "placeholder");
            addAttrs("a", "download");
            addAttrs("link script img", "crossorigin");
            addAttrs("iframe", "sandbox seamless allowfullscreen"); // Excluded: srcdoc
        }

        // Special: iframe, ruby, video, audio, label

        // Delete children of the same name from it's parent
        // For example: form can't have a child of the name form
        each(split('a form meter progress dfn'), function(name) {
            if (schema[name]) {
                delete schema[name].children[name];
            }
        });

        // Delete header, footer, sectioning and heading content descendants
        /*each('dt th address', function(name) {
            delete schema[name].children[name];
        });*/

        // Caption can't have tables
        delete schema.caption.children.table;

        // TODO: LI:s can only have value if parent is OL

        // TODO: Handle transparent elements
        // a ins del canvas map

        mapCache[type] = schema;

        return schema;
    }

    function compileElementMap(value, mode) {
        var styles;

        if (value) {
            styles = {};

            if (typeof value == 'string') {
                value = {
                    '*': value
                };
            }

            // Convert styles into a rule list
            each(value, function(value, key) {
                styles[key] = mode == 'map' ? makeMap(value, /[, ]/) : explode(value, /[, ]/);
            });
        }

        return styles;
    }

    /**
     * Constructs a new Schema instance.
     *
     * @constructor
     * @method Schema
     * @param {Object} settings Name/value settings object.
     */
    return function(settings) {
        var self = this, elements = {}, children = {}, patternElements = [], validStyles, invalidStyles, schemaItems;
        var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, validClasses;
        var blockElementsMap, nonEmptyElementsMap, textBlockElementsMap, textInlineElementsMap;
        var customElementsMap = {}, specialElements = {};

        // Creates an lookup table map object for the specified option or the default value
        function createLookupTable(option, default_value, extendWith) {
            var value = settings[option];

            if (!value) {
                // Get cached default map or make it if needed
                value = mapCache[option];

                if (!value) {
                    value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
                    value = extend(value, extendWith);

                    mapCache[option] = value;
                }
            } else {
                // Create custom map
                value = makeMap(value, /[, ]/, makeMap(value.toUpperCase(), /[, ]/));
            }

            return value;
        }

        settings = settings || {};
        schemaItems = compileSchema(settings.schema);

        // Allow all elements and attributes if verify_html is set to false
        if (settings.verify_html === false) {
            settings.valid_elements = '*[*]';
        }

        validStyles = compileElementMap(settings.valid_styles);
        invalidStyles = compileElementMap(settings.invalid_styles, 'map');
        validClasses = compileElementMap(settings.valid_classes, 'map');

        // Setup map objects
        whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea video audio iframe object');
        selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
        shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link ' +
            'meta param embed source wbr track');
        boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize ' +
            'noshade nowrap readonly selected autoplay loop controls');
        nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script', shortEndedElementsMap);
        textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' +
                        'blockquote center dir fieldset header footer article section hgroup aside nav figure');
        blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' +
                        'th tr td li ol ul caption dl dt dd noscript menu isindex option ' +
                        'datalist select optgroup', textBlockElementsMap);
        textInlineElementsMap = createLookupTable('text_inline_elements', 'span strong b em i font strike u var cite ' +
                                        'dfn code mark q sup sub samp');

        each((settings.special || 'script noscript style textarea').split(' '), function(name) {
            specialElements[name] = new RegExp('<\/' + name + '[^>]*>', 'gi');
        });

        // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
        function patternToRegExp(str) {
            return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
        }

        // Parses the specified valid_elements string and adds to the current rules
        // This function is a bit hard to read since it's heavily optimized for speed
        function addValidElements(validElements) {
            var ei, el, ai, al, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
                prefix, outputName, globalAttributes, globalAttributesOrder, key, value,
                elementRuleRegExp = /^([#+\-])?([^\[!\/]+)(?:\/([^\[!]+))?(?:(!?)\[([^\]]+)\])?$/,
                attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
                hasPatternsRegExp = /[*?+]/;

            if (validElements) {
                // Split valid elements into an array with rules
                validElements = split(validElements, ',');

                if (elements['@']) {
                    globalAttributes = elements['@'].attributes;
                    globalAttributesOrder = elements['@'].attributesOrder;
                }

                // Loop all rules
                for (ei = 0, el = validElements.length; ei < el; ei++) {
                    // Parse element rule
                    matches = elementRuleRegExp.exec(validElements[ei]);
                    if (matches) {
                        // Setup local names for matches
                        prefix = matches[1];
                        elementName = matches[2];
                        outputName = matches[3];
                        attrData = matches[5];

                        // Create new attributes and attributesOrder
                        attributes = {};
                        attributesOrder = [];

                        // Create the new element
                        element = {
                            attributes: attributes,
                            attributesOrder: attributesOrder
                        };

                        // Padd empty elements prefix
                        if (prefix === '#') {
                            element.paddEmpty = true;
                        }

                        // Remove empty elements prefix
                        if (prefix === '-') {
                            element.removeEmpty = true;
                        }

                        if (matches[4] === '!') {
                            element.removeEmptyAttrs = true;
                        }

                        // Copy attributes from global rule into current rule
                        if (globalAttributes) {
                            for (key in globalAttributes) {
                                attributes[key] = globalAttributes[key];
                            }

                            attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
                        }

                        // Attributes defined
                        if (attrData) {
                            attrData = split(attrData, '|');
                            for (ai = 0, al = attrData.length; ai < al; ai++) {
                                matches = attrRuleRegExp.exec(attrData[ai]);
                                if (matches) {
                                    attr = {};
                                    attrType = matches[1];
                                    attrName = matches[2].replace(/::/g, ':');
                                    prefix = matches[3];
                                    value = matches[4];

                                    // Required
                                    if (attrType === '!') {
                                        element.attributesRequired = element.attributesRequired || [];
                                        element.attributesRequired.push(attrName);
                                        attr.required = true;
                                    }

                                    // Denied from global
                                    if (attrType === '-') {
                                        delete attributes[attrName];
                                        attributesOrder.splice(inArray(attributesOrder, attrName), 1);
                                        continue;
                                    }

                                    // Default value
                                    if (prefix) {
                                        // Default value
                                        if (prefix === '=') {
                                            element.attributesDefault = element.attributesDefault || [];
                                            element.attributesDefault.push({name: attrName, value: value});
                                            attr.defaultValue = value;
                                        }

                                        // Forced value
                                        if (prefix === ':') {
                                            element.attributesForced = element.attributesForced || [];
                                            element.attributesForced.push({name: attrName, value: value});
                                            attr.forcedValue = value;
                                        }

                                        // Required values
                                        if (prefix === '<') {
                                            attr.validValues = makeMap(value, '?');
                                        }
                                    }

                                    // Check for attribute patterns
                                    if (hasPatternsRegExp.test(attrName)) {
                                        element.attributePatterns = element.attributePatterns || [];
                                        attr.pattern = patternToRegExp(attrName);
                                        element.attributePatterns.push(attr);
                                    } else {
                                        // Add attribute to order list if it doesn't already exist
                                        if (!attributes[attrName]) {
                                            attributesOrder.push(attrName);
                                        }

                                        attributes[attrName] = attr;
                                    }
                                }
                            }
                        }

                        // Global rule, store away these for later usage
                        if (!globalAttributes && elementName == '@') {
                            globalAttributes = attributes;
                            globalAttributesOrder = attributesOrder;
                        }

                        // Handle substitute elements such as b/strong
                        if (outputName) {
                            element.outputName = elementName;
                            elements[outputName] = element;
                        }

                        // Add pattern or exact element
                        if (hasPatternsRegExp.test(elementName)) {
                            element.pattern = patternToRegExp(elementName);
                            patternElements.push(element);
                        } else {
                            elements[elementName] = element;
                        }
                    }
                }
            }
        }

        function setValidElements(validElements) {
            elements = {};
            patternElements = [];

            addValidElements(validElements);

            each(schemaItems, function(element, name) {
                children[name] = element.children;
            });
        }

        // Adds custom non HTML elements to the schema
        function addCustomElements(customElements) {
            var customElementRegExp = /^(~)?(.+)$/;

            if (customElements) {
                // Flush cached items since we are altering the default maps
                mapCache.text_block_elements = mapCache.block_elements = null;

                each(split(customElements, ','), function(rule) {
                    var matches = customElementRegExp.exec(rule),
                        inline = matches[1] === '~',
                        cloneName = inline ? 'span' : 'div',
                        name = matches[2];

                    children[name] = children[cloneName];
                    customElementsMap[name] = cloneName;

                    // If it's not marked as inline then add it to valid block elements
                    if (!inline) {
                        blockElementsMap[name.toUpperCase()] = {};
                        blockElementsMap[name] = {};
                    }

                    // Add elements clone if needed
                    if (!elements[name]) {
                        var customRule = elements[cloneName];

                        customRule = extend({}, customRule);
                        delete customRule.removeEmptyAttrs;
                        delete customRule.removeEmpty;

                        elements[name] = customRule;
                    }

                    // Add custom elements at span/div positions
                    each(children, function(element, elmName) {
                        if (element[cloneName]) {
                            children[elmName] = element = extend({}, children[elmName]);
                            element[name] = element[cloneName];
                        }
                    });
                });
            }
        }

        // Adds valid children to the schema object
        function addValidChildren(validChildren) {
            var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;

            if (validChildren) {
                each(split(validChildren, ','), function(rule) {
                    var matches = childRuleRegExp.exec(rule), parent, prefix;

                    if (matches) {
                        prefix = matches[1];

                        // Add/remove items from default
                        if (prefix) {
                            parent = children[matches[2]];
                        } else {
                            parent = children[matches[2]] = {'#comment': {}};
                        }

                        parent = children[matches[2]];

                        each(split(matches[3], '|'), function(child) {
                            if (prefix === '-') {
                                // Clone the element before we delete
                                // things in it to not mess up default schemas
                                children[matches[2]] = parent = extend({}, children[matches[2]]);

                                delete parent[child];
                            } else {
                                parent[child] = {};
                            }
                        });
                    }
                });
            }
        }

        function getElementRule(name) {
            var element = elements[name], i;

            // Exact match found
            if (element) {
                return element;
            }

            // No exact match then try the patterns
            i = patternElements.length;
            while (i--) {
                element = patternElements[i];

                if (element.pattern.test(name)) {
                    return element;
                }
            }
        }

        if (!settings.valid_elements) {
            // No valid elements defined then clone the elements from the schema spec
            each(schemaItems, function(element, name) {
                elements[name] = {
                    attributes: element.attributes,
                    attributesOrder: element.attributesOrder
                };

                children[name] = element.children;
            });

            // Switch these on HTML4
            if (settings.schema != "html5") {
                each(split('strong/b em/i'), function(item) {
                    item = split(item, '/');
                    elements[item[1]].outputName = item[0];
                });
            }

            // Add default alt attribute for images
            elements.img.attributesDefault = [{name: 'alt', value: ''}];

            // Remove these if they are empty by default
            each(split('ol ul sub sup blockquote span font a table tbody tr strong em b i'), function(name) {
                if (elements[name]) {
                    elements[name].removeEmpty = true;
                }
            });

            // Padd these by default
            each(split('p h1 h2 h3 h4 h5 h6 th td pre div address caption'), function(name) {
                elements[name].paddEmpty = true;
            });

            // Remove these if they have no attributes
            each(split('span'), function(name) {
                elements[name].removeEmptyAttrs = true;
            });

            // Remove these by default
            // TODO: Reenable in 4.1
            /*each(split('script style'), function(name) {
                delete elements[name];
            });*/
        } else {
            setValidElements(settings.valid_elements);
        }

        addCustomElements(settings.custom_elements);
        addValidChildren(settings.valid_children);
        addValidElements(settings.extended_valid_elements);

        // Todo: Remove this when we fix list handling to be valid
        addValidChildren('+ol[ul|ol],+ul[ul|ol]');

        // Delete invalid elements
        if (settings.invalid_elements) {
            each(explode(settings.invalid_elements), function(item) {
                if (elements[item]) {
                    delete elements[item];
                }
            });
        }

        // If the user didn't allow span only allow internal spans
        if (!getElementRule('span')) {
            addValidElements('span[!data-mce-type|*]');
        }

        /**
         * Name/value map object with valid parents and children to those parents.
         *
         * @example
         * children = {
         *    div:{p:{}, h1:{}}
         * };
         * @field children
         * @type Object
         */
        self.children = children;

        /**
         * Name/value map object with valid styles for each element.
         *
         * @method getValidStyles
         * @type Object
         */
        self.getValidStyles = function() {
            return validStyles;
        };

        /**
         * Name/value map object with valid styles for each element.
         *
         * @method getInvalidStyles
         * @type Object
         */
        self.getInvalidStyles = function() {
            return invalidStyles;
        };

        /**
         * Name/value map object with valid classes for each element.
         *
         * @method getValidClasses
         * @type Object
         */
        self.getValidClasses = function() {
            return validClasses;
        };

        /**
         * Returns a map with boolean attributes.
         *
         * @method getBoolAttrs
         * @return {Object} Name/value lookup map for boolean attributes.
         */
        self.getBoolAttrs = function() {
            return boolAttrMap;
        };

        /**
         * Returns a map with block elements.
         *
         * @method getBlockElements
         * @return {Object} Name/value lookup map for block elements.
         */
        self.getBlockElements = function() {
            return blockElementsMap;
        };

        /**
         * Returns a map with text block elements. Such as: p,h1-h6,div,address
         *
         * @method getTextBlockElements
         * @return {Object} Name/value lookup map for block elements.
         */
        self.getTextBlockElements = function() {
            return textBlockElementsMap;
        };

        /**
         * Returns a map of inline text format nodes for example strong/span or ins.
         *
         * @method getTextInlineElements
         * @return {Object} Name/value lookup map for text format elements.
         */
        self.getTextInlineElements = function() {
            return textInlineElementsMap;
        };

        /**
         * Returns a map with short ended elements such as BR or IMG.
         *
         * @method getShortEndedElements
         * @return {Object} Name/value lookup map for short ended elements.
         */
        self.getShortEndedElements = function() {
            return shortEndedElementsMap;
        };

        /**
         * Returns a map with self closing tags such as <li>.
         *
         * @method getSelfClosingElements
         * @return {Object} Name/value lookup map for self closing tags elements.
         */
        self.getSelfClosingElements = function() {
            return selfClosingElementsMap;
        };

        /**
         * Returns a map with elements that should be treated as contents regardless if it has text
         * content in them or not such as TD, VIDEO or IMG.
         *
         * @method getNonEmptyElements
         * @return {Object} Name/value lookup map for non empty elements.
         */
        self.getNonEmptyElements = function() {
            return nonEmptyElementsMap;
        };

        /**
         * Returns a map with elements where white space is to be preserved like PRE or SCRIPT.
         *
         * @method getWhiteSpaceElements
         * @return {Object} Name/value lookup map for white space elements.
         */
        self.getWhiteSpaceElements = function() {
            return whiteSpaceElementsMap;
        };

        /**
         * Returns a map with special elements. These are elements that needs to be parsed
         * in a special way such as script, style, textarea etc. The map object values
         * are regexps used to find the end of the element.
         *
         * @method getSpecialElements
         * @return {Object} Name/value lookup map for special elements.
         */
        self.getSpecialElements = function() {
            return specialElements;
        };

        /**
         * Returns true/false if the specified element and it's child is valid or not
         * according to the schema.
         *
         * @method isValidChild
         * @param {String} name Element name to check for.
         * @param {String} child Element child to verify.
         * @return {Boolean} True/false if the element is a valid child of the specified parent.
         */
        self.isValidChild = function(name, child) {
            var parent = children[name];

            return !!(parent && parent[child]);
        };

        /**
         * Returns true/false if the specified element name and optional attribute is
         * valid according to the schema.
         *
         * @method isValid
         * @param {String} name Name of element to check.
         * @param {String} attr Optional attribute name to check for.
         * @return {Boolean} True/false if the element and attribute is valid.
         */
        self.isValid = function(name, attr) {
            var attrPatterns, i, rule = getElementRule(name);

            // Check if it's a valid element
            if (rule) {
                if (attr) {
                    // Check if attribute name exists
                    if (rule.attributes[attr]) {
                        return true;
                    }

                    // Check if attribute matches a regexp pattern
                    attrPatterns = rule.attributePatterns;
                    if (attrPatterns) {
                        i = attrPatterns.length;
                        while (i--) {
                            if (attrPatterns[i].pattern.test(name)) {
                                return true;
                            }
                        }
                    }
                } else {
                    return true;
                }
            }

            // No match
            return false;
        };

        /**
         * Returns true/false if the specified element is valid or not
         * according to the schema.
         *
         * @method getElementRule
         * @param {String} name Element name to check for.
         * @return {Object} Element object or undefined if the element isn't valid.
         */
        self.getElementRule = getElementRule;

        /**
         * Returns an map object of all custom elements.
         *
         * @method getCustomElements
         * @return {Object} Name/value map object of all custom elements.
         */
        self.getCustomElements = function() {
            return customElementsMap;
        };

        /**
         * Parses a valid elements string and adds it to the schema. The valid elements
         * format is for example "element[attr=default|otherattr]".
         * Existing rules will be replaced with the ones specified, so this extends the schema.
         *
         * @method addValidElements
         * @param {String} valid_elements String in the valid elements format to be parsed.
         */
        self.addValidElements = addValidElements;

        /**
         * Parses a valid elements string and sets it to the schema. The valid elements
         * format is for example "element[attr=default|otherattr]".
         * Existing rules will be replaced with the ones specified, so this extends the schema.
         *
         * @method setValidElements
         * @param {String} valid_elements String in the valid elements format to be parsed.
         */
        self.setValidElements = setValidElements;

        /**
         * Adds custom non HTML elements to the schema.
         *
         * @method addCustomElements
         * @param {String} custom_elements Comma separated list of custom elements to add.
         */
        self.addCustomElements = addCustomElements;

        /**
         * Parses a valid children string and adds them to the schema structure. The valid children
         * format is for example: "element[child1|child2]".
         *
         * @method addValidChildren
         * @param {String} valid_children Valid children elements string to parse
         */
        self.addValidChildren = addValidChildren;

        self.elements = elements;
    };
});

// Included from: js/tinymce/classes/html/SaxParser.js

/**
 * SaxParser.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/*eslint max-depth:[2, 9] */

/**
 * This class parses HTML code using pure JavaScript and executes various events for each item it finds. It will
 * always execute the events in the right order for tag soup code like <b><p></b></p>. It will also remove elements
 * and attributes that doesn't fit the schema if the validate setting is enabled.
 *
 * @example
 * var parser = new tinymce.html.SaxParser({
 *     validate: true,
 *
 *     comment: function(text) {
 *         console.log('Comment:', text);
 *     },
 *
 *     cdata: function(text) {
 *         console.log('CDATA:', text);
 *     },
 *
 *     text: function(text, raw) {
 *         console.log('Text:', text, 'Raw:', raw);
 *     },
 *
 *     start: function(name, attrs, empty) {
 *         console.log('Start:', name, attrs, empty);
 *     },
 *
 *     end: function(name) {
 *         console.log('End:', name);
 *     },
 *
 *     pi: function(name, text) {
 *         console.log('PI:', name, text);
 *     },
 *
 *     doctype: function(text) {
 *         console.log('DocType:', text);
 *     }
 * }, schema);
 * @class tinymce.html.SaxParser
 * @version 3.4
 */
define("tinymce/html/SaxParser", [
    "tinymce/html/Schema",
    "tinymce/html/Entities",
    "tinymce/util/Tools"
], function(Schema, Entities, Tools) {
    var each = Tools.each;

    /**
     * Returns the index of the end tag for a specific start tag. This can be
     * used to skip all children of a parent element from being processed.
     *
     * @private
     * @method findEndTag
     * @param {tinymce.html.Schema} schema Schema instance to use to match short ended elements.
     * @param {String} html HTML string to find the end tag in.
     * @param {Number} startIndex Indext to start searching at should be after the start tag.
     * @return {Number} Index of the end tag.
     */
    function findEndTag(schema, html, startIndex) {
        var count = 1, index, matches, tokenRegExp, shortEndedElements;

        shortEndedElements = schema.getShortEndedElements();
        tokenRegExp = /<([!?\/])?([A-Za-z0-9\-_\:\.]+)((?:\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\/|\s+)>/g;
        tokenRegExp.lastIndex = index = startIndex;

        while ((matches = tokenRegExp.exec(html))) {
            index = tokenRegExp.lastIndex;

            if (matches[1] === '/') { // End element
                count--;
            } else if (!matches[1]) { // Start element
                if (matches[2] in shortEndedElements) {
                    continue;
                }

                count++;
            }

            if (count === 0) {
                break;
            }
        }

        return index;
    }

    /**
     * Constructs a new SaxParser instance.
     *
     * @constructor
     * @method SaxParser
     * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks.
     * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing.
     */
    function SaxParser(settings, schema) {
        var self = this;

        function noop() {}

        settings = settings || {};
        self.schema = schema = schema || new Schema();

        if (settings.fix_self_closing !== false) {
            settings.fix_self_closing = true;
        }

        // Add handler functions from settings and setup default handlers
        each('comment cdata text start end pi doctype'.split(' '), function(name) {
            if (name) {
                self[name] = settings[name] || noop;
            }
        });

        /**
         * Parses the specified HTML string and executes the callbacks for each item it finds.
         *
         * @example
         * new SaxParser({...}).parse('<b>text</b>');
         * @method parse
         * @param {String} html Html string to sax parse.
         */
        self.parse = function(html) {
            var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name;
            var isInternalElement, removeInternalElements, shortEndedElements, fillAttrsMap, isShortEnded;
            var validate, elementRule, isValidElement, attr, attribsValue, validAttributesMap, validAttributePatterns;
            var attributesRequired, attributesDefault, attributesForced;
            var anyAttributesRequired, selfClosing, tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0;
            var decode = Entities.decode, fixSelfClosing, filteredUrlAttrs = Tools.makeMap('src,href,data,background,formaction,poster');
            var scriptUriRegExp = /((java|vb)script|mhtml):/i, dataUriRegExp = /^data:/i;

            function processEndTag(name) {
                var pos, i;

                // Find position of parent of the same type
                pos = stack.length;
                while (pos--) {
                    if (stack[pos].name === name) {
                        break;
                    }
                }

                // Found parent
                if (pos >= 0) {
                    // Close all the open elements
                    for (i = stack.length - 1; i >= pos; i--) {
                        name = stack[i];

                        if (name.valid) {
                            self.end(name.name);
                        }
                    }

                    // Remove the open elements from the stack
                    stack.length = pos;
                }
            }

            function parseAttribute(match, name, value, val2, val3) {
                var attrRule, i, trimRegExp = /[\s\u0000-\u001F]+/g;

                name = name.toLowerCase();
                value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute

                // Validate name and value pass through all data- attributes
                if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
                    attrRule = validAttributesMap[name];

                    // Find rule by pattern matching
                    if (!attrRule && validAttributePatterns) {
                        i = validAttributePatterns.length;
                        while (i--) {
                            attrRule = validAttributePatterns[i];
                            if (attrRule.pattern.test(name)) {
                                break;
                            }
                        }

                        // No rule matched
                        if (i === -1) {
                            attrRule = null;
                        }
                    }

                    // No attribute rule found
                    if (!attrRule) {
                        return;
                    }

                    // Validate value
                    if (attrRule.validValues && !(value in attrRule.validValues)) {
                        return;
                    }
                }

                // Block any javascript: urls or non image data uris
                if (filteredUrlAttrs[name] && !settings.allow_script_urls) {
                    var uri = value.replace(trimRegExp, '');

                    try {
                        // Might throw malformed URI sequence
                        uri = decodeURIComponent(uri);
                    } catch (ex) {
                        // Fallback to non UTF-8 decoder
                        uri = unescape(uri);
                    }

                    if (scriptUriRegExp.test(uri)) {
                        return;
                    }

                    if (!settings.allow_html_data_urls && dataUriRegExp.test(uri) && !/^data:image\//i.test(uri)) {
                        return;
                    }
                }

                // Add attribute to list and map
                attrList.map[name] = value;
                attrList.push({
                    name: name,
                    value: value
                });
            }

            // Precompile RegExps and map objects
            tokenRegExp = new RegExp('<(?:' +
                '(?:!--([\\w\\W]*?)-->)|' + // Comment
                '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
                '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
                '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
                '(?:\\/([^>]+)>)|' + // End element
                '(?:([A-Za-z0-9\\-_\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
            ')', 'g');

            attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g;

            // Setup lookup tables for empty elements and boolean attributes
            shortEndedElements = schema.getShortEndedElements();
            selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
            fillAttrsMap = schema.getBoolAttrs();
            validate = settings.validate;
            removeInternalElements = settings.remove_internals;
            fixSelfClosing = settings.fix_self_closing;
            specialElements = schema.getSpecialElements();

            while ((matches = tokenRegExp.exec(html))) {
                // Text
                if (index < matches.index) {
                    self.text(decode(html.substr(index, matches.index - index)));
                }

                if ((value = matches[6])) { // End element
                    value = value.toLowerCase();

                    // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
                    if (value.charAt(0) === ':') {
                        value = value.substr(1);
                    }

                    processEndTag(value);
                } else if ((value = matches[7])) { // Start element
                    value = value.toLowerCase();

                    // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
                    if (value.charAt(0) === ':') {
                        value = value.substr(1);
                    }

                    isShortEnded = value in shortEndedElements;

                    // Is self closing tag for example an <li> after an open <li>
                    if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) {
                        processEndTag(value);
                    }

                    // Validate element
                    if (!validate || (elementRule = schema.getElementRule(value))) {
                        isValidElement = true;

                        // Grab attributes map and patters when validation is enabled
                        if (validate) {
                            validAttributesMap = elementRule.attributes;
                            validAttributePatterns = elementRule.attributePatterns;
                        }

                        // Parse attributes
                        if ((attribsValue = matches[8])) {
                            isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element

                            // If the element has internal attributes then remove it if we are told to do so
                            if (isInternalElement && removeInternalElements) {
                                isValidElement = false;
                            }

                            attrList = [];
                            attrList.map = {};

                            attribsValue.replace(attrRegExp, parseAttribute);
                        } else {
                            attrList = [];
                            attrList.map = {};
                        }

                        // Process attributes if validation is enabled
                        if (validate && !isInternalElement) {
                            attributesRequired = elementRule.attributesRequired;
                            attributesDefault = elementRule.attributesDefault;
                            attributesForced = elementRule.attributesForced;
                            anyAttributesRequired = elementRule.removeEmptyAttrs;

                            // Check if any attribute exists
                            if (anyAttributesRequired && !attrList.length) {
                                isValidElement = false;
                            }

                            // Handle forced attributes
                            if (attributesForced) {
                                i = attributesForced.length;
                                while (i--) {
                                    attr = attributesForced[i];
                                    name = attr.name;
                                    attrValue = attr.value;

                                    if (attrValue === '{$uid}') {
                                        attrValue = 'mce_' + idCount++;
                                    }

                                    attrList.map[name] = attrValue;
                                    attrList.push({name: name, value: attrValue});
                                }
                            }

                            // Handle default attributes
                            if (attributesDefault) {
                                i = attributesDefault.length;
                                while (i--) {
                                    attr = attributesDefault[i];
                                    name = attr.name;

                                    if (!(name in attrList.map)) {
                                        attrValue = attr.value;

                                        if (attrValue === '{$uid}') {
                                            attrValue = 'mce_' + idCount++;
                                        }

                                        attrList.map[name] = attrValue;
                                        attrList.push({name: name, value: attrValue});
                                    }
                                }
                            }

                            // Handle required attributes
                            if (attributesRequired) {
                                i = attributesRequired.length;
                                while (i--) {
                                    if (attributesRequired[i] in attrList.map) {
                                        break;
                                    }
                                }

                                // None of the required attributes where found
                                if (i === -1) {
                                    isValidElement = false;
                                }
                            }

                            // Invalidate element if it's marked as bogus
                            if ((attr = attrList.map['data-mce-bogus'])) {
                                if (attr === 'all') {
                                    index = findEndTag(schema, html, tokenRegExp.lastIndex);
                                    tokenRegExp.lastIndex = index;
                                    continue;
                                }

                                isValidElement = false;
                            }
                        }

                        if (isValidElement) {
                            self.start(value, attrList, isShortEnded);
                        }
                    } else {
                        isValidElement = false;
                    }

                    // Treat script, noscript and style a bit different since they may include code that looks like elements
                    if ((endRegExp = specialElements[value])) {
                        endRegExp.lastIndex = index = matches.index + matches[0].length;

                        if ((matches = endRegExp.exec(html))) {
                            if (isValidElement) {
                                text = html.substr(index, matches.index - index);
                            }

                            index = matches.index + matches[0].length;
                        } else {
                            text = html.substr(index);
                            index = html.length;
                        }

                        if (isValidElement) {
                            if (text.length > 0) {
                                self.text(text, true);
                            }

                            self.end(value);
                        }

                        tokenRegExp.lastIndex = index;
                        continue;
                    }

                    // Push value on to stack
                    if (!isShortEnded) {
                        if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) {
                            stack.push({name: value, valid: isValidElement});
                        } else if (isValidElement) {
                            self.end(value);
                        }
                    }
                } else if ((value = matches[1])) { // Comment
                    // Padd comment value to avoid browsers from parsing invalid comments as HTML
                    if (value.charAt(0) === '>') {
                        value = ' ' + value;
                    }

                    if (!settings.allow_conditional_comments && value.substr(0, 3) === '[if') {
                        value = ' ' + value;
                    }

                    self.comment(value);
                } else if ((value = matches[2])) { // CDATA
                    self.cdata(value);
                } else if ((value = matches[3])) { // DOCTYPE
                    self.doctype(value);
                } else if ((value = matches[4])) { // PI
                    self.pi(value, matches[5]);
                }

                index = matches.index + matches[0].length;
            }

            // Text
            if (index < html.length) {
                self.text(decode(html.substr(index)));
            }

            // Close any open elements
            for (i = stack.length - 1; i >= 0; i--) {
                value = stack[i];

                if (value.valid) {
                    self.end(value.name);
                }
            }
        };
    }

    SaxParser.findEndTag = findEndTag;

    return SaxParser;
});

// Included from: js/tinymce/classes/html/DomParser.js

/**
 * DomParser.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class parses HTML code into a DOM like structure of nodes it will remove redundant whitespace and make
 * sure that the node tree is valid according to the specified schema.
 * So for example: <p>a<p>b</p>c</p> will become <p>a</p><p>b</p><p>c</p>
 *
 * @example
 * var parser = new tinymce.html.DomParser({validate: true}, schema);
 * var rootNode = parser.parse('<h1>content</h1>');
 *
 * @class tinymce.html.DomParser
 * @version 3.4
 */
define("tinymce/html/DomParser", [
    "tinymce/html/Node",
    "tinymce/html/Schema",
    "tinymce/html/SaxParser",
    "tinymce/util/Tools"
], function(Node, Schema, SaxParser, Tools) {
    var makeMap = Tools.makeMap, each = Tools.each, explode = Tools.explode, extend = Tools.extend;

    /**
     * Constructs a new DomParser instance.
     *
     * @constructor
     * @method DomParser
     * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks.
     * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing.
     */
    return function(settings, schema) {
        var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};

        settings = settings || {};
        settings.validate = "validate" in settings ? settings.validate : true;
        settings.root_name = settings.root_name || 'body';
        self.schema = schema = schema || new Schema();

        function fixInvalidChildren(nodes) {
            var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i;
            var nonEmptyElements, nonSplitableElements, textBlockElements, sibling, nextNode;

            nonSplitableElements = makeMap('tr,td,th,tbody,thead,tfoot,table');
            nonEmptyElements = schema.getNonEmptyElements();
            textBlockElements = schema.getTextBlockElements();

            for (ni = 0; ni < nodes.length; ni++) {
                node = nodes[ni];

                // Already removed or fixed
                if (!node.parent || node.fixed) {
                    continue;
                }

                // If the invalid element is a text block and the text block is within a parent LI element
                // Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office
                if (textBlockElements[node.name] && node.parent.name == 'li') {
                    // Move sibling text blocks after LI element
                    sibling = node.next;
                    while (sibling) {
                        if (textBlockElements[sibling.name]) {
                            sibling.name = 'li';
                            sibling.fixed = true;
                            node.parent.insert(sibling, node.parent);
                        } else {
                            break;
                        }

                        sibling = sibling.next;
                    }

                    // Unwrap current text block
                    node.unwrap(node);
                    continue;
                }

                // Get list of all parent nodes until we find a valid parent to stick the child into
                parents = [node];
                for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) &&
                    !nonSplitableElements[parent.name]; parent = parent.parent) {
                    parents.push(parent);
                }

                // Found a suitable parent
                if (parent && parents.length > 1) {
                    // Reverse the array since it makes looping easier
                    parents.reverse();

                    // Clone the related parent and insert that after the moved node
                    newParent = currentNode = self.filterNode(parents[0].clone());

                    // Start cloning and moving children on the left side of the target node
                    for (i = 0; i < parents.length - 1; i++) {
                        if (schema.isValidChild(currentNode.name, parents[i].name)) {
                            tempNode = self.filterNode(parents[i].clone());
                            currentNode.append(tempNode);
                        } else {
                            tempNode = currentNode;
                        }

                        for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
                            nextNode = childNode.next;
                            tempNode.append(childNode);
                            childNode = nextNode;
                        }

                        currentNode = tempNode;
                    }

                    if (!newParent.isEmpty(nonEmptyElements)) {
                        parent.insert(newParent, parents[0], true);
                        parent.insert(node, newParent);
                    } else {
                        parent.insert(node, parents[0], true);
                    }

                    // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
                    parent = parents[0];
                    if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
                        parent.empty().remove();
                    }
                } else if (node.parent) {
                    // If it's an LI try to find a UL/OL for it or wrap it
                    if (node.name === 'li') {
                        sibling = node.prev;
                        if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
                            sibling.append(node);
                            continue;
                        }

                        sibling = node.next;
                        if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
                            sibling.insert(node, sibling.firstChild, true);
                            continue;
                        }

                        node.wrap(self.filterNode(new Node('ul', 1)));
                        continue;
                    }

                    // Try wrapping the element in a DIV
                    if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
                        node.wrap(self.filterNode(new Node('div', 1)));
                    } else {
                        // We failed wrapping it, then remove or unwrap it
                        if (node.name === 'style' || node.name === 'script') {
                            node.empty().remove();
                        } else {
                            node.unwrap();
                        }
                    }
                }
            }
        }

        /**
         * Runs the specified node though the element and attributes filters.
         *
         * @method filterNode
         * @param {tinymce.html.Node} Node the node to run filters on.
         * @return {tinymce.html.Node} The passed in node.
         */
        self.filterNode = function(node) {
            var i, name, list;

            // Run element filters
            if (name in nodeFilters) {
                list = matchedNodes[name];

                if (list) {
                    list.push(node);
                } else {
                    matchedNodes[name] = [node];
                }
            }

            // Run attribute filters
            i = attributeFilters.length;
            while (i--) {
                name = attributeFilters[i].name;

                if (name in node.attributes.map) {
                    list = matchedAttributes[name];

                    if (list) {
                        list.push(node);
                    } else {
                        matchedAttributes[name] = [node];
                    }
                }
            }

            return node;
        };

        /**
         * Adds a node filter function to the parser, the parser will collect the specified nodes by name
         * and then execute the callback ones it has finished parsing the document.
         *
         * @example
         * parser.addNodeFilter('p,h1', function(nodes, name) {
         *        for (var i = 0; i < nodes.length; i++) {
         *            console.log(nodes[i].name);
         *        }
         * });
         * @method addNodeFilter
         * @method {String} name Comma separated list of nodes to collect.
         * @param {function} callback Callback function to execute once it has collected nodes.
         */
        self.addNodeFilter = function(name, callback) {
            each(explode(name), function(name) {
                var list = nodeFilters[name];

                if (!list) {
                    nodeFilters[name] = list = [];
                }

                list.push(callback);
            });
        };

        /**
         * Adds a attribute filter function to the parser, the parser will collect nodes that has the specified attributes
         * and then execute the callback ones it has finished parsing the document.
         *
         * @example
         * parser.addAttributeFilter('src,href', function(nodes, name) {
         *        for (var i = 0; i < nodes.length; i++) {
         *            console.log(nodes[i].name);
         *        }
         * });
         * @method addAttributeFilter
         * @method {String} name Comma separated list of nodes to collect.
         * @param {function} callback Callback function to execute once it has collected nodes.
         */
        self.addAttributeFilter = function(name, callback) {
            each(explode(name), function(name) {
                var i;

                for (i = 0; i < attributeFilters.length; i++) {
                    if (attributeFilters[i].name === name) {
                        attributeFilters[i].callbacks.push(callback);
                        return;
                    }
                }

                attributeFilters.push({name: name, callbacks: [callback]});
            });
        };

        /**
         * Parses the specified HTML string into a DOM like node tree and returns the result.
         *
         * @example
         * var rootNode = new DomParser({...}).parse('<b>text</b>');
         * @method parse
         * @param {String} html Html string to sax parse.
         * @param {Object} args Optional args object that gets passed to all filter functions.
         * @return {tinymce.html.Node} Root node containing the tree.
         */
        self.parse = function(html, args) {
            var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate;
            var blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement;
            var endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements;
            var children, nonEmptyElements, rootBlockName;

            args = args || {};
            matchedNodes = {};
            matchedAttributes = {};
            blockElements = extend(makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
            nonEmptyElements = schema.getNonEmptyElements();
            children = schema.children;
            validate = settings.validate;
            rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;

            whiteSpaceElements = schema.getWhiteSpaceElements();
            startWhiteSpaceRegExp = /^[ \t\r\n]+/;
            endWhiteSpaceRegExp = /[ \t\r\n]+$/;
            allWhiteSpaceRegExp = /[ \t\r\n]+/g;
            isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;

            function addRootBlocks() {
                var node = rootNode.firstChild, next, rootBlockNode;

                // Removes whitespace at beginning and end of block so:
                // <p> x </p> -> <p>x</p>
                function trim(rootBlockNode) {
                    if (rootBlockNode) {
                        node = rootBlockNode.firstChild;
                        if (node && node.type == 3) {
                            node.value = node.value.replace(startWhiteSpaceRegExp, '');
                        }

                        node = rootBlockNode.lastChild;
                        if (node && node.type == 3) {
                            node.value = node.value.replace(endWhiteSpaceRegExp, '');
                        }
                    }
                }

                // Check if rootBlock is valid within rootNode for example if P is valid in H1 if H1 is the contentEditabe root
                if (!schema.isValidChild(rootNode.name, rootBlockName.toLowerCase())) {
                    return;
                }

                while (node) {
                    next = node.next;

                    if (node.type == 3 || (node.type == 1 && node.name !== 'p' &&
                        !blockElements[node.name] && !node.attr('data-mce-type'))) {
                        if (!rootBlockNode) {
                            // Create a new root block element
                            rootBlockNode = createNode(rootBlockName, 1);
                            rootBlockNode.attr(settings.forced_root_block_attrs);
                            rootNode.insert(rootBlockNode, node);
                            rootBlockNode.append(node);
                        } else {
                            rootBlockNode.append(node);
                        }
                    } else {
                        trim(rootBlockNode);
                        rootBlockNode = null;
                    }

                    node = next;
                }

                trim(rootBlockNode);
            }

            function createNode(name, type) {
                var node = new Node(name, type), list;

                if (name in nodeFilters) {
                    list = matchedNodes[name];

                    if (list) {
                        list.push(node);
                    } else {
                        matchedNodes[name] = [node];
                    }
                }

                return node;
            }

            function removeWhitespaceBefore(node) {
                var textNode, textVal, sibling;

                for (textNode = node.prev; textNode && textNode.type === 3; ) {
                    textVal = textNode.value.replace(endWhiteSpaceRegExp, '');

                    if (textVal.length > 0) {
                        textNode.value = textVal;
                        textNode = textNode.prev;
                    } else {
                        sibling = textNode.prev;
                        textNode.remove();
                        textNode = sibling;
                    }
                }
            }

            function cloneAndExcludeBlocks(input) {
                var name, output = {};

                for (name in input) {
                    if (name !== 'li' && name != 'p') {
                        output[name] = input[name];
                    }
                }

                return output;
            }

            parser = new SaxParser({
                validate: validate,
                allow_script_urls: settings.allow_script_urls,
                allow_conditional_comments: settings.allow_conditional_comments,

                // Exclude P and LI from DOM parsing since it's treated better by the DOM parser
                self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),

                cdata: function(text) {
                    node.append(createNode('#cdata', 4)).value = text;
                },

                text: function(text, raw) {
                    var textNode;

                    // Trim all redundant whitespace on non white space elements
                    if (!isInWhiteSpacePreservedElement) {
                        text = text.replace(allWhiteSpaceRegExp, ' ');

                        if (node.lastChild && blockElements[node.lastChild.name]) {
                            text = text.replace(startWhiteSpaceRegExp, '');
                        }
                    }

                    // Do we need to create the node
                    if (text.length !== 0) {
                        textNode = createNode('#text', 3);
                        textNode.raw = !!raw;
                        node.append(textNode).value = text;
                    }
                },

                comment: function(text) {
                    node.append(createNode('#comment', 8)).value = text;
                },

                pi: function(name, text) {
                    node.append(createNode(name, 7)).value = text;
                    removeWhitespaceBefore(node);
                },

                doctype: function(text) {
                    var newNode;

                    newNode = node.append(createNode('#doctype', 10));
                    newNode.value = text;
                    removeWhitespaceBefore(node);
                },

                start: function(name, attrs, empty) {
                    var newNode, attrFiltersLen, elementRule, attrName, parent;

                    elementRule = validate ? schema.getElementRule(name) : {};
                    if (elementRule) {
                        newNode = createNode(elementRule.outputName || name, 1);
                        newNode.attributes = attrs;
                        newNode.shortEnded = empty;

                        node.append(newNode);

                        // Check if node is valid child of the parent node is the child is
                        // unknown we don't collect it since it's probably a custom element
                        parent = children[node.name];
                        if (parent && children[newNode.name] && !parent[newNode.name]) {
                            invalidChildren.push(newNode);
                        }

                        attrFiltersLen = attributeFilters.length;
                        while (attrFiltersLen--) {
                            attrName = attributeFilters[attrFiltersLen].name;

                            if (attrName in attrs.map) {
                                list = matchedAttributes[attrName];

                                if (list) {
                                    list.push(newNode);
                                } else {
                                    matchedAttributes[attrName] = [newNode];
                                }
                            }
                        }

                        // Trim whitespace before block
                        if (blockElements[name]) {
                            removeWhitespaceBefore(newNode);
                        }

                        // Change current node if the element wasn't empty i.e not <br /> or <img />
                        if (!empty) {
                            node = newNode;
                        }

                        // Check if we are inside a whitespace preserved element
                        if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
                            isInWhiteSpacePreservedElement = true;
                        }
                    }
                },

                end: function(name) {
                    var textNode, elementRule, text, sibling, tempNode;

                    elementRule = validate ? schema.getElementRule(name) : {};
                    if (elementRule) {
                        if (blockElements[name]) {
                            if (!isInWhiteSpacePreservedElement) {
                                // Trim whitespace of the first node in a block
                                textNode = node.firstChild;
                                if (textNode && textNode.type === 3) {
                                    text = textNode.value.replace(startWhiteSpaceRegExp, '');

                                    // Any characters left after trim or should we remove it
                                    if (text.length > 0) {
                                        textNode.value = text;
                                        textNode = textNode.next;
                                    } else {
                                        sibling = textNode.next;
                                        textNode.remove();
                                        textNode = sibling;

                                        // Remove any pure whitespace siblings
                                        while (textNode && textNode.type === 3) {
                                            text = textNode.value;
                                            sibling = textNode.next;

                                            if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
                                                textNode.remove();
                                                textNode = sibling;
                                            }

                                            textNode = sibling;
                                        }
                                    }
                                }

                                // Trim whitespace of the last node in a block
                                textNode = node.lastChild;
                                if (textNode && textNode.type === 3) {
                                    text = textNode.value.replace(endWhiteSpaceRegExp, '');

                                    // Any characters left after trim or should we remove it
                                    if (text.length > 0) {
                                        textNode.value = text;
                                        textNode = textNode.prev;
                                    } else {
                                        sibling = textNode.prev;
                                        textNode.remove();
                                        textNode = sibling;

                                        // Remove any pure whitespace siblings
                                        while (textNode && textNode.type === 3) {
                                            text = textNode.value;
                                            sibling = textNode.prev;

                                            if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
                                                textNode.remove();
                                                textNode = sibling;
                                            }

                                            textNode = sibling;
                                        }
                                    }
                                }
                            }

                            // Trim start white space
                            // Removed due to: #5424
                            /*textNode = node.prev;
                            if (textNode && textNode.type === 3) {
                                text = textNode.value.replace(startWhiteSpaceRegExp, '');

                                if (text.length > 0)
                                    textNode.value = text;
                                else
                                    textNode.remove();
                            }*/
                        }

                        // Check if we exited a whitespace preserved element
                        if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
                            isInWhiteSpacePreservedElement = false;
                        }

                        // Handle empty nodes
                        if (elementRule.removeEmpty || elementRule.paddEmpty) {
                            if (node.isEmpty(nonEmptyElements)) {
                                if (elementRule.paddEmpty) {
                                    node.empty().append(new Node('#text', '3')).value = '\u00a0';
                                } else {
                                    // Leave nodes that have a name like <a name="name">
                                    if (!node.attributes.map.name && !node.attributes.map.id) {
                                        tempNode = node.parent;
                                        node.unwrap();
                                        node = tempNode;
                                        return;
                                    }
                                }
                            }
                        }

                        node = node.parent;
                    }
                }
            }, schema);

            rootNode = node = new Node(args.context || settings.root_name, 11);

            parser.parse(html);

            // Fix invalid children or report invalid children in a contextual parsing
            if (validate && invalidChildren.length) {
                if (!args.context) {
                    fixInvalidChildren(invalidChildren);
                } else {
                    args.invalid = true;
                }
            }

            // Wrap nodes in the root into block elements if the root is body
            if (rootBlockName && (rootNode.name == 'body' || args.isRootContent)) {
                addRootBlocks();
            }

            // Run filters only when the contents is valid
            if (!args.invalid) {
                // Run node filters
                for (name in matchedNodes) {
                    list = nodeFilters[name];
                    nodes = matchedNodes[name];

                    // Remove already removed children
                    fi = nodes.length;
                    while (fi--) {
                        if (!nodes[fi].parent) {
                            nodes.splice(fi, 1);
                        }
                    }

                    for (i = 0, l = list.length; i < l; i++) {
                        list[i](nodes, name, args);
                    }
                }

                // Run attribute filters
                for (i = 0, l = attributeFilters.length; i < l; i++) {
                    list = attributeFilters[i];

                    if (list.name in matchedAttributes) {
                        nodes = matchedAttributes[list.name];

                        // Remove already removed children
                        fi = nodes.length;
                        while (fi--) {
                            if (!nodes[fi].parent) {
                                nodes.splice(fi, 1);
                            }
                        }

                        for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) {
                            list.callbacks[fi](nodes, list.name, args);
                        }
                    }
                }
            }

            return rootNode;
        };

        // Remove <br> at end of block elements Gecko and WebKit injects BR elements to
        // make it possible to place the caret inside empty blocks. This logic tries to remove
        // these elements and keep br elements that where intended to be there intact
        if (settings.remove_trailing_brs) {
            self.addNodeFilter('br', function(nodes) {
                var i, l = nodes.length, node, blockElements = extend({}, schema.getBlockElements());
                var nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
                var elementRule, textNode;

                // Remove brs from body element as well
                blockElements.body = 1;

                // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
                for (i = 0; i < l; i++) {
                    node = nodes[i];
                    parent = node.parent;

                    if (blockElements[node.parent.name] && node === parent.lastChild) {
                        // Loop all nodes to the left of the current node and check for other BR elements
                        // excluding bookmarks since they are invisible
                        prev = node.prev;
                        while (prev) {
                            prevName = prev.name;

                            // Ignore bookmarks
                            if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
                                // Found a non BR element
                                if (prevName !== "br") {
                                    break;
                                }

                                // Found another br it's a <br><br> structure then don't remove anything
                                if (prevName === 'br') {
                                    node = null;
                                    break;
                                }
                            }

                            prev = prev.prev;
                        }

                        if (node) {
                            node.remove();

                            // Is the parent to be considered empty after we removed the BR
                            if (parent.isEmpty(nonEmptyElements)) {
                                elementRule = schema.getElementRule(parent.name);

                                // Remove or padd the element depending on schema rule
                                if (elementRule) {
                                    if (elementRule.removeEmpty) {
                                        parent.remove();
                                    } else if (elementRule.paddEmpty) {
                                        parent.empty().append(new Node('#text', 3)).value = '\u00a0';
                                    }
                                }
                            }
                        }
                    } else {
                        // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p>
                        // so they become <p><b><i>&nbsp;</i></b></p>
                        lastParent = node;
                        while (parent && parent.firstChild === lastParent && parent.lastChild === lastParent) {
                            lastParent = parent;

                            if (blockElements[parent.name]) {
                                break;
                            }

                            parent = parent.parent;
                        }

                        if (lastParent === parent) {
                            textNode = new Node('#text', 3);
                            textNode.value = '\u00a0';
                            node.replace(textNode);
                        }
                    }
                }
            });
        }

        // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
        if (!settings.allow_html_in_named_anchor) {
            self.addAttributeFilter('id,name', function(nodes) {
                var i = nodes.length, sibling, prevSibling, parent, node;

                while (i--) {
                    node = nodes[i];
                    if (node.name === 'a' && node.firstChild && !node.attr('href')) {
                        parent = node.parent;

                        // Move children after current node
                        sibling = node.lastChild;
                        do {
                            prevSibling = sibling.prev;
                            parent.insert(sibling, node);
                            sibling = prevSibling;
                        } while (sibling);
                    }
                }
            });
        }

        if (settings.validate && schema.getValidClasses()) {
            self.addAttributeFilter('class', function(nodes) {
                var i = nodes.length, node, classList, ci, className, classValue;
                var validClasses = schema.getValidClasses(), validClassesMap, valid;

                while (i--) {
                    node = nodes[i];
                    classList = node.attr('class').split(' ');
                    classValue = '';

                    for (ci = 0; ci < classList.length; ci++) {
                        className = classList[ci];
                        valid = false;

                        validClassesMap = validClasses['*'];
                        if (validClassesMap && validClassesMap[className]) {
                            valid = true;
                        }

                        validClassesMap = validClasses[node.name];
                        if (!valid && validClassesMap && !validClassesMap[className]) {
                            valid = true;
                        }

                        if (valid) {
                            if (classValue) {
                                classValue += ' ';
                            }

                            classValue += className;
                        }
                    }

                    if (!classValue.length) {
                        classValue = null;
                    }

                    node.attr('class', classValue);
                }
            });
        }
    };
});

// Included from: js/tinymce/classes/html/Writer.js

/**
 * Writer.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class is used to write HTML tags out it can be used with the Serializer or the SaxParser.
 *
 * @class tinymce.html.Writer
 * @example
 * var writer = new tinymce.html.Writer({indent: true});
 * var parser = new tinymce.html.SaxParser(writer).parse('<p><br></p>');
 * console.log(writer.getContent());
 *
 * @class tinymce.html.Writer
 * @version 3.4
 */
define("tinymce/html/Writer", [
    "tinymce/html/Entities",
    "tinymce/util/Tools"
], function(Entities, Tools) {
    var makeMap = Tools.makeMap;

    /**
     * Constructs a new Writer instance.
     *
     * @constructor
     * @method Writer
     * @param {Object} settings Name/value settings object.
     */
    return function(settings) {
        var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;

        settings = settings || {};
        indent = settings.indent;
        indentBefore = makeMap(settings.indent_before || '');
        indentAfter = makeMap(settings.indent_after || '');
        encode = Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
        htmlOutput = settings.element_format == "html";

        return {
            /**
             * Writes the a start element such as <p id="a">.
             *
             * @method start
             * @param {String} name Name of the element.
             * @param {Array} attrs Optional attribute array or undefined if it hasn't any.
             * @param {Boolean} empty Optional empty state if the tag should end like <br />.
             */
            start: function(name, attrs, empty) {
                var i, l, attr, value;

                if (indent && indentBefore[name] && html.length > 0) {
                    value = html[html.length - 1];

                    if (value.length > 0 && value !== '\n') {
                        html.push('\n');
                    }
                }

                html.push('<', name);

                if (attrs) {
                    for (i = 0, l = attrs.length; i < l; i++) {
                        attr = attrs[i];
                        html.push(' ', attr.name, '="', encode(attr.value, true), '"');
                    }
                }

                if (!empty || htmlOutput) {
                    html[html.length] = '>';
                } else {
                    html[html.length] = ' />';
                }

                if (empty && indent && indentAfter[name] && html.length > 0) {
                    value = html[html.length - 1];

                    if (value.length > 0 && value !== '\n') {
                        html.push('\n');
                    }
                }
            },

            /**
             * Writes the a end element such as </p>.
             *
             * @method end
             * @param {String} name Name of the element.
             */
            end: function(name) {
                var value;

                /*if (indent && indentBefore[name] && html.length > 0) {
                    value = html[html.length - 1];

                    if (value.length > 0 && value !== '\n')
                        html.push('\n');
                }*/

                html.push('</', name, '>');

                if (indent && indentAfter[name] && html.length > 0) {
                    value = html[html.length - 1];

                    if (value.length > 0 && value !== '\n') {
                        html.push('\n');
                    }
                }
            },

            /**
             * Writes a text node.
             *
             * @method text
             * @param {String} text String to write out.
             * @param {Boolean} raw Optional raw state if true the contents wont get encoded.
             */
            text: function(text, raw) {
                if (text.length > 0) {
                    html[html.length] = raw ? text : encode(text);
                }
            },

            /**
             * Writes a cdata node such as <![CDATA[data]]>.
             *
             * @method cdata
             * @param {String} text String to write out inside the cdata.
             */
            cdata: function(text) {
                html.push('<![CDATA[', text, ']]>');
            },

            /**
             * Writes a comment node such as <!-- Comment -->.
             *
             * @method cdata
             * @param {String} text String to write out inside the comment.
             */
            comment: function(text) {
                html.push('<!--', text, '-->');
            },

            /**
             * Writes a PI node such as <?xml attr="value" ?>.
             *
             * @method pi
             * @param {String} name Name of the pi.
             * @param {String} text String to write out inside the pi.
             */
            pi: function(name, text) {
                if (text) {
                    html.push('<?', name, ' ', text, '?>');
                } else {
                    html.push('<?', name, '?>');
                }

                if (indent) {
                    html.push('\n');
                }
            },

            /**
             * Writes a doctype node such as <!DOCTYPE data>.
             *
             * @method doctype
             * @param {String} text String to write out inside the doctype.
             */
            doctype: function(text) {
                html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
            },

            /**
             * Resets the internal buffer if one wants to reuse the writer.
             *
             * @method reset
             */
            reset: function() {
                html.length = 0;
            },

            /**
             * Returns the contents that got serialized.
             *
             * @method getContent
             * @return {String} HTML contents that got written down.
             */
            getContent: function() {
                return html.join('').replace(/\n$/, '');
            }
        };
    };
});

// Included from: js/tinymce/classes/html/Serializer.js

/**
 * Serializer.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class is used to serialize down the DOM tree into a string using a Writer instance.
 *
 *
 * @example
 * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>'));
 * @class tinymce.html.Serializer
 * @version 3.4
 */
define("tinymce/html/Serializer", [
    "tinymce/html/Writer",
    "tinymce/html/Schema"
], function(Writer, Schema) {
    /**
     * Constructs a new Serializer instance.
     *
     * @constructor
     * @method Serializer
     * @param {Object} settings Name/value settings object.
     * @param {tinymce.html.Schema} schema Schema instance to use.
     */
    return function(settings, schema) {
        var self = this, writer = new Writer(settings);

        settings = settings || {};
        settings.validate = "validate" in settings ? settings.validate : true;

        self.schema = schema = schema || new Schema();
        self.writer = writer;

        /**
         * Serializes the specified node into a string.
         *
         * @example
         * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>'));
         * @method serialize
         * @param {tinymce.html.Node} node Node instance to serialize.
         * @return {String} String with HTML based on DOM tree.
         */
        self.serialize = function(node) {
            var handlers, validate;

            validate = settings.validate;

            handlers = {
                // #text
                3: function(node) {
                    writer.text(node.value, node.raw);
                },

                // #comment
                8: function(node) {
                    writer.comment(node.value);
                },

                // Processing instruction
                7: function(node) {
                    writer.pi(node.name, node.value);
                },

                // Doctype
                10: function(node) {
                    writer.doctype(node.value);
                },

                // CDATA
                4: function(node) {
                    writer.cdata(node.value);
                },

                // Document fragment
                11: function(node) {
                    if ((node = node.firstChild)) {
                        do {
                            walk(node);
                        } while ((node = node.next));
                    }
                }
            };

            writer.reset();

            function walk(node) {
                var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;

                if (!handler) {
                    name = node.name;
                    isEmpty = node.shortEnded;
                    attrs = node.attributes;

                    // Sort attributes
                    if (validate && attrs && attrs.length > 1) {
                        sortedAttrs = [];
                        sortedAttrs.map = {};

                        elementRule = schema.getElementRule(node.name);
                        for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
                            attrName = elementRule.attributesOrder[i];

                            if (attrName in attrs.map) {
                                attrValue = attrs.map[attrName];
                                sortedAttrs.map[attrName] = attrValue;
                                sortedAttrs.push({name: attrName, value: attrValue});
                            }
                        }

                        for (i = 0, l = attrs.length; i < l; i++) {
                            attrName = attrs[i].name;

                            if (!(attrName in sortedAttrs.map)) {
                                attrValue = attrs.map[attrName];
                                sortedAttrs.map[attrName] = attrValue;
                                sortedAttrs.push({name: attrName, value: attrValue});
                            }
                        }

                        attrs = sortedAttrs;
                    }

                    writer.start(node.name, attrs, isEmpty);

                    if (!isEmpty) {
                        if ((node = node.firstChild)) {
                            do {
                                walk(node);
                            } while ((node = node.next));
                        }

                        writer.end(name);
                    }
                } else {
                    handler(node);
                }
            }

            // Serialize element and treat all non elements as fragments
            if (node.type == 1 && !settings.inner) {
                walk(node);
            } else {
                handlers[11](node);
            }

            return writer.getContent();
        };
    };
});

// Included from: js/tinymce/classes/dom/Serializer.js

/**
 * Serializer.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class is used to serialize DOM trees into a string. Consult the TinyMCE Wiki API for
 * more details and examples on how to use this class.
 *
 * @class tinymce.dom.Serializer
 */
define("tinymce/dom/Serializer", [
    "tinymce/dom/DOMUtils",
    "tinymce/html/DomParser",
    "tinymce/html/Entities",
    "tinymce/html/Serializer",
    "tinymce/html/Node",
    "tinymce/html/Schema",
    "tinymce/Env",
    "tinymce/util/Tools"
], function(DOMUtils, DomParser, Entities, Serializer, Node, Schema, Env, Tools) {
    var each = Tools.each, trim = Tools.trim;
    var DOM = DOMUtils.DOM;

    /**
     * Constructs a new DOM serializer class.
     *
     * @constructor
     * @method Serializer
     * @param {Object} settings Serializer settings object.
     * @param {tinymce.Editor} editor Optional editor to bind events to and get schema/dom from.
     */
    return function(settings, editor) {
        var dom, schema, htmlParser;

        if (editor) {
            dom = editor.dom;
            schema = editor.schema;
        }

        // Default DOM and Schema if they are undefined
        dom = dom || DOM;
        schema = schema || new Schema(settings);
        settings.entity_encoding = settings.entity_encoding || 'named';
        settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;

        htmlParser = new DomParser(settings, schema);

        // Convert tabindex back to elements when serializing contents
        htmlParser.addAttributeFilter('data-mce-tabindex', function(nodes, name) {
            var i = nodes.length, node;

            while (i--) {
                node = nodes[i];
                node.attr('tabindex', node.attributes.map['data-mce-tabindex']);
                node.attr(name, null);
            }
        });

        // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
        htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
            var i = nodes.length, node, value, internalName = 'data-mce-' + name;
            var urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;

            while (i--) {
                node = nodes[i];

                value = node.attributes.map[internalName];
                if (value !== undef) {
                    // Set external name to internal value and remove internal
                    node.attr(name, value.length > 0 ? value : null);
                    node.attr(internalName, null);
                } else {
                    // No internal attribute found then convert the value we have in the DOM
                    value = node.attributes.map[name];

                    if (name === "style") {
                        value = dom.serializeStyle(dom.parseStyle(value), node.name);
                    } else if (urlConverter) {
                        value = urlConverter.call(urlConverterScope, value, name, node.name);
                    }

                    node.attr(name, value.length > 0 ? value : null);
                }
            }
        });

        // Remove internal classes mceItem<..> or mceSelected
        htmlParser.addAttributeFilter('class', function(nodes) {
            var i = nodes.length, node, value;

            while (i--) {
                node = nodes[i];
                value = node.attr('class');

                if (value) {
                    value = node.attr('class').replace(/(?:^|\s)mce-item-\w+(?!\S)/g, '');
                    node.attr('class', value.length > 0 ? value : null);
                }
            }
        });

        // Remove bookmark elements
        htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
            var i = nodes.length, node;

            while (i--) {
                node = nodes[i];

                if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) {
                    node.remove();
                }
            }
        });

        htmlParser.addNodeFilter('noscript', function(nodes) {
            var i = nodes.length, node;

            while (i--) {
                node = nodes[i].firstChild;

                if (node) {
                    node.value = Entities.decode(node.value);
                }
            }
        });

        // Force script into CDATA sections and remove the mce- prefix also add comments around styles
        htmlParser.addNodeFilter('script,style', function(nodes, name) {
            var i = nodes.length, node, value, type;

            function trim(value) {
                /*jshint maxlen:255 */
                /*eslint max-len:0 */
                return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
                        .replace(/^[\r\n]*|[\r\n]*$/g, '')
                        .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
                        .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
            }

            while (i--) {
                node = nodes[i];
                value = node.firstChild ? node.firstChild.value : '';

                if (name === "script") {
                    // Remove mce- prefix from script elements and remove default type since the user specified
                    // a script element without type attribute
                    type = node.attr('type');
                    if (type) {
                        node.attr('type', type == 'mce-no/type' ? null : type.replace(/^mce\-/, ''));
                    }

                    if (value.length > 0) {
                        node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
                    }
                } else {
                    if (value.length > 0) {
                        node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
                    }
                }
            }
        });

        // Convert comments to cdata and handle protected comments
        htmlParser.addNodeFilter('#comment', function(nodes) {
            var i = nodes.length, node;

            while (i--) {
                node = nodes[i];

                if (node.value.indexOf('[CDATA[') === 0) {
                    node.name = '#cdata';
                    node.type = 4;
                    node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
                } else if (node.value.indexOf('mce:protected ') === 0) {
                    node.name = "#text";
                    node.type = 3;
                    node.raw = true;
                    node.value = unescape(node.value).substr(14);
                }
            }
        });

        htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
            var i = nodes.length, node;

            while (i--) {
                node = nodes[i];
                if (node.type === 7) {
                    node.remove();
                } else if (node.type === 1) {
                    if (name === "input" && !("type" in node.attributes.map)) {
                        node.attr('type', 'text');
                    }
                }
            }
        });

        // Fix list elements, TODO: Replace this later
        if (settings.fix_list_elements) {
            htmlParser.addNodeFilter('ul,ol', function(nodes) {
                var i = nodes.length, node, parentNode;

                while (i--) {
                    node = nodes[i];
                    parentNode = node.parent;

                    if (parentNode.name === 'ul' || parentNode.name === 'ol') {
                        if (node.prev && node.prev.name === 'li') {
                            node.prev.append(node);
                        }
                    }
                }
            });
        }

        // Remove internal data attributes
        htmlParser.addAttributeFilter(
            'data-mce-src,data-mce-href,data-mce-style,' +
            'data-mce-selected,data-mce-expando,' +
            'data-mce-type,data-mce-resize',

            function(nodes, name) {
                var i = nodes.length;

                while (i--) {
                    nodes[i].attr(name, null);
                }
            }
        );

        // Return public methods
        return {
            /**
             * Schema instance that was used to when the Serializer was constructed.
             *
             * @field {tinymce.html.Schema} schema
             */
            schema: schema,

            /**
             * Adds a node filter function to the parser used by the serializer, the parser will collect the specified nodes by name
             * and then execute the callback ones it has finished parsing the document.
             *
             * @example
             * parser.addNodeFilter('p,h1', function(nodes, name) {
             *        for (var i = 0; i < nodes.length; i++) {
             *            console.log(nodes[i].name);
             *        }
             * });
             * @method addNodeFilter
             * @method {String} name Comma separated list of nodes to collect.
             * @param {function} callback Callback function to execute once it has collected nodes.
             */
            addNodeFilter: htmlParser.addNodeFilter,

            /**
             * Adds a attribute filter function to the parser used by the serializer, the parser will
             * collect nodes that has the specified attributes
             * and then execute the callback ones it has finished parsing the document.
             *
             * @example
             * parser.addAttributeFilter('src,href', function(nodes, name) {
             *        for (var i = 0; i < nodes.length; i++) {
             *            console.log(nodes[i].name);
             *        }
             * });
             * @method addAttributeFilter
             * @method {String} name Comma separated list of nodes to collect.
             * @param {function} callback Callback function to execute once it has collected nodes.
             */
            addAttributeFilter: htmlParser.addAttributeFilter,

            /**
             * Serializes the specified browser DOM node into a HTML string.
             *
             * @method serialize
             * @param {DOMNode} node DOM node to serialize.
             * @param {Object} args Arguments option that gets passed to event handlers.
             */
            serialize: function(node, args) {
                var self = this, impl, doc, oldDoc, htmlSerializer, content;

                // Explorer won't clone contents of script and style and the
                // selected index of select elements are cleared on a clone operation.
                if (Env.ie && dom.select('script,style,select,map').length > 0) {
                    content = node.innerHTML;
                    node = node.cloneNode(false);
                    dom.setHTML(node, content);
                } else {
                    node = node.cloneNode(true);
                }

                // Nodes needs to be attached to something in WebKit/Opera
                // This fix will make DOM ranges and make Sizzle happy!
                impl = node.ownerDocument.implementation;
                if (impl.createHTMLDocument) {
                    // Create an empty HTML document
                    doc = impl.createHTMLDocument("");

                    // Add the element or it's children if it's a body element to the new document
                    each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
                        doc.body.appendChild(doc.importNode(node, true));
                    });

                    // Grab first child or body element for serialization
                    if (node.nodeName != 'BODY') {
                        node = doc.body.firstChild;
                    } else {
                        node = doc.body;
                    }

                    // set the new document in DOMUtils so createElement etc works
                    oldDoc = dom.doc;
                    dom.doc = doc;
                }

                args = args || {};
                args.format = args.format || 'html';

                // Don't wrap content if we want selected html
                if (args.selection) {
                    args.forced_root_block = '';
                }

                // Pre process
                if (!args.no_events) {
                    args.node = node;
                    self.onPreProcess(args);
                }

                // Setup serializer
                htmlSerializer = new Serializer(settings, schema);

                // Parse and serialize HTML
                args.content = htmlSerializer.serialize(
                    htmlParser.parse(trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args)
                );

                // Replace all BOM characters for now until we can find a better solution
                if (!args.cleanup) {
                    args.content = args.content.replace(/\uFEFF/g, '');
                }

                // Post process
                if (!args.no_events) {
                    self.onPostProcess(args);
                }

                // Restore the old document if it was changed
                if (oldDoc) {
                    dom.doc = oldDoc;
                }

                args.node = null;

                return args.content;
            },

            /**
             * Adds valid elements rules to the serializers schema instance this enables you to specify things
             * like what elements should be outputted and what attributes specific elements might have.
             * Consult the Wiki for more details on this format.
             *
             * @method addRules
             * @param {String} rules Valid elements rules string to add to schema.
             */
            addRules: function(rules) {
                schema.addValidElements(rules);
            },

            /**
             * Sets the valid elements rules to the serializers schema instance this enables you to specify things
             * like what elements should be outputted and what attributes specific elements might have.
             * Consult the Wiki for more details on this format.
             *
             * @method setRules
             * @param {String} rules Valid elements rules string.
             */
            setRules: function(rules) {
                schema.setValidElements(rules);
            },

            onPreProcess: function(args) {
                if (editor) {
                    editor.fire('PreProcess', args);
                }
            },

            onPostProcess: function(args) {
                if (editor) {
                    editor.fire('PostProcess', args);
                }
            }
        };
    };
});

// Included from: js/tinymce/classes/dom/TridentSelection.js

/**
 * TridentSelection.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Selection class for old explorer versions. This one fakes the
 * native selection object available on modern browsers.
 *
 * @class tinymce.dom.TridentSelection
 */
define("tinymce/dom/TridentSelection", [], function() {
    function Selection(selection) {
        var self = this, dom = selection.dom, FALSE = false;

        function getPosition(rng, start) {
            var checkRng, startIndex = 0, endIndex, inside,
                children, child, offset, index, position = -1, parent;

            // Setup test range, collapse it and get the parent
            checkRng = rng.duplicate();
            checkRng.collapse(start);
            parent = checkRng.parentElement();

            // Check if the selection is within the right document
            if (parent.ownerDocument !== selection.dom.doc) {
                return;
            }

            // IE will report non editable elements as it's parent so look for an editable one
            while (parent.contentEditable === "false") {
                parent = parent.parentNode;
            }

            // If parent doesn't have any children then return that we are inside the element
            if (!parent.hasChildNodes()) {
                return {node: parent, inside: 1};
            }

            // Setup node list and endIndex
            children = parent.children;
            endIndex = children.length - 1;

            // Perform a binary search for the position
            while (startIndex <= endIndex) {
                index = Math.floor((startIndex + endIndex) / 2);

                // Move selection to node and compare the ranges
                child = children[index];
                checkRng.moveToElementText(child);
                position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);

                // Before/after or an exact match
                if (position > 0) {
                    endIndex = index - 1;
                } else if (position < 0) {
                    startIndex = index + 1;
                } else {
                    return {node: child};
                }
            }

            // Check if child position is before or we didn't find a position
            if (position < 0) {
                // No element child was found use the parent element and the offset inside that
                if (!child) {
                    checkRng.moveToElementText(parent);
                    checkRng.collapse(true);
                    child = parent;
                    inside = true;
                } else {
                    checkRng.collapse(false);
                }

                // Walk character by character in text node until we hit the selected range endpoint,
                // hit the end of document or parent isn't the right one
                // We need to walk char by char since rng.text or rng.htmlText will trim line endings
                offset = 0;
                while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
                    if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
                        break;
                    }

                    offset++;
                }
            } else {
                // Child position is after the selection endpoint
                checkRng.collapse(true);

                // Walk character by character in text node until we hit the selected range endpoint, hit
                // the end of document or parent isn't the right one
                offset = 0;
                while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
                    if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
                        break;
                    }

                    offset++;
                }
            }

            return {node: child, position: position, offset: offset, inside: inside};
        }

        // Returns a W3C DOM compatible range object by using the IE Range API
        function getRange() {
            var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark;

            // If selection is outside the current document just return an empty range
            element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
            if (element.ownerDocument != dom.doc) {
                return domRange;
            }

            collapsed = selection.isCollapsed();

            // Handle control selection
            if (ieRange.item) {
                domRange.setStart(element.parentNode, dom.nodeIndex(element));
                domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);

                return domRange;
            }

            function findEndPoint(start) {
                var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;

                container = endPoint.node;
                offset = endPoint.offset;

                if (endPoint.inside && !container.hasChildNodes()) {
                    domRange[start ? 'setStart' : 'setEnd'](container, 0);
                    return;
                }

                if (offset === undef) {
                    domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
                    return;
                }

                if (endPoint.position < 0) {
                    sibling = endPoint.inside ? container.firstChild : container.nextSibling;

                    if (!sibling) {
                        domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
                        return;
                    }

                    if (!offset) {
                        if (sibling.nodeType == 3) {
                            domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
                        } else {
                            domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
                        }

                        return;
                    }

                    // Find the text node and offset
                    while (sibling) {
                        if (sibling.nodeType == 3) {
                            nodeValue = sibling.nodeValue;
                            textNodeOffset += nodeValue.length;

                            // We are at or passed the position we where looking for
                            if (textNodeOffset >= offset) {
                                container = sibling;
                                textNodeOffset -= offset;
                                textNodeOffset = nodeValue.length - textNodeOffset;
                                break;
                            }
                        }

                        sibling = sibling.nextSibling;
                    }
                } else {
                    // Find the text node and offset
                    sibling = container.previousSibling;

                    if (!sibling) {
                        return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
                    }

                    // If there isn't any text to loop then use the first position
                    if (!offset) {
                        if (container.nodeType == 3) {
                            domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
                        } else {
                            domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
                        }

                        return;
                    }

                    while (sibling) {
                        if (sibling.nodeType == 3) {
                            textNodeOffset += sibling.nodeValue.length;

                            // We are at or passed the position we where looking for
                            if (textNodeOffset >= offset) {
                                container = sibling;
                                textNodeOffset -= offset;
                                break;
                            }
                        }

                        sibling = sibling.previousSibling;
                    }
                }

                domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
            }

            try {
                // Find start point
                findEndPoint(true);

                // Find end point if needed
                if (!collapsed) {
                    findEndPoint();
                }
            } catch (ex) {
                // IE has a nasty bug where text nodes might throw "invalid argument" when you
                // access the nodeValue or other properties of text nodes. This seems to happend when
                // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
                if (ex.number == -2147024809) {
                    // Get the current selection
                    bookmark = self.getBookmark(2);

                    // Get start element
                    tmpRange = ieRange.duplicate();
                    tmpRange.collapse(true);
                    element = tmpRange.parentElement();

                    // Get end element
                    if (!collapsed) {
                        tmpRange = ieRange.duplicate();
                        tmpRange.collapse(false);
                        element2 = tmpRange.parentElement();
                        element2.innerHTML = element2.innerHTML;
                    }

                    // Remove the broken elements
                    element.innerHTML = element.innerHTML;

                    // Restore the selection
                    self.moveToBookmark(bookmark);

                    // Since the range has moved we need to re-get it
                    ieRange = selection.getRng();

                    // Find start point
                    findEndPoint(true);

                    // Find end point if needed
                    if (!collapsed) {
                        findEndPoint();
                    }
                } else {
                    throw ex; // Throw other errors
                }
            }

            return domRange;
        }

        this.getBookmark = function(type) {
            var rng = selection.getRng(), bookmark = {};

            function getIndexes(node) {
                var parent, root, children, i, indexes = [];

                parent = node.parentNode;
                root = dom.getRoot().parentNode;

                while (parent != root && parent.nodeType !== 9) {
                    children = parent.children;

                    i = children.length;
                    while (i--) {
                        if (node === children[i]) {
                            indexes.push(i);
                            break;
                        }
                    }

                    node = parent;
                    parent = parent.parentNode;
                }

                return indexes;
            }

            function getBookmarkEndPoint(start) {
                var position;

                position = getPosition(rng, start);
                if (position) {
                    return {
                        position: position.position,
                        offset: position.offset,
                        indexes: getIndexes(position.node),
                        inside: position.inside
                    };
                }
            }

            // Non ubstructive bookmark
            if (type === 2) {
                // Handle text selection
                if (!rng.item) {
                    bookmark.start = getBookmarkEndPoint(true);

                    if (!selection.isCollapsed()) {
                        bookmark.end = getBookmarkEndPoint();
                    }
                } else {
                    bookmark.start = {ctrl: true, indexes: getIndexes(rng.item(0))};
                }
            }

            return bookmark;
        };

        this.moveToBookmark = function(bookmark) {
            var rng, body = dom.doc.body;

            function resolveIndexes(indexes) {
                var node, i, idx, children;

                node = dom.getRoot();
                for (i = indexes.length - 1; i >= 0; i--) {
                    children = node.children;
                    idx = indexes[i];

                    if (idx <= children.length - 1) {
                        node = children[idx];
                    }
                }

                return node;
            }

            function setBookmarkEndPoint(start) {
                var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef, offset;

                if (endPoint) {
                    moveLeft = endPoint.position > 0;

                    moveRng = body.createTextRange();
                    moveRng.moveToElementText(resolveIndexes(endPoint.indexes));

                    offset = endPoint.offset;
                    if (offset !== undef) {
                        moveRng.collapse(endPoint.inside || moveLeft);
                        moveRng.moveStart('character', moveLeft ? -offset : offset);
                    } else {
                        moveRng.collapse(start);
                    }

                    rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);

                    if (start) {
                        rng.collapse(true);
                    }
                }
            }

            if (bookmark.start) {
                if (bookmark.start.ctrl) {
                    rng = body.createControlRange();
                    rng.addElement(resolveIndexes(bookmark.start.indexes));
                    rng.select();
                } else {
                    rng = body.createTextRange();
                    setBookmarkEndPoint(true);
                    setBookmarkEndPoint();
                    rng.select();
                }
            }
        };

        this.addRange = function(rng) {
            var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling,
                doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm;

            function setEndPoint(start) {
                var container, offset, marker, tmpRng, nodes;

                marker = dom.create('a');
                container = start ? startContainer : endContainer;
                offset = start ? startOffset : endOffset;
                tmpRng = ieRng.duplicate();

                if (container == doc || container == doc.documentElement) {
                    container = body;
                    offset = 0;
                }

                if (container.nodeType == 3) {
                    container.parentNode.insertBefore(marker, container);
                    tmpRng.moveToElementText(marker);
                    tmpRng.moveStart('character', offset);
                    dom.remove(marker);
                    ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
                } else {
                    nodes = container.childNodes;

                    if (nodes.length) {
                        if (offset >= nodes.length) {
                            dom.insertAfter(marker, nodes[nodes.length - 1]);
                        } else {
                            container.insertBefore(marker, nodes[offset]);
                        }

                        tmpRng.moveToElementText(marker);
                    } else if (container.canHaveHTML) {
                        // Empty node selection for example <div>|</div>
                        // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
                        container.innerHTML = '<span>&#xFEFF;</span>';
                        marker = container.firstChild;
                        tmpRng.moveToElementText(marker);
                        tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
                    }

                    ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
                    dom.remove(marker);
                }
            }

            // Setup some shorter versions
            startContainer = rng.startContainer;
            startOffset = rng.startOffset;
            endContainer = rng.endContainer;
            endOffset = rng.endOffset;
            ieRng = body.createTextRange();

            // If single element selection then try making a control selection out of it
            if (startContainer == endContainer && startContainer.nodeType == 1) {
                // Trick to place the caret inside an empty block element like <p></p>
                if (startOffset == endOffset && !startContainer.hasChildNodes()) {
                    if (startContainer.canHaveHTML) {
                        // Check if previous sibling is an empty block if it is then we need to render it
                        // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
                        // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
                        sibling = startContainer.previousSibling;
                        if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
                            sibling.innerHTML = '&#xFEFF;';
                        } else {
                            sibling = null;
                        }

                        startContainer.innerHTML = '<span>&#xFEFF;</span><span>&#xFEFF;</span>';
                        ieRng.moveToElementText(startContainer.lastChild);
                        ieRng.select();
                        dom.doc.selection.clear();
                        startContainer.innerHTML = '';

                        if (sibling) {
                            sibling.innerHTML = '';
                        }
                        return;
                    } else {
                        startOffset = dom.nodeIndex(startContainer);
                        startContainer = startContainer.parentNode;
                    }
                }

                if (startOffset == endOffset - 1) {
                    try {
                        ctrlElm = startContainer.childNodes[startOffset];
                        ctrlRng = body.createControlRange();
                        ctrlRng.addElement(ctrlElm);
                        ctrlRng.select();

                        // Check if the range produced is on the correct element and is a control range
                        // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398
                        nativeRng = selection.getRng();
                        if (nativeRng.item && ctrlElm === nativeRng.item(0)) {
                            return;
                        }
                    } catch (ex) {
                        // Ignore
                    }
                }
            }

            // Set start/end point of selection
            setEndPoint(true);
            setEndPoint();

            // Select the new range and scroll it into view
            ieRng.select();
        };

        // Expose range method
        this.getRangeAt = getRange;
    }

    return Selection;
});

// Included from: js/tinymce/classes/util/VK.js

/**
 * VK.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This file exposes a set of the common KeyCodes for use.  Please grow it as needed.
 */
define("tinymce/util/VK", [
    "tinymce/Env"
], function(Env) {
    return {
        BACKSPACE: 8,
        DELETE: 46,
        DOWN: 40,
        ENTER: 13,
        LEFT: 37,
        RIGHT: 39,
        SPACEBAR: 32,
        TAB: 9,
        UP: 38,

        modifierPressed: function(e) {
            return e.shiftKey || e.ctrlKey || e.altKey;
        },

        metaKeyPressed: function(e) {
            // Check if ctrl or meta key is pressed. Edge case for AltGr on Windows where it produces ctrlKey+altKey states
            return (Env.mac ? e.metaKey : e.ctrlKey && !e.altKey);
        }
    };
});

// Included from: js/tinymce/classes/dom/ControlSelection.js

/**
 * ControlSelection.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class handles control selection of elements. Controls are elements
 * that can be resized and needs to be selected as a whole. It adds custom resize handles
 * to all browser engines that support properly disabling the built in resize logic.
 *
 * @class tinymce.dom.ControlSelection
 */
define("tinymce/dom/ControlSelection", [
    "tinymce/util/VK",
    "tinymce/util/Tools",
    "tinymce/Env"
], function(VK, Tools, Env) {
    return function(selection, editor) {
        var dom = editor.dom, each = Tools.each;
        var selectedElm, selectedElmGhost, resizeHelper, resizeHandles, selectedHandle, lastMouseDownEvent;
        var startX, startY, selectedElmX, selectedElmY, startW, startH, ratio, resizeStarted;
        var width, height, editableDoc = editor.getDoc(), rootDocument = document, isIE = Env.ie && Env.ie < 11;
        var abs = Math.abs, round = Math.round, rootElement = editor.getBody(), startScrollWidth, startScrollHeight;

        // Details about each resize handle how to scale etc
        resizeHandles = {
            // Name: x multiplier, y multiplier, delta size x, delta size y
            n:  [0.5,   0,     0,   -1],
            e:  [1,    0.5,    1,    0],
            s:  [0.5,   1,     0,    1],
            w:  [0,    0.5,   -1,    0],
            nw: [0,     0,    -1,   -1],
            ne: [1,     0,     1,   -1],
            se: [1,     1,     1,    1],
            sw: [0,     1,    -1,    1]
        };

        // Add CSS for resize handles, cloned element and selected
        var rootClass = '.mce-content-body';
        editor.contentStyles.push(
            rootClass + ' div.mce-resizehandle {' +
                'position: absolute;' +
                'border: 1px solid black;' +
                'background: #FFF;' +
                'width: 5px;' +
                'height: 5px;' +
                'z-index: 10000' +
            '}' +
            rootClass + ' .mce-resizehandle:hover {' +
                'background: #000' +
            '}' +
            rootClass + ' img[data-mce-selected], hr[data-mce-selected] {' +
                'outline: 1px solid black;' +
                'resize: none' + // Have been talks about implementing this in browsers
            '}' +
            rootClass + ' .mce-clonedresizable {' +
                'position: absolute;' +
                (Env.gecko ? '' : 'outline: 1px dashed black;') + // Gecko produces trails while resizing
                'opacity: .5;' +
                'filter: alpha(opacity=50);' +
                'z-index: 10000' +
            '}' +
            rootClass + ' .mce-resize-helper {' +
                'background-color: #555;' +
                'background-color: rgba(0,0,0,0.75);' +
                'border-radius: 3px;' +
                'border: 1px;' +
                'color: white;' +
                'display: none;' +
                'font-family: sans-serif;' +
                'font-size: 12px;' +
                'white-space: nowrap;' +
                'line-height: 14px;' +
                'margin: 5px 10px;' +
                'padding: 5px;' +
                'position: absolute;' +
                'z-index: 10001' +
            '}'
        );

        function isResizable(elm) {
            var selector = editor.settings.object_resizing;

            if (selector === false || Env.iOS) {
                return false;
            }

            if (typeof selector != 'string') {
                selector = 'table,img,div';
            }

            if (elm.getAttribute('data-mce-resize') === 'false') {
                return false;
            }

            return editor.dom.is(elm, selector);
        }

        function resizeGhostElement(e) {
            var deltaX, deltaY, proportional;
            var resizeHelperX, resizeHelperY;

            // Calc new width/height
            deltaX = e.screenX - startX;
            deltaY = e.screenY - startY;

            // Calc new size
            width = deltaX * selectedHandle[2] + startW;
            height = deltaY * selectedHandle[3] + startH;

            // Never scale down lower than 5 pixels
            width = width < 5 ? 5 : width;
            height = height < 5 ? 5 : height;

            if (selectedElm.nodeName == "IMG" && editor.settings.resize_img_proportional !== false) {
                proportional = !VK.modifierPressed(e);
            } else {
                proportional = VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0);
            }

            // Constrain proportions
            if (proportional) {
                if (abs(deltaX) > abs(deltaY)) {
                    height = round(width * ratio);
                    width = round(height / ratio);
                } else {
                    width = round(height / ratio);
                    height = round(width * ratio);
                }
            }

            // Update ghost size
            dom.setStyles(selectedElmGhost, {
                width: width,
                height: height
            });

            // Update resize helper position
            resizeHelperX = selectedHandle.startPos.x + deltaX;
            resizeHelperY = selectedHandle.startPos.y + deltaY;
            resizeHelperX = resizeHelperX > 0 ? resizeHelperX : 0;
            resizeHelperY = resizeHelperY > 0 ? resizeHelperY : 0;

            dom.setStyles(resizeHelper, {
                left: resizeHelperX,
                top: resizeHelperY,
                display: 'block'
            });

            resizeHelper.innerHTML = width + ' &times; ' + height;

            // Update ghost X position if needed
            if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) {
                dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width));
            }

            // Update ghost Y position if needed
            if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) {
                dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height));
            }

            // Calculate how must overflow we got
            deltaX = rootElement.scrollWidth - startScrollWidth;
            deltaY = rootElement.scrollHeight - startScrollHeight;

            // Re-position the resize helper based on the overflow
            if (deltaX + deltaY !== 0) {
                dom.setStyles(resizeHelper, {
                    left: resizeHelperX - deltaX,
                    top: resizeHelperY - deltaY
                });
            }

            if (!resizeStarted) {
                editor.fire('ObjectResizeStart', {target: selectedElm, width: startW, height: startH});
                resizeStarted = true;
            }
        }

        function endGhostResize() {
            resizeStarted = false;

            function setSizeProp(name, value) {
                if (value) {
                    // Resize by using style or attribute
                    if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) {
                        dom.setStyle(selectedElm, name, value);
                    } else {
                        dom.setAttrib(selectedElm, name, value);
                    }
                }
            }

            // Set width/height properties
            setSizeProp('width', width);
            setSizeProp('height', height);

            dom.unbind(editableDoc, 'mousemove', resizeGhostElement);
            dom.unbind(editableDoc, 'mouseup', endGhostResize);

            if (rootDocument != editableDoc) {
                dom.unbind(rootDocument, 'mousemove', resizeGhostElement);
                dom.unbind(rootDocument, 'mouseup', endGhostResize);
            }

            // Remove ghost/helper and update resize handle positions
            dom.remove(selectedElmGhost);
            dom.remove(resizeHelper);

            if (!isIE || selectedElm.nodeName == "TABLE") {
                showResizeRect(selectedElm);
            }

            editor.fire('ObjectResized', {target: selectedElm, width: width, height: height});
            editor.nodeChanged();
        }

        function showResizeRect(targetElm, mouseDownHandleName, mouseDownEvent) {
            var position, targetWidth, targetHeight, e, rect;

            unbindResizeHandleEvents();

            // Get position and size of target
            position = dom.getPos(targetElm, rootElement);
            selectedElmX = position.x;
            selectedElmY = position.y;
            rect = targetElm.getBoundingClientRect(); // Fix for Gecko offsetHeight for table with caption
            targetWidth = rect.width || (rect.right - rect.left);
            targetHeight = rect.height || (rect.bottom - rect.top);

            // Reset width/height if user selects a new image/table
            if (selectedElm != targetElm) {
                detachResizeStartListener();
                selectedElm = targetElm;
                width = height = 0;
            }

            // Makes it possible to disable resizing
            e = editor.fire('ObjectSelected', {target: targetElm});

            if (isResizable(targetElm) && !e.isDefaultPrevented()) {
                each(resizeHandles, function(handle, name) {
                    var handleElm, handlerContainerElm;

                    function startDrag(e) {
                        startX = e.screenX;
                        startY = e.screenY;
                        startW = selectedElm.clientWidth;
                        startH = selectedElm.clientHeight;
                        ratio = startH / startW;
                        selectedHandle = handle;

                        handle.startPos = {
                            x: targetWidth * handle[0] + selectedElmX,
                            y: targetHeight * handle[1] + selectedElmY
                        };

                        startScrollWidth = rootElement.scrollWidth;
                        startScrollHeight = rootElement.scrollHeight;

                        selectedElmGhost = selectedElm.cloneNode(true);
                        dom.addClass(selectedElmGhost, 'mce-clonedresizable');
                        dom.setAttrib(selectedElmGhost, 'data-mce-bogus', 'all');
                        selectedElmGhost.contentEditable = false; // Hides IE move layer cursor
                        selectedElmGhost.unSelectabe = true;
                        dom.setStyles(selectedElmGhost, {
                            left: selectedElmX,
                            top: selectedElmY,
                            margin: 0
                        });

                        selectedElmGhost.removeAttribute('data-mce-selected');
                        rootElement.appendChild(selectedElmGhost);

                        dom.bind(editableDoc, 'mousemove', resizeGhostElement);
                        dom.bind(editableDoc, 'mouseup', endGhostResize);

                        if (rootDocument != editableDoc) {
                            dom.bind(rootDocument, 'mousemove', resizeGhostElement);
                            dom.bind(rootDocument, 'mouseup', endGhostResize);
                        }

                        resizeHelper = dom.add(rootElement, 'div', {
                            'class': 'mce-resize-helper',
                            'data-mce-bogus': 'all'
                        }, startW + ' &times; ' + startH);
                    }

                    if (mouseDownHandleName) {
                        // Drag started by IE native resizestart
                        if (name == mouseDownHandleName) {
                            startDrag(mouseDownEvent);
                        }

                        return;
                    }

                    // Get existing or render resize handle
                    handleElm = dom.get('mceResizeHandle' + name);
                    if (!handleElm) {
                        handlerContainerElm = rootElement;

                        handleElm = dom.add(handlerContainerElm, 'div', {
                            id: 'mceResizeHandle' + name,
                            'data-mce-bogus': 'all',
                            'class': 'mce-resizehandle',
                            unselectable: true,
                            style: 'cursor:' + name + '-resize; margin:0; padding:0'
                        });

                        // Hides IE move layer cursor
                        // If we set it on Chrome we get this wounderful bug: #6725
                        if (Env.ie) {
                            handleElm.contentEditable = false;
                        }
                    } else {
                        dom.show(handleElm);
                    }

                    if (!handle.elm) {
                        dom.bind(handleElm, 'mousedown', function(e) {
                            e.stopImmediatePropagation();
                            e.preventDefault();
                            startDrag(e);
                        });

                        handle.elm = handleElm;
                    }

                    // Position element
                    dom.setStyles(handleElm, {
                        left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2),
                        top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2)
                    });
                });
            } else {
                hideResizeRect();
            }

            selectedElm.setAttribute('data-mce-selected', '1');
        }

        function hideResizeRect() {
            var name, handleElm;

            unbindResizeHandleEvents();

            if (selectedElm) {
                selectedElm.removeAttribute('data-mce-selected');
            }

            for (name in resizeHandles) {
                handleElm = dom.get('mceResizeHandle' + name);
                if (handleElm) {
                    dom.unbind(handleElm);
                    dom.remove(handleElm);
                }
            }
        }

        function updateResizeRect(e) {
            var controlElm;

            function isChildOrEqual(node, parent) {
                if (node) {
                    do {
                        if (node === parent) {
                            return true;
                        }
                    } while ((node = node.parentNode));
                }
            }

            // Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v
            each(dom.select('img[data-mce-selected],hr[data-mce-selected]'), function(img) {
                img.removeAttribute('data-mce-selected');
            });

            controlElm = e.type == 'mousedown' ? e.target : selection.getNode();
            controlElm = dom.$(controlElm).closest(isIE ? 'table' : 'table,img,hr')[0];

            if (isChildOrEqual(controlElm, rootElement)) {
                disableGeckoResize();

                if (isChildOrEqual(selection.getStart(), controlElm) && isChildOrEqual(selection.getEnd(), controlElm)) {
                    if (!isIE || (controlElm != selection.getStart() && selection.getStart().nodeName !== 'IMG')) {
                        showResizeRect(controlElm);
                        return;
                    }
                }
            }

            hideResizeRect();
        }

        function attachEvent(elm, name, func) {
            if (elm && elm.attachEvent) {
                elm.attachEvent('on' + name, func);
            }
        }

        function detachEvent(elm, name, func) {
            if (elm && elm.detachEvent) {
                elm.detachEvent('on' + name, func);
            }
        }

        function resizeNativeStart(e) {
            var target = e.srcElement, pos, name, corner, cornerX, cornerY, relativeX, relativeY;

            pos = target.getBoundingClientRect();
            relativeX = lastMouseDownEvent.clientX - pos.left;
            relativeY = lastMouseDownEvent.clientY - pos.top;

            // Figure out what corner we are draging on
            for (name in resizeHandles) {
                corner = resizeHandles[name];

                cornerX = target.offsetWidth * corner[0];
                cornerY = target.offsetHeight * corner[1];

                if (abs(cornerX - relativeX) < 8 && abs(cornerY - relativeY) < 8) {
                    selectedHandle = corner;
                    break;
                }
            }

            // Remove native selection and let the magic begin
            resizeStarted = true;
            editor.fire('ObjectResizeStart', {
                target: selectedElm,
                width: selectedElm.clientWidth,
                height: selectedElm.clientHeight
            });
            editor.getDoc().selection.empty();
            showResizeRect(target, name, lastMouseDownEvent);
        }

        function nativeControlSelect(e) {
            var target = e.srcElement;

            if (target != selectedElm) {
                editor.fire('ObjectSelected', {target: target});
                detachResizeStartListener();

                if (target.id.indexOf('mceResizeHandle') === 0) {
                    e.returnValue = false;
                    return;
                }

                if (target.nodeName == 'IMG' || target.nodeName == 'TABLE') {
                    hideResizeRect();
                    selectedElm = target;
                    attachEvent(target, 'resizestart', resizeNativeStart);
                }
            }
        }

        function detachResizeStartListener() {
            detachEvent(selectedElm, 'resizestart', resizeNativeStart);
        }

        function unbindResizeHandleEvents() {
            for (var name in resizeHandles) {
                var handle = resizeHandles[name];

                if (handle.elm) {
                    dom.unbind(handle.elm);
                    delete handle.elm;
                }
            }
        }

        function disableGeckoResize() {
            try {
                // Disable object resizing on Gecko
                editor.getDoc().execCommand('enableObjectResizing', false, false);
            } catch (ex) {
                // Ignore
            }
        }

        function controlSelect(elm) {
            var ctrlRng;

            if (!isIE) {
                return;
            }

            ctrlRng = editableDoc.body.createControlRange();

            try {
                ctrlRng.addElement(elm);
                ctrlRng.select();
                return true;
            } catch (ex) {
                // Ignore since the element can't be control selected for example a P tag
            }
        }

        editor.on('init', function() {
            if (isIE) {
                // Hide the resize rect on resize and reselect the image
                editor.on('ObjectResized', function(e) {
                    if (e.target.nodeName != 'TABLE') {
                        hideResizeRect();
                        controlSelect(e.target);
                    }
                });

                attachEvent(rootElement, 'controlselect', nativeControlSelect);

                editor.on('mousedown', function(e) {
                    lastMouseDownEvent = e;
                });
            } else {
                disableGeckoResize();

                if (Env.ie >= 11) {
                    // TODO: Drag/drop doesn't work
                    editor.on('mouseup', function(e) {
                        var nodeName = e.target.nodeName;

                        if (!resizeStarted && /^(TABLE|IMG|HR)$/.test(nodeName)) {
                            editor.selection.select(e.target, nodeName == 'TABLE');
                            editor.nodeChanged();
                        }
                    });

                    editor.dom.bind(rootElement, 'mscontrolselect', function(e) {
                        if (/^(TABLE|IMG|HR)$/.test(e.target.nodeName)) {
                            e.preventDefault();

                            // This moves the selection from being a control selection to a text like selection like in WebKit #6753
                            // TODO: Fix this the day IE works like other browsers without this nasty native ugly control selections.
                            if (e.target.tagName == 'IMG') {
                                window.setTimeout(function() {
                                    editor.selection.select(e.target);
                                }, 0);
                            }
                        }
                    });
                }
            }

            editor.on('nodechange mousedown mouseup ResizeEditor', updateResizeRect);

            // Update resize rect while typing in a table
            editor.on('keydown keyup', function(e) {
                if (selectedElm && selectedElm.nodeName == "TABLE") {
                    updateResizeRect(e);
                }
            });

            editor.on('hide', hideResizeRect);

            // Hide rect on focusout since it would float on top of windows otherwise
            //editor.on('focusout', hideResizeRect);
        });

        editor.on('remove', unbindResizeHandleEvents);

        function destroy() {
            selectedElm = selectedElmGhost = null;

            if (isIE) {
                detachResizeStartListener();
                detachEvent(rootElement, 'controlselect', nativeControlSelect);
            }
        }

        return {
            isResizable: isResizable,
            showResizeRect: showResizeRect,
            hideResizeRect: hideResizeRect,
            updateResizeRect: updateResizeRect,
            controlSelect: controlSelect,
            destroy: destroy
        };
    };
});

// Included from: js/tinymce/classes/dom/BookmarkManager.js

/**
 * BookmarkManager.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class handles selection bookmarks.
 *
 * @class tinymce.dom.BookmarkManager
 */
define("tinymce/dom/BookmarkManager", [
    "tinymce/Env",
    "tinymce/util/Tools"
], function(Env, Tools) {
    /**
     * Constructs a new BookmarkManager instance for a specific selection instance.
     *
     * @constructor
     * @method BookmarkManager
     * @param {tinymce.dom.Selection} selection Selection instance to handle bookmarks for.
     */
    function BookmarkManager(selection) {
        var dom = selection.dom;

        /**
         * Returns a bookmark location for the current selection. This bookmark object
         * can then be used to restore the selection after some content modification to the document.
         *
         * @method getBookmark
         * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
         * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
         * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
         * @example
         * // Stores a bookmark of the current selection
         * var bm = tinymce.activeEditor.selection.getBookmark();
         *
         * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
         *
         * // Restore the selection bookmark
         * tinymce.activeEditor.selection.moveToBookmark(bm);
         */
        this.getBookmark = function(type, normalized) {
            var rng, rng2, id, collapsed, name, element, chr = '&#xFEFF;', styles;

            function findIndex(name, element) {
                var index = 0;

                Tools.each(dom.select(name), function(node, i) {
                    if (node == element) {
                        index = i;
                    }
                });

                return index;
            }

            function normalizeTableCellSelection(rng) {
                function moveEndPoint(start) {
                    var container, offset, childNodes, prefix = start ? 'start' : 'end';

                    container = rng[prefix + 'Container'];
                    offset = rng[prefix + 'Offset'];

                    if (container.nodeType == 1 && container.nodeName == "TR") {
                        childNodes = container.childNodes;
                        container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
                        if (container) {
                            offset = start ? 0 : container.childNodes.length;
                            rng['set' + (start ? 'Start' : 'End')](container, offset);
                        }
                    }
                }

                moveEndPoint(true);
                moveEndPoint();

                return rng;
            }

            function getLocation() {
                var rng = selection.getRng(true), root = dom.getRoot(), bookmark = {};

                function getPoint(rng, start) {
                    var container = rng[start ? 'startContainer' : 'endContainer'],
                        offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;

                    if (container.nodeType == 3) {
                        if (normalized) {
                            for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) {
                                offset += node.nodeValue.length;
                            }
                        }

                        point.push(offset);
                    } else {
                        childNodes = container.childNodes;

                        if (offset >= childNodes.length && childNodes.length) {
                            after = 1;
                            offset = Math.max(0, childNodes.length - 1);
                        }

                        point.push(dom.nodeIndex(childNodes[offset], normalized) + after);
                    }

                    for (; container && container != root; container = container.parentNode) {
                        point.push(dom.nodeIndex(container, normalized));
                    }

                    return point;
                }

                bookmark.start = getPoint(rng, true);

                if (!selection.isCollapsed()) {
                    bookmark.end = getPoint(rng);
                }

                return bookmark;
            }

            if (type == 2) {
                element = selection.getNode();
                name = element ? element.nodeName : null;

                if (name == 'IMG') {
                    return {name: name, index: findIndex(name, element)};
                }

                if (selection.tridentSel) {
                    return selection.tridentSel.getBookmark(type);
                }

                return getLocation();
            }

            // Handle simple range
            if (type) {
                return {rng: selection.getRng()};
            }

            rng = selection.getRng();
            id = dom.uniqueId();
            collapsed = selection.isCollapsed();
            styles = 'overflow:hidden;line-height:0px';

            // Explorer method
            if (rng.duplicate || rng.item) {
                // Text selection
                if (!rng.item) {
                    rng2 = rng.duplicate();

                    try {
                        // Insert start marker
                        rng.collapse();
                        rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');

                        // Insert end marker
                        if (!collapsed) {
                            rng2.collapse(false);

                            // Detect the empty space after block elements in IE and move the
                            // end back one character <p></p>] becomes <p>]</p>
                            rng.moveToElementText(rng2.parentElement());
                            if (rng.compareEndPoints('StartToEnd', rng2) === 0) {
                                rng2.move('character', -1);
                            }

                            rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
                        }
                    } catch (ex) {
                        // IE might throw unspecified error so lets ignore it
                        return null;
                    }
                } else {
                    // Control selection
                    element = rng.item(0);
                    name = element.nodeName;

                    return {name: name, index: findIndex(name, element)};
                }
            } else {
                element = selection.getNode();
                name = element.nodeName;
                if (name == 'IMG') {
                    return {name: name, index: findIndex(name, element)};
                }

                // W3C method
                rng2 = normalizeTableCellSelection(rng.cloneRange());

                // Insert end marker
                if (!collapsed) {
                    rng2.collapse(false);
                    rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr));
                }

                rng = normalizeTableCellSelection(rng);
                rng.collapse(true);
                rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr));
            }

            selection.moveToBookmark({id: id, keep: 1});

            return {id: id};
        };

        /**
         * Restores the selection to the specified bookmark.
         *
         * @method moveToBookmark
         * @param {Object} bookmark Bookmark to restore selection from.
         * @return {Boolean} true/false if it was successful or not.
         * @example
         * // Stores a bookmark of the current selection
         * var bm = tinymce.activeEditor.selection.getBookmark();
         *
         * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
         *
         * // Restore the selection bookmark
         * tinymce.activeEditor.selection.moveToBookmark(bm);
         */
        this.moveToBookmark = function(bookmark) {
            var rng, root, startContainer, endContainer, startOffset, endOffset;

            function setEndPoint(start) {
                var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;

                if (point) {
                    offset = point[0];

                    // Find container node
                    for (node = root, i = point.length - 1; i >= 1; i--) {
                        children = node.childNodes;

                        if (point[i] > children.length - 1) {
                            return;
                        }

                        node = children[point[i]];
                    }

                    // Move text offset to best suitable location
                    if (node.nodeType === 3) {
                        offset = Math.min(point[0], node.nodeValue.length);
                    }

                    // Move element offset to best suitable location
                    if (node.nodeType === 1) {
                        offset = Math.min(point[0], node.childNodes.length);
                    }

                    // Set offset within container node
                    if (start) {
                        rng.setStart(node, offset);
                    } else {
                        rng.setEnd(node, offset);
                    }
                }

                return true;
            }

            function restoreEndPoint(suffix) {
                var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;

                if (marker) {
                    node = marker.parentNode;

                    if (suffix == 'start') {
                        if (!keep) {
                            idx = dom.nodeIndex(marker);
                        } else {
                            node = marker.firstChild;
                            idx = 1;
                        }

                        startContainer = endContainer = node;
                        startOffset = endOffset = idx;
                    } else {
                        if (!keep) {
                            idx = dom.nodeIndex(marker);
                        } else {
                            node = marker.firstChild;
                            idx = 1;
                        }

                        endContainer = node;
                        endOffset = idx;
                    }

                    if (!keep) {
                        prev = marker.previousSibling;
                        next = marker.nextSibling;

                        // Remove all marker text nodes
                        Tools.each(Tools.grep(marker.childNodes), function(node) {
                            if (node.nodeType == 3) {
                                node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
                            }
                        });

                        // Remove marker but keep children if for example contents where inserted into the marker
                        // Also remove duplicated instances of the marker for example by a
                        // split operation or by WebKit auto split on paste feature
                        while ((marker = dom.get(bookmark.id + '_' + suffix))) {
                            dom.remove(marker, 1);
                        }

                        // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
                        // and we are sniffing since adding a lot of detection code for a browser with 3% of the market
                        // isn't worth the effort. Sorry, Opera but it's just a fact
                        if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !Env.opera) {
                            idx = prev.nodeValue.length;
                            prev.appendData(next.nodeValue);
                            dom.remove(next);

                            if (suffix == 'start') {
                                startContainer = endContainer = prev;
                                startOffset = endOffset = idx;
                            } else {
                                endContainer = prev;
                                endOffset = idx;
                            }
                        }
                    }
                }
            }

            function addBogus(node) {
                // Adds a bogus BR element for empty block elements
                if (dom.isBlock(node) && !node.innerHTML && !Env.ie) {
                    node.innerHTML = '<br data-mce-bogus="1" />';
                }

                return node;
            }

            if (bookmark) {
                if (bookmark.start) {
                    rng = dom.createRng();
                    root = dom.getRoot();

                    if (selection.tridentSel) {
                        return selection.tridentSel.moveToBookmark(bookmark);
                    }

                    if (setEndPoint(true) && setEndPoint()) {
                        selection.setRng(rng);
                    }
                } else if (bookmark.id) {
                    // Restore start/end points
                    restoreEndPoint('start');
                    restoreEndPoint('end');

                    if (startContainer) {
                        rng = dom.createRng();
                        rng.setStart(addBogus(startContainer), startOffset);
                        rng.setEnd(addBogus(endContainer), endOffset);
                        selection.setRng(rng);
                    }
                } else if (bookmark.name) {
                    selection.select(dom.select(bookmark.name)[bookmark.index]);
                } else if (bookmark.rng) {
                    selection.setRng(bookmark.rng);
                }
            }
        };
    }

    /**
     * Returns true/false if the specified node is a bookmark node or not.
     *
     * @static
     * @method isBookmarkNode
     * @param {DOMNode} node DOM Node to check if it's a bookmark node or not.
     * @return {Boolean} true/false if the node is a bookmark node or not.
     */
    BookmarkManager.isBookmarkNode = function(node) {
        return node && node.tagName === 'SPAN' && node.getAttribute('data-mce-type') === 'bookmark';
    };

    return BookmarkManager;
});

// Included from: js/tinymce/classes/dom/Selection.js

/**
 * Selection.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class handles text and control selection it's an crossbrowser utility class.
 * Consult the TinyMCE Wiki API for more details and examples on how to use this class.
 *
 * @class tinymce.dom.Selection
 * @example
 * // Getting the currently selected node for the active editor
 * alert(tinymce.activeEditor.selection.getNode().nodeName);
 */
define("tinymce/dom/Selection", [
    "tinymce/dom/TreeWalker",
    "tinymce/dom/TridentSelection",
    "tinymce/dom/ControlSelection",
    "tinymce/dom/RangeUtils",
    "tinymce/dom/BookmarkManager",
    "tinymce/Env",
    "tinymce/util/Tools"
], function(TreeWalker, TridentSelection, ControlSelection, RangeUtils, BookmarkManager, Env, Tools) {
    var each = Tools.each, trim = Tools.trim;
    var isIE = Env.ie;

    /**
     * Constructs a new selection instance.
     *
     * @constructor
     * @method Selection
     * @param {tinymce.dom.DOMUtils} dom DOMUtils object reference.
     * @param {Window} win Window to bind the selection object to.
     * @param {tinymce.dom.Serializer} serializer DOM serialization class to use for getContent.
     */
    function Selection(dom, win, serializer, editor) {
        var self = this;

        self.dom = dom;
        self.win = win;
        self.serializer = serializer;
        self.editor = editor;
        self.bookmarkManager = new BookmarkManager(self);
        self.controlSelection = new ControlSelection(self, editor);

        // No W3C Range support
        if (!self.win.getSelection) {
            self.tridentSel = new TridentSelection(self);
        }
    }

    Selection.prototype = {
        /**
         * Move the selection cursor range to the specified node and offset.
         * If there is no node specified it will move it to the first suitable location within the body.
         *
         * @method setCursorLocation
         * @param {Node} node Optional node to put the cursor in.
         * @param {Number} offset Optional offset from the start of the node to put the cursor at.
         */
        setCursorLocation: function(node, offset) {
            var self = this, rng = self.dom.createRng();

            if (!node) {
                self._moveEndPoint(rng, self.editor.getBody(), true);
                self.setRng(rng);
            } else {
                rng.setStart(node, offset);
                rng.setEnd(node, offset);
                self.setRng(rng);
                self.collapse(false);
            }
        },

        /**
         * Returns the selected contents using the DOM serializer passed in to this class.
         *
         * @method getContent
         * @param {Object} s Optional settings class with for example output format text or html.
         * @return {String} Selected contents in for example HTML format.
         * @example
         * // Alerts the currently selected contents
         * alert(tinymce.activeEditor.selection.getContent());
         *
         * // Alerts the currently selected contents as plain text
         * alert(tinymce.activeEditor.selection.getContent({format: 'text'}));
         */
        getContent: function(args) {
            var self = this, rng = self.getRng(), tmpElm = self.dom.create("body");
            var se = self.getSel(), whiteSpaceBefore, whiteSpaceAfter, fragment;

            args = args || {};
            whiteSpaceBefore = whiteSpaceAfter = '';
            args.get = true;
            args.format = args.format || 'html';
            args.selection = true;
            self.editor.fire('BeforeGetContent', args);

            if (args.format == 'text') {
                return self.isCollapsed() ? '' : (rng.text || (se.toString ? se.toString() : ''));
            }

            if (rng.cloneContents) {
                fragment = rng.cloneContents();

                if (fragment) {
                    tmpElm.appendChild(fragment);
                }
            } else if (rng.item !== undefined || rng.htmlText !== undefined) {
                // IE will produce invalid markup if elements are present that
                // it doesn't understand like custom elements or HTML5 elements.
                // Adding a BR in front of the contents and then remoiving it seems to fix it though.
                tmpElm.innerHTML = '<br>' + (rng.item ? rng.item(0).outerHTML : rng.htmlText);
                tmpElm.removeChild(tmpElm.firstChild);
            } else {
                tmpElm.innerHTML = rng.toString();
            }

            // Keep whitespace before and after
            if (/^\s/.test(tmpElm.innerHTML)) {
                whiteSpaceBefore = ' ';
            }

            if (/\s+$/.test(tmpElm.innerHTML)) {
                whiteSpaceAfter = ' ';
            }

            args.getInner = true;

            args.content = self.isCollapsed() ? '' : whiteSpaceBefore + self.serializer.serialize(tmpElm, args) + whiteSpaceAfter;
            self.editor.fire('GetContent', args);

            return args.content;
        },

        /**
         * Sets the current selection to the specified content. If any contents is selected it will be replaced
         * with the contents passed in to this function. If there is no selection the contents will be inserted
         * where the caret is placed in the editor/page.
         *
         * @method setContent
         * @param {String} content HTML contents to set could also be other formats depending on settings.
         * @param {Object} args Optional settings object with for example data format.
         * @example
         * // Inserts some HTML contents at the current selection
         * tinymce.activeEditor.selection.setContent('<strong>Some contents</strong>');
         */
        setContent: function(content, args) {
            var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;

            args = args || {format: 'html'};
            args.set = true;
            args.selection = true;
            content = args.content = content;

            // Dispatch before set content event
            if (!args.no_events) {
                self.editor.fire('BeforeSetContent', args);
            }

            content = args.content;

            if (rng.insertNode) {
                // Make caret marker since insertNode places the caret in the beginning of text after insert
                content += '<span id="__caret">_</span>';

                // Delete and insert new node
                if (rng.startContainer == doc && rng.endContainer == doc) {
                    // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
                    doc.body.innerHTML = content;
                } else {
                    rng.deleteContents();

                    if (doc.body.childNodes.length === 0) {
                        doc.body.innerHTML = content;
                    } else {
                        // createContextualFragment doesn't exists in IE 9 DOMRanges
                        if (rng.createContextualFragment) {
                            rng.insertNode(rng.createContextualFragment(content));
                        } else {
                            // Fake createContextualFragment call in IE 9
                            frag = doc.createDocumentFragment();
                            temp = doc.createElement('div');

                            frag.appendChild(temp);
                            temp.outerHTML = content;

                            rng.insertNode(frag);
                        }
                    }
                }

                // Move to caret marker
                caretNode = self.dom.get('__caret');

                // Make sure we wrap it compleatly, Opera fails with a simple select call
                rng = doc.createRange();
                rng.setStartBefore(caretNode);
                rng.setEndBefore(caretNode);
                self.setRng(rng);

                // Remove the caret position
                self.dom.remove('__caret');

                try {
                    self.setRng(rng);
                } catch (ex) {
                    // Might fail on Opera for some odd reason
                }
            } else {
                if (rng.item) {
                    // Delete content and get caret text selection
                    doc.execCommand('Delete', false, null);
                    rng = self.getRng();
                }

                // Explorer removes spaces from the beginning of pasted contents
                if (/^\s+/.test(content)) {
                    rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
                    self.dom.remove('__mce_tmp');
                } else {
                    rng.pasteHTML(content);
                }
            }

            // Dispatch set content event
            if (!args.no_events) {
                self.editor.fire('SetContent', args);
            }
        },

        /**
         * Returns the start element of a selection range. If the start is in a text
         * node the parent element will be returned.
         *
         * @method getStart
         * @return {Element} Start element of selection range.
         */
        getStart: function() {
            var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;

            if (rng.duplicate || rng.item) {
                // Control selection, return first item
                if (rng.item) {
                    return rng.item(0);
                }

                // Get start element
                checkRng = rng.duplicate();
                checkRng.collapse(1);
                startElement = checkRng.parentElement();
                if (startElement.ownerDocument !== self.dom.doc) {
                    startElement = self.dom.getRoot();
                }

                // Check if range parent is inside the start element, then return the inner parent element
                // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
                parentElement = node = rng.parentElement();
                while ((node = node.parentNode)) {
                    if (node == startElement) {
                        startElement = parentElement;
                        break;
                    }
                }

                return startElement;
            } else {
                startElement = rng.startContainer;

                if (startElement.nodeType == 1 && startElement.hasChildNodes()) {
                    startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
                }

                if (startElement && startElement.nodeType == 3) {
                    return startElement.parentNode;
                }

                return startElement;
            }
        },

        /**
         * Returns the end element of a selection range. If the end is in a text
         * node the parent element will be returned.
         *
         * @method getEnd
         * @return {Element} End element of selection range.
         */
        getEnd: function() {
            var self = this, rng = self.getRng(), endElement, endOffset;

            if (rng.duplicate || rng.item) {
                if (rng.item) {
                    return rng.item(0);
                }

                rng = rng.duplicate();
                rng.collapse(0);
                endElement = rng.parentElement();
                if (endElement.ownerDocument !== self.dom.doc) {
                    endElement = self.dom.getRoot();
                }

                if (endElement && endElement.nodeName == 'BODY') {
                    return endElement.lastChild || endElement;
                }

                return endElement;
            } else {
                endElement = rng.endContainer;
                endOffset = rng.endOffset;

                if (endElement.nodeType == 1 && endElement.hasChildNodes()) {
                    endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
                }

                if (endElement && endElement.nodeType == 3) {
                    return endElement.parentNode;
                }

                return endElement;
            }
        },

        /**
         * Returns a bookmark location for the current selection. This bookmark object
         * can then be used to restore the selection after some content modification to the document.
         *
         * @method getBookmark
         * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
         * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
         * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
         * @example
         * // Stores a bookmark of the current selection
         * var bm = tinymce.activeEditor.selection.getBookmark();
         *
         * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
         *
         * // Restore the selection bookmark
         * tinymce.activeEditor.selection.moveToBookmark(bm);
         */
        getBookmark: function(type, normalized) {
            return this.bookmarkManager.getBookmark(type, normalized);
        },

        /**
         * Restores the selection to the specified bookmark.
         *
         * @method moveToBookmark
         * @param {Object} bookmark Bookmark to restore selection from.
         * @return {Boolean} true/false if it was successful or not.
         * @example
         * // Stores a bookmark of the current selection
         * var bm = tinymce.activeEditor.selection.getBookmark();
         *
         * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
         *
         * // Restore the selection bookmark
         * tinymce.activeEditor.selection.moveToBookmark(bm);
         */
        moveToBookmark: function(bookmark) {
            return this.bookmarkManager.moveToBookmark(bookmark);
        },

        /**
         * Selects the specified element. This will place the start and end of the selection range around the element.
         *
         * @method select
         * @param {Element} node HMTL DOM element to select.
         * @param {Boolean} content Optional bool state if the contents should be selected or not on non IE browser.
         * @return {Element} Selected element the same element as the one that got passed in.
         * @example
         * // Select the first paragraph in the active editor
         * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]);
         */
        select: function(node, content) {
            var self = this, dom = self.dom, rng = dom.createRng(), idx;

            // Clear stored range set by FocusManager
            self.lastFocusBookmark = null;

            if (node) {
                if (!content && self.controlSelection.controlSelect(node)) {
                    return;
                }

                idx = dom.nodeIndex(node);
                rng.setStart(node.parentNode, idx);
                rng.setEnd(node.parentNode, idx + 1);

                // Find first/last text node or BR element
                if (content) {
                    self._moveEndPoint(rng, node, true);
                    self._moveEndPoint(rng, node);
                }

                self.setRng(rng);
            }

            return node;
        },

        /**
         * Returns true/false if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection.
         *
         * @method isCollapsed
         * @return {Boolean} true/false state if the selection range is collapsed or not.
         * Collapsed means if it's a caret or a larger selection.
         */
        isCollapsed: function() {
            var self = this, rng = self.getRng(), sel = self.getSel();

            if (!rng || rng.item) {
                return false;
            }

            if (rng.compareEndPoints) {
                return rng.compareEndPoints('StartToEnd', rng) === 0;
            }

            return !sel || rng.collapsed;
        },

        /**
         * Collapse the selection to start or end of range.
         *
         * @method collapse
         * @param {Boolean} toStart Optional boolean state if to collapse to end or not. Defaults to start.
         */
        collapse: function(toStart) {
            var self = this, rng = self.getRng(), node;

            // Control range on IE
            if (rng.item) {
                node = rng.item(0);
                rng = self.win.document.body.createTextRange();
                rng.moveToElementText(node);
            }

            rng.collapse(!!toStart);
            self.setRng(rng);
        },

        /**
         * Returns the browsers internal selection object.
         *
         * @method getSel
         * @return {Selection} Internal browser selection object.
         */
        getSel: function() {
            var win = this.win;

            return win.getSelection ? win.getSelection() : win.document.selection;
        },

        /**
         * Returns the browsers internal range object.
         *
         * @method getRng
         * @param {Boolean} w3c Forces a compatible W3C range on IE.
         * @return {Range} Internal browser range object.
         * @see http://www.quirksmode.org/dom/range_intro.html
         * @see http://www.dotvoid.com/2001/03/using-the-range-object-in-mozilla/
         */
        getRng: function(w3c) {
            var self = this, selection, rng, elm, doc = self.win.document, ieRng;

            function tryCompareBounderyPoints(how, sourceRange, destinationRange) {
                try {
                    return sourceRange.compareBoundaryPoints(how, destinationRange);
                } catch (ex) {
                    // Gecko throws wrong document exception if the range points
                    // to nodes that where removed from the dom #6690
                    // Browsers should mutate existing DOMRange instances so that they always point
                    // to something in the document this is not the case in Gecko works fine in IE/WebKit/Blink
                    // For performance reasons just return -1
                    return -1;
                }
            }

            // Use last rng passed from FocusManager if it's available this enables
            // calls to editor.selection.getStart() to work when caret focus is lost on IE
            if (!w3c && self.lastFocusBookmark) {
                var bookmark = self.lastFocusBookmark;

                // Convert bookmark to range IE 11 fix
                if (bookmark.startContainer) {
                    rng = doc.createRange();
                    rng.setStart(bookmark.startContainer, bookmark.startOffset);
                    rng.setEnd(bookmark.endContainer, bookmark.endOffset);
                } else {
                    rng = bookmark;
                }

                return rng;
            }

            // Found tridentSel object then we need to use that one
            if (w3c && self.tridentSel) {
                return self.tridentSel.getRangeAt(0);
            }

            try {
                if ((selection = self.getSel())) {
                    if (selection.rangeCount > 0) {
                        rng = selection.getRangeAt(0);
                    } else {
                        rng = selection.createRange ? selection.createRange() : doc.createRange();
                    }
                }
            } catch (ex) {
                // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
            }

            // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
            // IE 11 doesn't support the selection object so we check for that as well
            if (isIE && rng && rng.setStart && doc.selection) {
                try {
                    // IE will sometimes throw an exception here
                    ieRng = doc.selection.createRange();
                } catch (ex) {

                }

                if (ieRng && ieRng.item) {
                    elm = ieRng.item(0);
                    rng = doc.createRange();
                    rng.setStartBefore(elm);
                    rng.setEndAfter(elm);
                }
            }

            // No range found then create an empty one
            // This can occur when the editor is placed in a hidden container element on Gecko
            // Or on IE when there was an exception
            if (!rng) {
                rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();
            }

            // If range is at start of document then move it to start of body
            if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
                elm = self.dom.getRoot();
                rng.setStart(elm, 0);
                rng.setEnd(elm, 0);
            }

            if (self.selectedRange && self.explicitRange) {
                if (tryCompareBounderyPoints(rng.START_TO_START, rng, self.selectedRange) === 0 &&
                    tryCompareBounderyPoints(rng.END_TO_END, rng, self.selectedRange) === 0) {
                    // Safari, Opera and Chrome only ever select text which causes the range to change.
                    // This lets us use the originally set range if the selection hasn't been changed by the user.
                    rng = self.explicitRange;
                } else {
                    self.selectedRange = null;
                    self.explicitRange = null;
                }
            }

            return rng;
        },

        /**
         * Changes the selection to the specified DOM range.
         *
         * @method setRng
         * @param {Range} rng Range to select.
         */
        setRng: function(rng, forward) {
            var self = this, sel;

            // Is IE specific range
            if (rng.select) {
                try {
                    rng.select();
                } catch (ex) {
                    // Needed for some odd IE bug #1843306
                }

                return;
            }

            if (!self.tridentSel) {
                sel = self.getSel();

                if (sel) {
                    self.explicitRange = rng;

                    try {
                        sel.removeAllRanges();
                        sel.addRange(rng);
                    } catch (ex) {
                        // IE might throw errors here if the editor is within a hidden container and selection is changed
                    }

                    // Forward is set to false and we have an extend function
                    if (forward === false && sel.extend) {
                        sel.collapse(rng.endContainer, rng.endOffset);
                        sel.extend(rng.startContainer, rng.startOffset);
                    }

                    // adding range isn't always successful so we need to check range count otherwise an exception can occur
                    self.selectedRange = sel.rangeCount > 0 ? sel.getRangeAt(0) : null;
                }
            } else {
                // Is W3C Range fake range on IE
                if (rng.cloneRange) {
                    try {
                        self.tridentSel.addRange(rng);
                        return;
                    } catch (ex) {
                        //IE9 throws an error here if called before selection is placed in the editor
                    }
                }
            }
        },

        /**
         * Sets the current selection to the specified DOM element.
         *
         * @method setNode
         * @param {Element} elm Element to set as the contents of the selection.
         * @return {Element} Returns the element that got passed in.
         * @example
         * // Inserts a DOM node at current selection/caret location
         * tinymce.activeEditor.selection.setNode(tinymce.activeEditor.dom.create('img', {src: 'some.gif', title: 'some title'}));
         */
        setNode: function(elm) {
            var self = this;

            self.setContent(self.dom.getOuterHTML(elm));

            return elm;
        },

        /**
         * Returns the currently selected element or the common ancestor element for both start and end of the selection.
         *
         * @method getNode
         * @return {Element} Currently selected element or common ancestor element.
         * @example
         * // Alerts the currently selected elements node name
         * alert(tinymce.activeEditor.selection.getNode().nodeName);
         */
        getNode: function() {
            var self = this, rng = self.getRng(), elm;
            var startContainer = rng.startContainer, endContainer = rng.endContainer;
            var startOffset = rng.startOffset, endOffset = rng.endOffset, root = self.dom.getRoot();

            function skipEmptyTextNodes(node, forwards) {
                var orig = node;

                while (node && node.nodeType === 3 && node.length === 0) {
                    node = forwards ? node.nextSibling : node.previousSibling;
                }

                return node || orig;
            }

            // Range maybe lost after the editor is made visible again
            if (!rng) {
                return root;
            }

            if (rng.setStart) {
                elm = rng.commonAncestorContainer;

                // Handle selection a image or other control like element such as anchors
                if (!rng.collapsed) {
                    if (startContainer == endContainer) {
                        if (endOffset - startOffset < 2) {
                            if (startContainer.hasChildNodes()) {
                                elm = startContainer.childNodes[startOffset];
                            }
                        }
                    }

                    // If the anchor node is a element instead of a text node then return this element
                    //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
                    //    return sel.anchorNode.childNodes[sel.anchorOffset];

                    // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
                    // This happens when you double click an underlined word in FireFox.
                    if (startContainer.nodeType === 3 && endContainer.nodeType === 3) {
                        if (startContainer.length === startOffset) {
                            startContainer = skipEmptyTextNodes(startContainer.nextSibling, true);
                        } else {
                            startContainer = startContainer.parentNode;
                        }

                        if (endOffset === 0) {
                            endContainer = skipEmptyTextNodes(endContainer.previousSibling, false);
                        } else {
                            endContainer = endContainer.parentNode;
                        }

                        if (startContainer && startContainer === endContainer) {
                            return startContainer;
                        }
                    }
                }

                if (elm && elm.nodeType == 3) {
                    return elm.parentNode;
                }

                return elm;
            }

            elm = rng.item ? rng.item(0) : rng.parentElement();

            // IE 7 might return elements outside the iframe
            if (elm.ownerDocument !== self.win.document) {
                elm = root;
            }

            return elm;
        },

        getSelectedBlocks: function(startElm, endElm) {
            var self = this, dom = self.dom, node, root, selectedBlocks = [];

            root = dom.getRoot();
            startElm = dom.getParent(startElm || self.getStart(), dom.isBlock);
            endElm = dom.getParent(endElm || self.getEnd(), dom.isBlock);

            if (startElm && startElm != root) {
                selectedBlocks.push(startElm);
            }

            if (startElm && endElm && startElm != endElm) {
                node = startElm;

                var walker = new TreeWalker(startElm, root);
                while ((node = walker.next()) && node != endElm) {
                    if (dom.isBlock(node)) {
                        selectedBlocks.push(node);
                    }
                }
            }

            if (endElm && startElm != endElm && endElm != root) {
                selectedBlocks.push(endElm);
            }

            return selectedBlocks;
        },

        isForward: function() {
            var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;

            // No support for selection direction then always return true
            if (!sel || !sel.anchorNode || !sel.focusNode) {
                return true;
            }

            anchorRange = dom.createRng();
            anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
            anchorRange.collapse(true);

            focusRange = dom.createRng();
            focusRange.setStart(sel.focusNode, sel.focusOffset);
            focusRange.collapse(true);

            return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
        },

        normalize: function() {
            var self = this, rng = self.getRng();

            if (!isIE && new RangeUtils(self.dom).normalize(rng)) {
                self.setRng(rng, self.isForward());
            }

            return rng;
        },

        /**
         * Executes callback of the current selection matches the specified selector or not and passes the state and args to the callback.
         *
         * @method selectorChanged
         * @param {String} selector CSS selector to check for.
         * @param {function} callback Callback with state and args when the selector is matches or not.
         */
        selectorChanged: function(selector, callback) {
            var self = this, currentSelectors;

            if (!self.selectorChangedData) {
                self.selectorChangedData = {};
                currentSelectors = {};

                self.editor.on('NodeChange', function(e) {
                    var node = e.element, dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};

                    // Check for new matching selectors
                    each(self.selectorChangedData, function(callbacks, selector) {
                        each(parents, function(node) {
                            if (dom.is(node, selector)) {
                                if (!currentSelectors[selector]) {
                                    // Execute callbacks
                                    each(callbacks, function(callback) {
                                        callback(true, {node: node, selector: selector, parents: parents});
                                    });

                                    currentSelectors[selector] = callbacks;
                                }

                                matchedSelectors[selector] = callbacks;
                                return false;
                            }
                        });
                    });

                    // Check if current selectors still match
                    each(currentSelectors, function(callbacks, selector) {
                        if (!matchedSelectors[selector]) {
                            delete currentSelectors[selector];

                            each(callbacks, function(callback) {
                                callback(false, {node: node, selector: selector, parents: parents});
                            });
                        }
                    });
                });
            }

            // Add selector listeners
            if (!self.selectorChangedData[selector]) {
                self.selectorChangedData[selector] = [];
            }

            self.selectorChangedData[selector].push(callback);

            return self;
        },

        getScrollContainer: function() {
            var scrollContainer, node = this.dom.getRoot();

            while (node && node.nodeName != 'BODY') {
                if (node.scrollHeight > node.clientHeight) {
                    scrollContainer = node;
                    break;
                }

                node = node.parentNode;
            }

            return scrollContainer;
        },

        scrollIntoView: function(elm) {
            var y, viewPort, self = this, dom = self.dom, root = dom.getRoot(), viewPortY, viewPortH;

            function getPos(elm) {
                var x = 0, y = 0;

                var offsetParent = elm;
                while (offsetParent && offsetParent.nodeType) {
                    x += offsetParent.offsetLeft || 0;
                    y += offsetParent.offsetTop || 0;
                    offsetParent = offsetParent.offsetParent;
                }

                return {x: x, y: y};
            }

            if (root.nodeName != 'BODY') {
                var scrollContainer = self.getScrollContainer();
                if (scrollContainer) {
                    y = getPos(elm).y - getPos(scrollContainer).y;
                    viewPortH = scrollContainer.clientHeight;
                    viewPortY = scrollContainer.scrollTop;
                    if (y < viewPortY || y + 25 > viewPortY + viewPortH) {
                        scrollContainer.scrollTop = y < viewPortY ? y : y - viewPortH + 25;
                    }

                    return;
                }
            }

            viewPort = dom.getViewPort(self.editor.getWin());
            y = dom.getPos(elm).y;
            viewPortY = viewPort.y;
            viewPortH = viewPort.h;
            if (y < viewPort.y || y + 25 > viewPortY + viewPortH) {
                self.editor.getWin().scrollTo(0, y < viewPortY ? y : y - viewPortH + 25);
            }
        },

        _moveEndPoint: function(rng, node, start) {
            var root = node, walker = new TreeWalker(node, root);
            var nonEmptyElementsMap = this.dom.schema.getNonEmptyElements();

            do {
                // Text node
                if (node.nodeType == 3 && trim(node.nodeValue).length !== 0) {
                    if (start) {
                        rng.setStart(node, 0);
                    } else {
                        rng.setEnd(node, node.nodeValue.length);
                    }

                    return;
                }

                // BR/IMG/INPUT elements
                if (nonEmptyElementsMap[node.nodeName]) {
                    if (start) {
                        rng.setStartBefore(node);
                    } else {
                        if (node.nodeName == 'BR') {
                            rng.setEndBefore(node);
                        } else {
                            rng.setEndAfter(node);
                        }
                    }

                    return;
                }

                // Found empty text block old IE can place the selection inside those
                if (Env.ie && Env.ie < 11 && this.dom.isBlock(node) && this.dom.isEmpty(node)) {
                    if (start) {
                        rng.setStart(node, 0);
                    } else {
                        rng.setEnd(node, 0);
                    }

                    return;
                }
            } while ((node = (start ? walker.next() : walker.prev())));

            // Failed to find any text node or other suitable location then move to the root of body
            if (root.nodeName == 'BODY') {
                if (start) {
                    rng.setStart(root, 0);
                } else {
                    rng.setEnd(root, root.childNodes.length);
                }
            }
        },

        destroy: function() {
            this.win = null;
            this.controlSelection.destroy();
        }
    };

    return Selection;
});

// Included from: js/tinymce/classes/dom/ElementUtils.js

/**
 * ElementUtils.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Utility class for various element specific functions.
 *
 * @private
 */
define("tinymce/dom/ElementUtils", [
    "tinymce/dom/BookmarkManager",
    "tinymce/util/Tools"
], function(BookmarkManager, Tools) {
    var each = Tools.each;

    function ElementUtils(dom) {
        /**
         * Compares two nodes and checks if it's attributes and styles matches.
         * This doesn't compare classes as items since their order is significant.
         *
         * @method compare
         * @param {Node} node1 First node to compare with.
         * @param {Node} node2 Second node to compare with.
         * @return {boolean} True/false if the nodes are the same or not.
         */
        this.compare = function(node1, node2) {
            // Not the same name
            if (node1.nodeName != node2.nodeName) {
                return false;
            }

            /**
             * Returns all the nodes attributes excluding internal ones, styles and classes.
             *
             * @private
             * @param {Node} node Node to get attributes from.
             * @return {Object} Name/value object with attributes and attribute values.
             */
            function getAttribs(node) {
                var attribs = {};

                each(dom.getAttribs(node), function(attr) {
                    var name = attr.nodeName.toLowerCase();

                    // Don't compare internal attributes or style
                    if (name.indexOf('_') !== 0 && name !== 'style' && name !== 'data-mce-style') {
                        attribs[name] = dom.getAttrib(node, name);
                    }
                });

                return attribs;
            }

            /**
             * Compares two objects checks if it's key + value exists in the other one.
             *
             * @private
             * @param {Object} obj1 First object to compare.
             * @param {Object} obj2 Second object to compare.
             * @return {boolean} True/false if the objects matches or not.
             */
            function compareObjects(obj1, obj2) {
                var value, name;

                for (name in obj1) {
                    // Obj1 has item obj2 doesn't have
                    if (obj1.hasOwnProperty(name)) {
                        value = obj2[name];

                        // Obj2 doesn't have obj1 item
                        if (typeof value == "undefined") {
                            return false;
                        }

                        // Obj2 item has a different value
                        if (obj1[name] != value) {
                            return false;
                        }

                        // Delete similar value
                        delete obj2[name];
                    }
                }

                // Check if obj 2 has something obj 1 doesn't have
                for (name in obj2) {
                    // Obj2 has item obj1 doesn't have
                    if (obj2.hasOwnProperty(name)) {
                        return false;
                    }
                }

                return true;
            }

            // Attribs are not the same
            if (!compareObjects(getAttribs(node1), getAttribs(node2))) {
                return false;
            }

            // Styles are not the same
            if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) {
                return false;
            }

            return !BookmarkManager.isBookmarkNode(node1) && !BookmarkManager.isBookmarkNode(node2);
        };
    }

    return ElementUtils;
});

// Included from: js/tinymce/classes/fmt/Preview.js

/**
 * Preview.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Internal class for generating previews styles for formats.
 *
 * Example:
 *  Preview.getCssText(editor, 'bold');
 *
 * @class tinymce.fmt.Preview
 * @private
 */
define("tinymce/fmt/Preview", [
    "tinymce/util/Tools"
], function(Tools) {
    var each = Tools.each;

    function getCssText(editor, format) {
        var name, previewElm, dom = editor.dom;
        var previewCss = '', parentFontSize, previewStyles;

        previewStyles = editor.settings.preview_styles;

        // No preview forced
        if (previewStyles === false) {
            return '';
        }

        // Default preview
        if (!previewStyles) {
            previewStyles = 'font-family font-size font-weight font-style text-decoration ' +
                'text-transform color background-color border border-radius outline text-shadow';
        }

        // Removes any variables since these can't be previewed
        function removeVars(val) {
            return val.replace(/%(\w+)/g, '');
        }

        // Create block/inline element to use for preview
        if (typeof(format) == "string") {
            format = editor.formatter.get(format);
            if (!format) {
                return;
            }

            format = format[0];
        }

        name = format.block || format.inline || 'span';
        previewElm = dom.create(name);

        // Add format styles to preview element
        each(format.styles, function(value, name) {
            value = removeVars(value);

            if (value) {
                dom.setStyle(previewElm, name, value);
            }
        });

        // Add attributes to preview element
        each(format.attributes, function(value, name) {
            value = removeVars(value);

            if (value) {
                dom.setAttrib(previewElm, name, value);
            }
        });

        // Add classes to preview element
        each(format.classes, function(value) {
            value = removeVars(value);

            if (!dom.hasClass(previewElm, value)) {
                dom.addClass(previewElm, value);
            }
        });

        editor.fire('PreviewFormats');

        // Add the previewElm outside the visual area
        dom.setStyles(previewElm, {position: 'absolute', left: -0xFFFF});
        editor.getBody().appendChild(previewElm);

        // Get parent container font size so we can compute px values out of em/% for older IE:s
        parentFontSize = dom.getStyle(editor.getBody(), 'fontSize', true);
        parentFontSize = /px$/.test(parentFontSize) ? parseInt(parentFontSize, 10) : 0;

        each(previewStyles.split(' '), function(name) {
            var value = dom.getStyle(previewElm, name, true);

            // If background is transparent then check if the body has a background color we can use
            if (name == 'background-color' && /transparent|rgba\s*\([^)]+,\s*0\)/.test(value)) {
                value = dom.getStyle(editor.getBody(), name, true);

                // Ignore white since it's the default color, not the nicest fix
                // TODO: Fix this by detecting runtime style
                if (dom.toHex(value).toLowerCase() == '#ffffff') {
                    return;
                }
            }

            if (name == 'color') {
                // Ignore black since it's the default color, not the nicest fix
                // TODO: Fix this by detecting runtime style
                if (dom.toHex(value).toLowerCase() == '#000000') {
                    return;
                }
            }

            // Old IE won't calculate the font size so we need to do that manually
            if (name == 'font-size') {
                if (/em|%$/.test(value)) {
                    if (parentFontSize === 0) {
                        return;
                    }

                    // Convert font size from em/% to px
                    value = parseFloat(value, 10) / (/%$/.test(value) ? 100 : 1);
                    value = (value * parentFontSize) + 'px';
                }
            }

            if (name == "border" && value) {
                previewCss += 'padding:0 2px;';
            }

            previewCss += name + ':' + value + ';';
        });

        editor.fire('AfterPreviewFormats');

        //previewCss += 'line-height:normal';

        dom.remove(previewElm);

        return previewCss;
    }

    return {
        getCssText: getCssText
    };
});

// Included from: js/tinymce/classes/Formatter.js

/**
 * Formatter.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Text formatter engine class. This class is used to apply formats like bold, italic, font size
 * etc to the current selection or specific nodes. This engine was build to replace the browsers
 * default formatting logic for execCommand due to it's inconsistent and buggy behavior.
 *
 * @class tinymce.Formatter
 * @example
 *  tinymce.activeEditor.formatter.register('mycustomformat', {
 *    inline: 'span',
 *    styles: {color: '#ff0000'}
 *  });
 *
 *  tinymce.activeEditor.formatter.apply('mycustomformat');
 */
define("tinymce/Formatter", [
    "tinymce/dom/TreeWalker",
    "tinymce/dom/RangeUtils",
    "tinymce/dom/BookmarkManager",
    "tinymce/dom/ElementUtils",
    "tinymce/util/Tools",
    "tinymce/fmt/Preview"
], function(TreeWalker, RangeUtils, BookmarkManager, ElementUtils, Tools, Preview) {
    /**
     * Constructs a new formatter instance.
     *
     * @constructor Formatter
     * @param {tinymce.Editor} ed Editor instance to construct the formatter engine to.
     */
    return function(ed) {
        var formats = {},
            dom = ed.dom,
            selection = ed.selection,
            rangeUtils = new RangeUtils(dom),
            isValid = ed.schema.isValidChild,
            isBlock = dom.isBlock,
            forcedRootBlock = ed.settings.forced_root_block,
            nodeIndex = dom.nodeIndex,
            INVISIBLE_CHAR = '\uFEFF',
            MCE_ATTR_RE = /^(src|href|style)$/,
            FALSE = false,
            TRUE = true,
            formatChangeData,
            undef,
            getContentEditable = dom.getContentEditable,
            disableCaretContainer,
            markCaretContainersBogus,
            isBookmarkNode = BookmarkManager.isBookmarkNode;

        var each = Tools.each,
            grep = Tools.grep,
            walk = Tools.walk,
            extend = Tools.extend;

        function isTextBlock(name) {
            if (name.nodeType) {
                name = name.nodeName;
            }

            return !!ed.schema.getTextBlockElements()[name.toLowerCase()];
        }

        function getParents(node, selector) {
            return dom.getParents(node, selector, dom.getRoot());
        }

        function isCaretNode(node) {
            return node.nodeType === 1 && node.id === '_mce_caret';
        }

        function defaultFormats() {
            register({
                valigntop: [
                    {selector: 'td,th', styles: {'verticalAlign': 'top'}}
                ],

                valignmiddle: [
                    {selector: 'td,th', styles: {'verticalAlign': 'middle'}}
                ],

                valignbottom: [
                    {selector: 'td,th', styles: {'verticalAlign': 'bottom'}}
                ],

                alignleft: [
                    {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'left'}, defaultBlock: 'div'},
                    {selector: 'img,table', collapsed: false, styles: {'float': 'left'}}
                ],

                aligncenter: [
                    {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'center'}, defaultBlock: 'div'},
                    {selector: 'img', collapsed: false, styles: {display: 'block', marginLeft: 'auto', marginRight: 'auto'}},
                    {selector: 'table', collapsed: false, styles: {marginLeft: 'auto', marginRight: 'auto'}}
                ],

                alignright: [
                    {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'right'}, defaultBlock: 'div'},
                    {selector: 'img,table', collapsed: false, styles: {'float': 'right'}}
                ],

                alignjustify: [
                    {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'justify'}, defaultBlock: 'div'}
                ],

                bold: [
                    {inline: 'strong', remove: 'all'},
                    {inline: 'span', styles: {fontWeight: 'bold'}},
                    {inline: 'b', remove: 'all'}
                ],

                italic: [
                    {inline: 'em', remove: 'all'},
                    {inline: 'span', styles: {fontStyle: 'italic'}},
                    {inline: 'i', remove: 'all'}
                ],

                underline: [
                    {inline: 'span', styles: {textDecoration: 'underline'}, exact: true},
                    {inline: 'u', remove: 'all'}
                ],

                strikethrough: [
                    {inline: 'span', styles: {textDecoration: 'line-through'}, exact: true},
                    {inline: 'strike', remove: 'all'}
                ],

                forecolor: {inline: 'span', styles: {color: '%value'}, wrap_links: false, remove_similar: true},
                hilitecolor: {inline: 'span', styles: {backgroundColor: '%value'}, wrap_links: false, remove_similar: true},
                fontname: {inline: 'span', styles: {fontFamily: '%value'}},
                fontsize: {inline: 'span', styles: {fontSize: '%value'}},
                fontsize_class: {inline: 'span', attributes: {'class': '%value'}},
                blockquote: {block: 'blockquote', wrapper: 1, remove: 'all'},
                subscript: {inline: 'sub'},
                superscript: {inline: 'sup'},
                code: {inline: 'code'},

                link: {inline: 'a', selector: 'a', remove: 'all', split: true, deep: true,
                    onmatch: function() {
                        return true;
                    },

                    onformat: function(elm, fmt, vars) {
                        each(vars, function(value, key) {
                            dom.setAttrib(elm, key, value);
                        });
                    }
                },

                removeformat: [
                    {
                        selector: 'b,strong,em,i,font,u,strike,sub,sup,dfn,code,samp,kbd,var,cite,mark,q',
                        remove: 'all',
                        split: true,
                        expand: false,
                        block_expand: true,
                        deep: true
                    },
                    {selector: 'span', attributes: ['style', 'class'], remove: 'empty', split: true, expand: false, deep: true},
                    {selector: '*', attributes: ['style', 'class'], split: false, expand: false, deep: true}
                ]
            });

            // Register default block formats
            each('p h1 h2 h3 h4 h5 h6 div address pre div dt dd samp'.split(/\s/), function(name) {
                register(name, {block: name, remove: 'all'});
            });

            // Register user defined formats
            register(ed.settings.formats);
        }

        function addKeyboardShortcuts() {
            // Add some inline shortcuts
            ed.addShortcut('ctrl+b', 'bold_desc', 'Bold');
            ed.addShortcut('ctrl+i', 'italic_desc', 'Italic');
            ed.addShortcut('ctrl+u', 'underline_desc', 'Underline');

            // BlockFormat shortcuts keys
            for (var i = 1; i <= 6; i++) {
                ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
            }

            ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
            ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
            ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
        }

        // Public functions

        /**
         * Returns the format by name or all formats if no name is specified.
         *
         * @method get
         * @param {String} name Optional name to retrive by.
         * @return {Array/Object} Array/Object with all registred formats or a specific format.
         */
        function get(name) {
            return name ? formats[name] : formats;
        }

        /**
         * Registers a specific format by name.
         *
         * @method register
         * @param {Object/String} name Name of the format for example "bold".
         * @param {Object/Array} format Optional format object or array of format variants
         * can only be omitted if the first arg is an object.
         */
        function register(name, format) {
            if (name) {
                if (typeof(name) !== 'string') {
                    each(name, function(format, name) {
                        register(name, format);
                    });
                } else {
                    // Force format into array and add it to internal collection
                    format = format.length ? format : [format];

                    each(format, function(format) {
                        // Set deep to false by default on selector formats this to avoid removing
                        // alignment on images inside paragraphs when alignment is changed on paragraphs
                        if (format.deep === undef) {
                            format.deep = !format.selector;
                        }

                        // Default to true
                        if (format.split === undef) {
                            format.split = !format.selector || format.inline;
                        }

                        // Default to true
                        if (format.remove === undef && format.selector && !format.inline) {
                            format.remove = 'none';
                        }

                        // Mark format as a mixed format inline + block level
                        if (format.selector && format.inline) {
                            format.mixed = true;
                            format.block_expand = true;
                        }

                        // Split classes if needed
                        if (typeof(format.classes) === 'string') {
                            format.classes = format.classes.split(/\s+/);
                        }
                    });

                    formats[name] = format;
                }
            }
        }

        function getTextDecoration(node) {
            var decoration;

            ed.dom.getParent(node, function(n) {
                decoration = ed.dom.getStyle(n, 'text-decoration');
                return decoration && decoration !== 'none';
            });

            return decoration;
        }

        function processUnderlineAndColor(node) {
            var textDecoration;
            if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
                textDecoration = getTextDecoration(node.parentNode);
                if (ed.dom.getStyle(node, 'color') && textDecoration) {
                    ed.dom.setStyle(node, 'text-decoration', textDecoration);
                } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
                    ed.dom.setStyle(node, 'text-decoration', null);
                }
            }
        }

        /**
         * Applies the specified format to the current selection or specified node.
         *
         * @method apply
         * @param {String} name Name of format to apply.
         * @param {Object} vars Optional list of variables to replace within format before applying it.
         * @param {Node} node Optional node to apply the format to defaults to current selection.
         */
        function apply(name, vars, node) {
            var formatList = get(name), format = formatList[0], bookmark, rng, isCollapsed = !node && selection.isCollapsed();

            function setElementFormat(elm, fmt) {
                fmt = fmt || format;

                if (elm) {
                    if (fmt.onformat) {
                        fmt.onformat(elm, fmt, vars, node);
                    }

                    each(fmt.styles, function(value, name) {
                        dom.setStyle(elm, name, replaceVars(value, vars));
                    });

                    // Needed for the WebKit span spam bug
                    // TODO: Remove this once WebKit/Blink fixes this
                    if (fmt.styles) {
                        var styleVal = dom.getAttrib(elm, 'style');

                        if (styleVal) {
                            elm.setAttribute('data-mce-style', styleVal);
                        }
                    }

                    each(fmt.attributes, function(value, name) {
                        dom.setAttrib(elm, name, replaceVars(value, vars));
                    });

                    each(fmt.classes, function(value) {
                        value = replaceVars(value, vars);

                        if (!dom.hasClass(elm, value)) {
                            dom.addClass(elm, value);
                        }
                    });
                }
            }

            function adjustSelectionToVisibleSelection() {
                function findSelectionEnd(start, end) {
                    var walker = new TreeWalker(end);
                    for (node = walker.current(); node; node = walker.prev()) {
                        if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
                            return node;
                        }
                    }
                }

                // Adjust selection so that a end container with a end offset of zero is not included in the selection
                // as this isn't visible to the user.
                var rng = ed.selection.getRng();
                var start = rng.startContainer;
                var end = rng.endContainer;

                if (start != end && rng.endOffset === 0) {
                    var newEnd = findSelectionEnd(start, end);
                    var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;

                    rng.setEnd(newEnd, endOffset);
                }

                return rng;
            }

            function applyRngStyle(rng, bookmark, node_specific) {
                var newWrappers = [], wrapName, wrapElm, contentEditable = true;

                // Setup wrapper element
                wrapName = format.inline || format.block;
                wrapElm = dom.create(wrapName);
                setElementFormat(wrapElm);

                rangeUtils.walk(rng, function(nodes) {
                    var currentWrapElm;

                    /**
                     * Process a list of nodes wrap them.
                     */
                    function process(node) {
                        var nodeName, parentName, found, hasContentEditableState, lastContentEditable;

                        lastContentEditable = contentEditable;
                        nodeName = node.nodeName.toLowerCase();
                        parentName = node.parentNode.nodeName.toLowerCase();

                        // Node has a contentEditable value
                        if (node.nodeType === 1 && getContentEditable(node)) {
                            lastContentEditable = contentEditable;
                            contentEditable = getContentEditable(node) === "true";
                            hasContentEditableState = true; // We don't want to wrap the container only it's children
                        }

                        // Stop wrapping on br elements
                        if (isEq(nodeName, 'br')) {
                            currentWrapElm = 0;

                            // Remove any br elements when we wrap things
                            if (format.block) {
                                dom.remove(node);
                            }

                            return;
                        }

                        // If node is wrapper type
                        if (format.wrapper && matchNode(node, name, vars)) {
                            currentWrapElm = 0;
                            return;
                        }

                        // Can we rename the block
                        // TODO: Break this if up, too complex
                        if (contentEditable && !hasContentEditableState && format.block &&
                            !format.wrapper && isTextBlock(nodeName) && isValid(parentName, wrapName)) {
                            node = dom.rename(node, wrapName);
                            setElementFormat(node);
                            newWrappers.push(node);
                            currentWrapElm = 0;
                            return;
                        }

                        // Handle selector patterns
                        if (format.selector) {
                            // Look for matching formats
                            each(formatList, function(format) {
                                // Check collapsed state if it exists
                                if ('collapsed' in format && format.collapsed !== isCollapsed) {
                                    return;
                                }

                                if (dom.is(node, format.selector) && !isCaretNode(node)) {
                                    setElementFormat(node, format);
                                    found = true;
                                }
                            });

                            // Continue processing if a selector match wasn't found and a inline element is defined
                            if (!format.inline || found) {
                                currentWrapElm = 0;
                                return;
                            }
                        }

                        // Is it valid to wrap this item
                        // TODO: Break this if up, too complex
                        if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
                                !(!node_specific && node.nodeType === 3 &&
                                node.nodeValue.length === 1 &&
                                node.nodeValue.charCodeAt(0) === 65279) &&
                                !isCaretNode(node) &&
                                (!format.inline || !isBlock(node))) {
                            // Start wrapping
                            if (!currentWrapElm) {
                                // Wrap the node
                                currentWrapElm = dom.clone(wrapElm, FALSE);
                                node.parentNode.insertBefore(currentWrapElm, node);
                                newWrappers.push(currentWrapElm);
                            }

                            currentWrapElm.appendChild(node);
                        } else {
                            // Start a new wrapper for possible children
                            currentWrapElm = 0;

                            each(grep(node.childNodes), process);

                            if (hasContentEditableState) {
                                contentEditable = lastContentEditable; // Restore last contentEditable state from stack
                            }

                            // End the last wrapper
                            currentWrapElm = 0;
                        }
                    }

                    // Process siblings from range
                    each(nodes, process);
                });

                // Wrap links inside as well, for example color inside a link when the wrapper is around the link
                if (format.wrap_links === false) {
                    each(newWrappers, function(node) {
                        function process(node) {
                            var i, currentWrapElm, children;

                            if (node.nodeName === 'A') {
                                currentWrapElm = dom.clone(wrapElm, FALSE);
                                newWrappers.push(currentWrapElm);

                                children = grep(node.childNodes);
                                for (i = 0; i < children.length; i++) {
                                    currentWrapElm.appendChild(children[i]);
                                }

                                node.appendChild(currentWrapElm);
                            }

                            each(grep(node.childNodes), process);
                        }

                        process(node);
                    });
                }

                // Cleanup
                each(newWrappers, function(node) {
                    var childCount;

                    function getChildCount(node) {
                        var count = 0;

                        each(node.childNodes, function(node) {
                            if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) {
                                count++;
                            }
                        });

                        return count;
                    }

                    function mergeStyles(node) {
                        var child, clone;

                        each(node.childNodes, function(node) {
                            if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
                                child = node;
                                return FALSE; // break loop
                            }
                        });

                        // If child was found and of the same type as the current node
                        if (child && !isBookmarkNode(child) && matchName(child, format)) {
                            clone = dom.clone(child, FALSE);
                            setElementFormat(clone);

                            dom.replace(clone, node, TRUE);
                            dom.remove(child, 1);
                        }

                        return clone || node;
                    }

                    childCount = getChildCount(node);

                    // Remove empty nodes but only if there is multiple wrappers and they are not block
                    // elements so never remove single <h1></h1> since that would remove the
                    // currrent empty block element where the caret is at
                    if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
                        dom.remove(node, 1);
                        return;
                    }

                    if (format.inline || format.wrapper) {
                        // Merges the current node with it's children of similar type to reduce the number of elements
                        if (!format.exact && childCount === 1) {
                            node = mergeStyles(node);
                        }

                        // Remove/merge children
                        each(formatList, function(format) {
                            // Merge all children of similar type will move styles from child to parent
                            // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
                            // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
                            each(dom.select(format.inline, node), function(child) {
                                var parent;

                                if (isBookmarkNode(child)) {
                                    return;
                                }

                                // When wrap_links is set to false we don't want
                                // to remove the format on children within links
                                if (format.wrap_links === false) {
                                    parent = child.parentNode;

                                    do {
                                        if (parent.nodeName === 'A') {
                                            return;
                                        }
                                    } while ((parent = parent.parentNode));
                                }

                                removeFormat(format, vars, child, format.exact ? child : null);
                            });
                        });

                        // Remove child if direct parent is of same type
                        if (matchNode(node.parentNode, name, vars)) {
                            dom.remove(node, 1);
                            node = 0;
                            return TRUE;
                        }

                        // Look for parent with similar style format
                        if (format.merge_with_parents) {
                            dom.getParent(node.parentNode, function(parent) {
                                if (matchNode(parent, name, vars)) {
                                    dom.remove(node, 1);
                                    node = 0;
                                    return TRUE;
                                }
                            });
                        }

                        // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
                        if (node && format.merge_siblings !== false) {
                            node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
                            node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
                        }
                    }
                });
            }

            if (format) {
                if (node) {
                    if (node.nodeType) {
                        rng = dom.createRng();
                        rng.setStartBefore(node);
                        rng.setEndAfter(node);
                        applyRngStyle(expandRng(rng, formatList), null, true);
                    } else {
                        applyRngStyle(node, null, true);
                    }
                } else {
                    if (!isCollapsed || !format.inline || dom.select('td.mce-item-selected,th.mce-item-selected').length) {
                        // Obtain selection node before selection is unselected by applyRngStyle()
                        var curSelNode = ed.selection.getNode();

                        // If the formats have a default block and we can't find a parent block then
                        // start wrapping it with a DIV this is for forced_root_blocks: false
                        // It's kind of a hack but people should be using the default block type P since all desktop editors work that way
                        if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
                            apply(formatList[0].defaultBlock);
                        }

                        // Apply formatting to selection
                        ed.selection.setRng(adjustSelectionToVisibleSelection());
                        bookmark = selection.getBookmark();
                        applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);

                        // Colored nodes should be underlined so that the color of the underline matches the text color.
                        if (format.styles && (format.styles.color || format.styles.textDecoration)) {
                            walk(curSelNode, processUnderlineAndColor, 'childNodes');
                            processUnderlineAndColor(curSelNode);
                        }

                        selection.moveToBookmark(bookmark);
                        moveStart(selection.getRng(TRUE));
                        ed.nodeChanged();
                    } else {
                        performCaretAction('apply', name, vars);
                    }
                }
            }
        }

        /**
         * Removes the specified format from the current selection or specified node.
         *
         * @method remove
         * @param {String} name Name of format to remove.
         * @param {Object} vars Optional list of variables to replace within format before removing it.
         * @param {Node/Range} node Optional node or DOM range to remove the format from defaults to current selection.
         */
        function remove(name, vars, node, similar) {
            var formatList = get(name), format = formatList[0], bookmark, rng, contentEditable = true;

            // Merges the styles for each node
            function process(node) {
                var children, i, l, lastContentEditable, hasContentEditableState;

                // Node has a contentEditable value
                if (node.nodeType === 1 && getContentEditable(node)) {
                    lastContentEditable = contentEditable;
                    contentEditable = getContentEditable(node) === "true";
                    hasContentEditableState = true; // We don't want to wrap the container only it's children
                }

                // Grab the children first since the nodelist might be changed
                children = grep(node.childNodes);

                // Process current node
                if (contentEditable && !hasContentEditableState) {
                    for (i = 0, l = formatList.length; i < l; i++) {
                        if (removeFormat(formatList[i], vars, node, node)) {
                            break;
                        }
                    }
                }

                // Process the children
                if (format.deep) {
                    if (children.length) {
                        for (i = 0, l = children.length; i < l; i++) {
                            process(children[i]);
                        }

                        if (hasContentEditableState) {
                            contentEditable = lastContentEditable; // Restore last contentEditable state from stack
                        }
                    }
                }
            }

            function findFormatRoot(container) {
                var formatRoot;

                // Find format root
                each(getParents(container.parentNode).reverse(), function(parent) {
                    var format;

                    // Find format root element
                    if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
                        // Is the node matching the format we are looking for
                        format = matchNode(parent, name, vars, similar);
                        if (format && format.split !== false) {
                            formatRoot = parent;
                        }
                    }
                });

                return formatRoot;
            }

            function wrapAndSplit(formatRoot, container, target, split) {
                var parent, clone, lastClone, firstClone, i, formatRootParent;

                // Format root found then clone formats and split it
                if (formatRoot) {
                    formatRootParent = formatRoot.parentNode;

                    for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
                        clone = dom.clone(parent, FALSE);

                        for (i = 0; i < formatList.length; i++) {
                            if (removeFormat(formatList[i], vars, clone, clone)) {
                                clone = 0;
                                break;
                            }
                        }

                        // Build wrapper node
                        if (clone) {
                            if (lastClone) {
                                clone.appendChild(lastClone);
                            }

                            if (!firstClone) {
                                firstClone = clone;
                            }

                            lastClone = clone;
                        }
                    }

                    // Never split block elements if the format is mixed
                    if (split && (!format.mixed || !isBlock(formatRoot))) {
                        container = dom.split(formatRoot, container);
                    }

                    // Wrap container in cloned formats
                    if (lastClone) {
                        target.parentNode.insertBefore(lastClone, target);
                        firstClone.appendChild(target);
                    }
                }

                return container;
            }

            function splitToFormatRoot(container) {
                return wrapAndSplit(findFormatRoot(container), container, container, true);
            }

            function unwrap(start) {
                var node = dom.get(start ? '_start' : '_end'),
                    out = node[start ? 'firstChild' : 'lastChild'];

                // If the end is placed within the start the result will be removed
                // So this checks if the out node is a bookmark node if it is it
                // checks for another more suitable node
                if (isBookmarkNode(out)) {
                    out = out[start ? 'firstChild' : 'lastChild'];
                }

                dom.remove(node, true);

                return out;
            }

            function removeRngStyle(rng) {
                var startContainer, endContainer;
                var commonAncestorContainer = rng.commonAncestorContainer;

                rng = expandRng(rng, formatList, TRUE);

                if (format.split) {
                    startContainer = getContainer(rng, TRUE);
                    endContainer = getContainer(rng);

                    if (startContainer != endContainer) {
                        // WebKit will render the table incorrectly if we wrap a TH or TD in a SPAN
                        // so let's see if we can use the first child instead
                        // This will happen if you triple click a table cell and use remove formatting
                        if (/^(TR|TH|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
                            if (startContainer.nodeName == "TR") {
                                startContainer = startContainer.firstChild.firstChild || startContainer;
                            } else {
                                startContainer = startContainer.firstChild || startContainer;
                            }
                        }

                        // Try to adjust endContainer as well if cells on the same row were selected - bug #6410
                        if (commonAncestorContainer &&
                            /^T(HEAD|BODY|FOOT|R)$/.test(commonAncestorContainer.nodeName) &&
                            /^(TH|TD)$/.test(endContainer.nodeName) && endContainer.firstChild) {
                            endContainer = endContainer.firstChild || endContainer;
                        }

                        // Wrap start/end nodes in span element since these might be cloned/moved
                        startContainer = wrap(startContainer, 'span', {id: '_start', 'data-mce-type': 'bookmark'});
                        endContainer = wrap(endContainer, 'span', {id: '_end', 'data-mce-type': 'bookmark'});

                        // Split start/end
                        splitToFormatRoot(startContainer);
                        splitToFormatRoot(endContainer);

                        // Unwrap start/end to get real elements again
                        startContainer = unwrap(TRUE);
                        endContainer = unwrap();
                    } else {
                        startContainer = endContainer = splitToFormatRoot(startContainer);
                    }

                    // Update range positions since they might have changed after the split operations
                    rng.startContainer = startContainer.parentNode;
                    rng.startOffset = nodeIndex(startContainer);
                    rng.endContainer = endContainer.parentNode;
                    rng.endOffset = nodeIndex(endContainer) + 1;
                }

                // Remove items between start/end
                rangeUtils.walk(rng, function(nodes) {
                    each(nodes, function(node) {
                        process(node);

                        // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
                        if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' &&
                            node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
                            removeFormat({
                                'deep': false,
                                'exact': true,
                                'inline': 'span',
                                'styles': {
                                    'textDecoration': 'underline'
                                }
                            }, null, node);
                        }
                    });
                });
            }

            // Handle node
            if (node) {
                if (node.nodeType) {
                    rng = dom.createRng();
                    rng.setStartBefore(node);
                    rng.setEndAfter(node);
                    removeRngStyle(rng);
                } else {
                    removeRngStyle(node);
                }

                return;
            }

            if (!selection.isCollapsed() || !format.inline || dom.select('td.mce-item-selected,th.mce-item-selected').length) {
                bookmark = selection.getBookmark();
                removeRngStyle(selection.getRng(TRUE));
                selection.moveToBookmark(bookmark);

                // Check if start element still has formatting then we are at: "<b>text|</b>text"
                // and need to move the start into the next text node
                if (format.inline && match(name, vars, selection.getStart())) {
                    moveStart(selection.getRng(true));
                }

                ed.nodeChanged();
            } else {
                performCaretAction('remove', name, vars, similar);
            }
        }

        /**
         * Toggles the specified format on/off.
         *
         * @method toggle
         * @param {String} name Name of format to apply/remove.
         * @param {Object} vars Optional list of variables to replace within format before applying/removing it.
         * @param {Node} node Optional node to apply the format to or remove from. Defaults to current selection.
         */
        function toggle(name, vars, node) {
            var fmt = get(name);

            if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) {
                remove(name, vars, node);
            } else {
                apply(name, vars, node);
            }
        }

        /**
         * Return true/false if the specified node has the specified format.
         *
         * @method matchNode
         * @param {Node} node Node to check the format on.
         * @param {String} name Format name to check.
         * @param {Object} vars Optional list of variables to replace before checking it.
         * @param {Boolean} similar Match format that has similar properties.
         * @return {Object} Returns the format object it matches or undefined if it doesn't match.
         */
        function matchNode(node, name, vars, similar) {
            var formatList = get(name), format, i, classes;

            function matchItems(node, format, item_name) {
                var key, value, items = format[item_name], i;

                // Custom match
                if (format.onmatch) {
                    return format.onmatch(node, format, item_name);
                }

                // Check all items
                if (items) {
                    // Non indexed object
                    if (items.length === undef) {
                        for (key in items) {
                            if (items.hasOwnProperty(key)) {
                                if (item_name === 'attributes') {
                                    value = dom.getAttrib(node, key);
                                } else {
                                    value = getStyle(node, key);
                                }

                                if (similar && !value && !format.exact) {
                                    return;
                                }

                                if ((!similar || format.exact) && !isEq(value, normalizeStyleValue(replaceVars(items[key], vars), key))) {
                                    return;
                                }
                            }
                        }
                    } else {
                        // Only one match needed for indexed arrays
                        for (i = 0; i < items.length; i++) {
                            if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) {
                                return format;
                            }
                        }
                    }
                }

                return format;
            }

            if (formatList && node) {
                // Check each format in list
                for (i = 0; i < formatList.length; i++) {
                    format = formatList[i];

                    // Name name, attributes, styles and classes
                    if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
                        // Match classes
                        if ((classes = format.classes)) {
                            for (i = 0; i < classes.length; i++) {
                                if (!dom.hasClass(node, classes[i])) {
                                    return;
                                }
                            }
                        }

                        return format;
                    }
                }
            }
        }

        /**
         * Matches the current selection or specified node against the specified format name.
         *
         * @method match
         * @param {String} name Name of format to match.
         * @param {Object} vars Optional list of variables to replace before checking it.
         * @param {Node} node Optional node to check.
         * @return {boolean} true/false if the specified selection/node matches the format.
         */
        function match(name, vars, node) {
            var startNode;

            function matchParents(node) {
                var root = dom.getRoot();

                if (node === root) {
                    return false;
                }

                // Find first node with similar format settings
                node = dom.getParent(node, function(node) {
                    return node.parentNode === root || !!matchNode(node, name, vars, true);
                });

                // Do an exact check on the similar format element
                return matchNode(node, name, vars);
            }

            // Check specified node
            if (node) {
                return matchParents(node);
            }

            // Check selected node
            node = selection.getNode();
            if (matchParents(node)) {
                return TRUE;
            }

            // Check start node if it's different
            startNode = selection.getStart();
            if (startNode != node) {
                if (matchParents(startNode)) {
                    return TRUE;
                }
            }

            return FALSE;
        }

        /**
         * Matches the current selection against the array of formats and returns a new array with matching formats.
         *
         * @method matchAll
         * @param {Array} names Name of format to match.
         * @param {Object} vars Optional list of variables to replace before checking it.
         * @return {Array} Array with matched formats.
         */
        function matchAll(names, vars) {
            var startElement, matchedFormatNames = [], checkedMap = {};

            // Check start of selection for formats
            startElement = selection.getStart();
            dom.getParent(startElement, function(node) {
                var i, name;

                for (i = 0; i < names.length; i++) {
                    name = names[i];

                    if (!checkedMap[name] && matchNode(node, name, vars)) {
                        checkedMap[name] = true;
                        matchedFormatNames.push(name);
                    }
                }
            }, dom.getRoot());

            return matchedFormatNames;
        }

        /**
         * Returns true/false if the specified format can be applied to the current selection or not. It
         * will currently only check the state for selector formats, it returns true on all other format types.
         *
         * @method canApply
         * @param {String} name Name of format to check.
         * @return {boolean} true/false if the specified format can be applied to the current selection/node.
         */
        function canApply(name) {
            var formatList = get(name), startNode, parents, i, x, selector;

            if (formatList) {
                startNode = selection.getStart();
                parents = getParents(startNode);

                for (x = formatList.length - 1; x >= 0; x--) {
                    selector = formatList[x].selector;

                    // Format is not selector based then always return TRUE
                    // Is it has a defaultBlock then it's likely it can be applied for example align on a non block element line
                    if (!selector || formatList[x].defaultBlock) {
                        return TRUE;
                    }

                    for (i = parents.length - 1; i >= 0; i--) {
                        if (dom.is(parents[i], selector)) {
                            return TRUE;
                        }
                    }
                }
            }

            return FALSE;
        }

        /**
         * Executes the specified callback when the current selection matches the formats or not.
         *
         * @method formatChanged
         * @param {String} formats Comma separated list of formats to check for.
         * @param {function} callback Callback with state and args when the format is changed/toggled on/off.
         * @param {Boolean} similar True/false state if the match should handle similar or exact formats.
         */
        function formatChanged(formats, callback, similar) {
            var currentFormats;

            // Setup format node change logic
            if (!formatChangeData) {
                formatChangeData = {};
                currentFormats = {};

                ed.on('NodeChange', function(e) {
                    var parents = getParents(e.element), matchedFormats = {};

                    // Ignore bogus nodes like the <a> tag created by moveStart()
                    parents = Tools.grep(parents, function(node) {
                        return !node.getAttribute('data-mce-bogus');
                    });

                    // Check for new formats
                    each(formatChangeData, function(callbacks, format) {
                        each(parents, function(node) {
                            if (matchNode(node, format, {}, callbacks.similar)) {
                                if (!currentFormats[format]) {
                                    // Execute callbacks
                                    each(callbacks, function(callback) {
                                        callback(true, {node: node, format: format, parents: parents});
                                    });

                                    currentFormats[format] = callbacks;
                                }

                                matchedFormats[format] = callbacks;
                                return false;
                            }
                        });
                    });

                    // Check if current formats still match
                    each(currentFormats, function(callbacks, format) {
                        if (!matchedFormats[format]) {
                            delete currentFormats[format];

                            each(callbacks, function(callback) {
                                callback(false, {node: e.element, format: format, parents: parents});
                            });
                        }
                    });
                });
            }

            // Add format listeners
            each(formats.split(','), function(format) {
                if (!formatChangeData[format]) {
                    formatChangeData[format] = [];
                    formatChangeData[format].similar = similar;
                }

                formatChangeData[format].push(callback);
            });

            return this;
        }

        /**
         * Returns a preview css text for the specified format.
         *
         * @method getCssText
         * @param {String/Object} format Format to generate preview css text for.
         * @return {String} Css text for the specified format.
         * @example
         * var cssText1 = editor.formatter.getCssText('bold');
         * var cssText2 = editor.formatter.getCssText({inline: 'b'});
         */
        function getCssText(format) {
            return Preview.getCssText(ed, format);
        }

        // Expose to public
        extend(this, {
            get: get,
            register: register,
            apply: apply,
            remove: remove,
            toggle: toggle,
            match: match,
            matchAll: matchAll,
            matchNode: matchNode,
            canApply: canApply,
            formatChanged: formatChanged,
            getCssText: getCssText
        });

        // Initialize
        defaultFormats();
        addKeyboardShortcuts();
        ed.on('BeforeGetContent', function() {
            if (markCaretContainersBogus) {
                markCaretContainersBogus();
            }
        });
        ed.on('mouseup keydown', function(e) {
            if (disableCaretContainer) {
                disableCaretContainer(e);
            }
        });

        // Private functions

        /**
         * Checks if the specified nodes name matches the format inline/block or selector.
         *
         * @private
         * @param {Node} node Node to match against the specified format.
         * @param {Object} format Format object o match with.
         * @return {boolean} true/false if the format matches.
         */
        function matchName(node, format) {
            // Check for inline match
            if (isEq(node, format.inline)) {
                return TRUE;
            }

            // Check for block match
            if (isEq(node, format.block)) {
                return TRUE;
            }

            // Check for selector match
            if (format.selector) {
                return node.nodeType == 1 && dom.is(node, format.selector);
            }
        }

        /**
         * Compares two string/nodes regardless of their case.
         *
         * @private
         * @param {String/Node} Node or string to compare.
         * @param {String/Node} Node or string to compare.
         * @return {boolean} True/false if they match.
         */
        function isEq(str1, str2) {
            str1 = str1 || '';
            str2 = str2 || '';

            str1 = '' + (str1.nodeName || str1);
            str2 = '' + (str2.nodeName || str2);

            return str1.toLowerCase() == str2.toLowerCase();
        }

        /**
         * Returns the style by name on the specified node. This method modifies the style
         * contents to make it more easy to match. This will resolve a few browser issues.
         *
         * @private
         * @param {Node} node to get style from.
         * @param {String} name Style name to get.
         * @return {String} Style item value.
         */
        function getStyle(node, name) {
            return normalizeStyleValue(dom.getStyle(node, name), name);
        }

        /**
         * Normalize style value by name. This method modifies the style contents
         * to make it more easy to match. This will resolve a few browser issues.
         *
         * @private
         * @param {Node} node to get style from.
         * @param {String} name Style name to get.
         * @return {String} Style item value.
         */
        function normalizeStyleValue(value, name) {
            // Force the format to hex
            if (name == 'color' || name == 'backgroundColor') {
                value = dom.toHex(value);
            }

            // Opera will return bold as 700
            if (name == 'fontWeight' && value == 700) {
                value = 'bold';
            }

            // Normalize fontFamily so "'Font name', Font" becomes: "Font name,Font"
            if (name == 'fontFamily') {
                value = value.replace(/[\'\"]/g, '').replace(/,\s+/g, ',');
            }

            return '' + value;
        }

        /**
         * Replaces variables in the value. The variable format is %var.
         *
         * @private
         * @param {String} value Value to replace variables in.
         * @param {Object} vars Name/value array with variables to replace.
         * @return {String} New value with replaced variables.
         */
        function replaceVars(value, vars) {
            if (typeof(value) != "string") {
                value = value(vars);
            } else if (vars) {
                value = value.replace(/%(\w+)/g, function(str, name) {
                    return vars[name] || str;
                });
            }

            return value;
        }

        function isWhiteSpaceNode(node) {
            return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
        }

        function wrap(node, name, attrs) {
            var wrapper = dom.create(name, attrs);

            node.parentNode.insertBefore(wrapper, node);
            wrapper.appendChild(node);

            return wrapper;
        }

        /**
         * Expands the specified range like object to depending on format.
         *
         * For example on block formats it will move the start/end position
         * to the beginning of the current block.
         *
         * @private
         * @param {Object} rng Range like object.
         * @param {Array} formats Array with formats to expand by.
         * @return {Object} Expanded range like object.
         */
        function expandRng(rng, format, remove) {
            var lastIdx, leaf, endPoint,
                startContainer = rng.startContainer,
                startOffset = rng.startOffset,
                endContainer = rng.endContainer,
                endOffset = rng.endOffset;

            // This function walks up the tree if there is no siblings before/after the node
            function findParentContainer(start) {
                var container, parent, sibling, siblingName, root;

                container = parent = start ? startContainer : endContainer;
                siblingName = start ? 'previousSibling' : 'nextSibling';
                root = dom.getRoot();

                function isBogusBr(node) {
                    return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling;
                }

                // If it's a text node and the offset is inside the text
                if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
                    if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
                        return container;
                    }
                }

                /*eslint no-constant-condition:0 */
                while (true) {
                    // Stop expanding on block elements
                    if (!format[0].block_expand && isBlock(parent)) {
                        return parent;
                    }

                    // Walk left/right
                    for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
                        if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) {
                            return parent;
                        }
                    }

                    // Check if we can move up are we at root level or body level
                    if (parent.parentNode == root) {
                        container = parent;
                        break;
                    }

                    parent = parent.parentNode;
                }

                return container;
            }

            // This function walks down the tree to find the leaf at the selection.
            // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
            function findLeaf(node, offset) {
                if (offset === undef) {
                    offset = node.nodeType === 3 ? node.length : node.childNodes.length;
                }

                while (node && node.hasChildNodes()) {
                    node = node.childNodes[offset];
                    if (node) {
                        offset = node.nodeType === 3 ? node.length : node.childNodes.length;
                    }
                }
                return { node: node, offset: offset };
            }

            // If index based start position then resolve it
            if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
                lastIdx = startContainer.childNodes.length - 1;
                startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];

                if (startContainer.nodeType == 3) {
                    startOffset = 0;
                }
            }

            // If index based end position then resolve it
            if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
                lastIdx = endContainer.childNodes.length - 1;
                endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];

                if (endContainer.nodeType == 3) {
                    endOffset = endContainer.nodeValue.length;
                }
            }

            // Expands the node to the closes contentEditable false element if it exists
            function findParentContentEditable(node) {
                var parent = node;

                while (parent) {
                    if (parent.nodeType === 1 && getContentEditable(parent)) {
                        return getContentEditable(parent) === "false" ? parent : node;
                    }

                    parent = parent.parentNode;
                }

                return node;
            }

            function findWordEndPoint(container, offset, start) {
                var walker, node, pos, lastTextNode;

                function findSpace(node, offset) {
                    var pos, pos2, str = node.nodeValue;

                    if (typeof(offset) == "undefined") {
                        offset = start ? str.length : 0;
                    }

                    if (start) {
                        pos = str.lastIndexOf(' ', offset);
                        pos2 = str.lastIndexOf('\u00a0', offset);
                        pos = pos > pos2 ? pos : pos2;

                        // Include the space on remove to avoid tag soup
                        if (pos !== -1 && !remove) {
                            pos++;
                        }
                    } else {
                        pos = str.indexOf(' ', offset);
                        pos2 = str.indexOf('\u00a0', offset);
                        pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
                    }

                    return pos;
                }

                if (container.nodeType === 3) {
                    pos = findSpace(container, offset);

                    if (pos !== -1) {
                        return {container: container, offset: pos};
                    }

                    lastTextNode = container;
                }

                // Walk the nodes inside the block
                walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
                while ((node = walker[start ? 'prev' : 'next']())) {
                    if (node.nodeType === 3) {
                        lastTextNode = node;
                        pos = findSpace(node);

                        if (pos !== -1) {
                            return {container: node, offset: pos};
                        }
                    } else if (isBlock(node)) {
                        break;
                    }
                }

                if (lastTextNode) {
                    if (start) {
                        offset = 0;
                    } else {
                        offset = lastTextNode.length;
                    }

                    return {container: lastTextNode, offset: offset};
                }
            }

            function findSelectorEndPoint(container, sibling_name) {
                var parents, i, y, curFormat;

                if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) {
                    container = container[sibling_name];
                }

                parents = getParents(container);
                for (i = 0; i < parents.length; i++) {
                    for (y = 0; y < format.length; y++) {
                        curFormat = format[y];

                        // If collapsed state is set then skip formats that doesn't match that
                        if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) {
                            continue;
                        }

                        if (dom.is(parents[i], curFormat.selector)) {
                            return parents[i];
                        }
                    }
                }

                return container;
            }

            function findBlockEndPoint(container, sibling_name) {
                var node, root = dom.getRoot();

                // Expand to block of similar type
                if (!format[0].wrapper) {
                    node = dom.getParent(container, format[0].block, root);
                }

                // Expand to first wrappable block element or any block element
                if (!node) {
                    node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, function(node) {
                        // Fixes #6183 where it would expand to editable parent element in inline mode
                        return node != root && isTextBlock(node);
                    });
                }

                // Exclude inner lists from wrapping
                if (node && format[0].wrapper) {
                    node = getParents(node, 'ul,ol').reverse()[0] || node;
                }

                // Didn't find a block element look for first/last wrappable element
                if (!node) {
                    node = container;

                    while (node[sibling_name] && !isBlock(node[sibling_name])) {
                        node = node[sibling_name];

                        // Break on BR but include it will be removed later on
                        // we can't remove it now since we need to check if it can be wrapped
                        if (isEq(node, 'br')) {
                            break;
                        }
                    }
                }

                return node || container;
            }

            // Expand to closest contentEditable element
            startContainer = findParentContentEditable(startContainer);
            endContainer = findParentContentEditable(endContainer);

            // Exclude bookmark nodes if possible
            if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
                startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
                startContainer = startContainer.nextSibling || startContainer;

                if (startContainer.nodeType == 3) {
                    startOffset = 0;
                }
            }

            if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
                endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
                endContainer = endContainer.previousSibling || endContainer;

                if (endContainer.nodeType == 3) {
                    endOffset = endContainer.length;
                }
            }

            if (format[0].inline) {
                if (rng.collapsed) {
                    // Expand left to closest word boundary
                    endPoint = findWordEndPoint(startContainer, startOffset, true);
                    if (endPoint) {
                        startContainer = endPoint.container;
                        startOffset = endPoint.offset;
                    }

                    // Expand right to closest word boundary
                    endPoint = findWordEndPoint(endContainer, endOffset);
                    if (endPoint) {
                        endContainer = endPoint.container;
                        endOffset = endPoint.offset;
                    }
                }

                // Avoid applying formatting to a trailing space.
                leaf = findLeaf(endContainer, endOffset);
                if (leaf.node) {
                    while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) {
                        leaf = findLeaf(leaf.node.previousSibling);
                    }

                    if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
                            leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {

                        if (leaf.offset > 1) {
                            endContainer = leaf.node;
                            endContainer.splitText(leaf.offset - 1);
                        }
                    }
                }
            }

            // Move start/end point up the tree if the leaves are sharp and if we are in different containers
            // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
            // This will reduce the number of wrapper elements that needs to be created
            // Move start point up the tree
            if (format[0].inline || format[0].block_expand) {
                if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
                    startContainer = findParentContainer(true);
                }

                if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
                    endContainer = findParentContainer();
                }
            }

            // Expand start/end container to matching selector
            if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
                // Find new startContainer/endContainer if there is better one
                startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
                endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
            }

            // Expand start/end container to matching block element or text node
            if (format[0].block || format[0].selector) {
                // Find new startContainer/endContainer if there is better one
                startContainer = findBlockEndPoint(startContainer, 'previousSibling');
                endContainer = findBlockEndPoint(endContainer, 'nextSibling');

                // Non block element then try to expand up the leaf
                if (format[0].block) {
                    if (!isBlock(startContainer)) {
                        startContainer = findParentContainer(true);
                    }

                    if (!isBlock(endContainer)) {
                        endContainer = findParentContainer();
                    }
                }
            }

            // Setup index for startContainer
            if (startContainer.nodeType == 1) {
                startOffset = nodeIndex(startContainer);
                startContainer = startContainer.parentNode;
            }

            // Setup index for endContainer
            if (endContainer.nodeType == 1) {
                endOffset = nodeIndex(endContainer) + 1;
                endContainer = endContainer.parentNode;
            }

            // Return new range like object
            return {
                startContainer: startContainer,
                startOffset: startOffset,
                endContainer: endContainer,
                endOffset: endOffset
            };
        }

        /**
         * Removes the specified format for the specified node. It will also remove the node if it doesn't have
         * any attributes if the format specifies it to do so.
         *
         * @private
         * @param {Object} format Format object with items to remove from node.
         * @param {Object} vars Name/value object with variables to apply to format.
         * @param {Node} node Node to remove the format styles on.
         * @param {Node} compare_node Optional compare node, if specified the styles will be compared to that node.
         * @return {Boolean} True/false if the node was removed or not.
         */
        function removeFormat(format, vars, node, compare_node) {
            var i, attrs, stylesModified;

            // Check if node matches format
            if (!matchName(node, format)) {
                return FALSE;
            }

            // Should we compare with format attribs and styles
            if (format.remove != 'all') {
                // Remove styles
                each(format.styles, function(value, name) {
                    value = normalizeStyleValue(replaceVars(value, vars), name);

                    // Indexed array
                    if (typeof(name) === 'number') {
                        name = value;
                        compare_node = 0;
                    }

                    if (format.remove_similar || (!compare_node || isEq(getStyle(compare_node, name), value))) {
                        dom.setStyle(node, name, '');
                    }

                    stylesModified = 1;
                });

                // Remove style attribute if it's empty
                if (stylesModified && dom.getAttrib(node, 'style') === '') {
                    node.removeAttribute('style');
                    node.removeAttribute('data-mce-style');
                }

                // Remove attributes
                each(format.attributes, function(value, name) {
                    var valueOut;

                    value = replaceVars(value, vars);

                    // Indexed array
                    if (typeof(name) === 'number') {
                        name = value;
                        compare_node = 0;
                    }

                    if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
                        // Keep internal classes
                        if (name == 'class') {
                            value = dom.getAttrib(node, name);
                            if (value) {
                                // Build new class value where everything is removed except the internal prefixed classes
                                valueOut = '';
                                each(value.split(/\s+/), function(cls) {
                                    if (/mce\w+/.test(cls)) {
                                        valueOut += (valueOut ? ' ' : '') + cls;
                                    }
                                });

                                // We got some internal classes left
                                if (valueOut) {
                                    dom.setAttrib(node, name, valueOut);
                                    return;
                                }
                            }
                        }

                        // IE6 has a bug where the attribute doesn't get removed correctly
                        if (name == "class") {
                            node.removeAttribute('className');
                        }

                        // Remove mce prefixed attributes
                        if (MCE_ATTR_RE.test(name)) {
                            node.removeAttribute('data-mce-' + name);
                        }

                        node.removeAttribute(name);
                    }
                });

                // Remove classes
                each(format.classes, function(value) {
                    value = replaceVars(value, vars);

                    if (!compare_node || dom.hasClass(compare_node, value)) {
                        dom.removeClass(node, value);
                    }
                });

                // Check for non internal attributes
                attrs = dom.getAttribs(node);
                for (i = 0; i < attrs.length; i++) {
                    if (attrs[i].nodeName.indexOf('_') !== 0) {
                        return FALSE;
                    }
                }
            }

            // Remove the inline child if it's empty for example <b> or <span>
            if (format.remove != 'none') {
                removeNode(node, format);
                return TRUE;
            }
        }

        /**
         * Removes the node and wrap it's children in paragraphs before doing so or
         * appends BR elements to the beginning/end of the block element if forcedRootBlocks is disabled.
         *
         * If the div in the node below gets removed:
         *  text<div>text</div>text
         *
         * Output becomes:
         *  text<div><br />text<br /></div>text
         *
         * So when the div is removed the result is:
         *  text<br />text<br />text
         *
         * @private
         * @param {Node} node Node to remove + apply BR/P elements to.
         * @param {Object} format Format rule.
         * @return {Node} Input node.
         */
        function removeNode(node, format) {
            var parentNode = node.parentNode, rootBlockElm;

            function find(node, next, inc) {
                node = getNonWhiteSpaceSibling(node, next, inc);

                return !node || (node.nodeName == 'BR' || isBlock(node));
            }

            if (format.block) {
                if (!forcedRootBlock) {
                    // Append BR elements if needed before we remove the block
                    if (isBlock(node) && !isBlock(parentNode)) {
                        if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) {
                            node.insertBefore(dom.create('br'), node.firstChild);
                        }

                        if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) {
                            node.appendChild(dom.create('br'));
                        }
                    }
                } else {
                    // Wrap the block in a forcedRootBlock if we are at the root of document
                    if (parentNode == dom.getRoot()) {
                        if (!format.list_block || !isEq(node, format.list_block)) {
                            each(grep(node.childNodes), function(node) {
                                if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
                                    if (!rootBlockElm) {
                                        rootBlockElm = wrap(node, forcedRootBlock);
                                        dom.setAttribs(rootBlockElm, ed.settings.forced_root_block_attrs);
                                    } else {
                                        rootBlockElm.appendChild(node);
                                    }
                                } else {
                                    rootBlockElm = 0;
                                }
                            });
                        }
                    }
                }
            }

            // Never remove nodes that isn't the specified inline element if a selector is specified too
            if (format.selector && format.inline && !isEq(format.inline, node)) {
                return;
            }

            dom.remove(node, 1);
        }

        /**
         * Returns the next/previous non whitespace node.
         *
         * @private
         * @param {Node} node Node to start at.
         * @param {boolean} next (Optional) Include next or previous node defaults to previous.
         * @param {boolean} inc (Optional) Include the current node in checking. Defaults to false.
         * @return {Node} Next or previous node or undefined if it wasn't found.
         */
        function getNonWhiteSpaceSibling(node, next, inc) {
            if (node) {
                next = next ? 'nextSibling' : 'previousSibling';

                for (node = inc ? node : node[next]; node; node = node[next]) {
                    if (node.nodeType == 1 || !isWhiteSpaceNode(node)) {
                        return node;
                    }
                }
            }
        }

        /**
         * Merges the next/previous sibling element if they match.
         *
         * @private
         * @param {Node} prev Previous node to compare/merge.
         * @param {Node} next Next node to compare/merge.
         * @return {Node} Next node if we didn't merge and prev node if we did.
         */
        function mergeSiblings(prev, next) {
            var sibling, tmpSibling, elementUtils = new ElementUtils(dom);

            function findElementSibling(node, sibling_name) {
                for (sibling = node; sibling; sibling = sibling[sibling_name]) {
                    if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) {
                        return node;
                    }

                    if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) {
                        return sibling;
                    }
                }

                return node;
            }

            // Check if next/prev exists and that they are elements
            if (prev && next) {
                // If previous sibling is empty then jump over it
                prev = findElementSibling(prev, 'previousSibling');
                next = findElementSibling(next, 'nextSibling');

                // Compare next and previous nodes
                if (elementUtils.compare(prev, next)) {
                    // Append nodes between
                    for (sibling = prev.nextSibling; sibling && sibling != next;) {
                        tmpSibling = sibling;
                        sibling = sibling.nextSibling;
                        prev.appendChild(tmpSibling);
                    }

                    // Remove next node
                    dom.remove(next);

                    // Move children into prev node
                    each(grep(next.childNodes), function(node) {
                        prev.appendChild(node);
                    });

                    return prev;
                }
            }

            return next;
        }

        function getContainer(rng, start) {
            var container, offset, lastIdx;

            container = rng[start ? 'startContainer' : 'endContainer'];
            offset = rng[start ? 'startOffset' : 'endOffset'];

            if (container.nodeType == 1) {
                lastIdx = container.childNodes.length - 1;

                if (!start && offset) {
                    offset--;
                }

                container = container.childNodes[offset > lastIdx ? lastIdx : offset];
            }

            // If start text node is excluded then walk to the next node
            if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
                container = new TreeWalker(container, ed.getBody()).next() || container;
            }

            // If end text node is excluded then walk to the previous node
            if (container.nodeType === 3 && !start && offset === 0) {
                container = new TreeWalker(container, ed.getBody()).prev() || container;
            }

            return container;
        }

        function performCaretAction(type, name, vars, similar) {
            var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;

            // Creates a caret container bogus element
            function createCaretContainer(fill) {
                var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});

                if (fill) {
                    caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
                }

                return caretContainer;
            }

            function isCaretContainerEmpty(node, nodes) {
                while (node) {
                    if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
                        return false;
                    }

                    // Collect nodes
                    if (nodes && node.nodeType === 1) {
                        nodes.push(node);
                    }

                    node = node.firstChild;
                }

                return true;
            }

            // Returns any parent caret container element
            function getParentCaretContainer(node) {
                while (node) {
                    if (node.id === caretContainerId) {
                        return node;
                    }

                    node = node.parentNode;
                }
            }

            // Finds the first text node in the specified node
            function findFirstTextNode(node) {
                var walker;

                if (node) {
                    walker = new TreeWalker(node, node);

                    for (node = walker.current(); node; node = walker.next()) {
                        if (node.nodeType === 3) {
                            return node;
                        }
                    }
                }
            }

            // Removes the caret container for the specified node or all on the current document
            function removeCaretContainer(node, move_caret) {
                var child, rng;

                if (!node) {
                    node = getParentCaretContainer(selection.getStart());

                    if (!node) {
                        while ((node = dom.get(caretContainerId))) {
                            removeCaretContainer(node, false);
                        }
                    }
                } else {
                    rng = selection.getRng(true);

                    if (isCaretContainerEmpty(node)) {
                        if (move_caret !== false) {
                            rng.setStartBefore(node);
                            rng.setEndBefore(node);
                        }

                        dom.remove(node);
                    } else {
                        child = findFirstTextNode(node);

                        if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
                            child.deleteData(0, 1);

                            // Fix for bug #6976
                            if (rng.startContainer == child) {
                                rng.startOffset--;
                            }

                            if (rng.endContainer == child) {
                                rng.endOffset--;
                            }
                        }

                        dom.remove(node, 1);
                    }

                    selection.setRng(rng);
                }
            }

            // Applies formatting to the caret postion
            function applyCaretFormat() {
                var rng, caretContainer, textNode, offset, bookmark, container, text;

                rng = selection.getRng(true);
                offset = rng.startOffset;
                container = rng.startContainer;
                text = container.nodeValue;

                caretContainer = getParentCaretContainer(selection.getStart());
                if (caretContainer) {
                    textNode = findFirstTextNode(caretContainer);
                }

                // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
                if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
                    // Get bookmark of caret position
                    bookmark = selection.getBookmark();

                    // Collapse bookmark range (WebKit)
                    rng.collapse(true);

                    // Expand the range to the closest word and split it at those points
                    rng = expandRng(rng, get(name));
                    rng = rangeUtils.split(rng);

                    // Apply the format to the range
                    apply(name, vars, rng);

                    // Move selection back to caret position
                    selection.moveToBookmark(bookmark);
                } else {
                    if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
                        caretContainer = createCaretContainer(true);
                        textNode = caretContainer.firstChild;

                        rng.insertNode(caretContainer);
                        offset = 1;

                        apply(name, vars, caretContainer);
                    } else {
                        apply(name, vars, caretContainer);
                    }

                    // Move selection to text node
                    selection.setCursorLocation(textNode, offset);
                }
            }

            function removeCaretFormat() {
                var rng = selection.getRng(true), container, offset, bookmark,
                    hasContentAfter, node, formatNode, parents = [], i, caretContainer;

                container = rng.startContainer;
                offset = rng.startOffset;
                node = container;

                if (container.nodeType == 3) {
                    if (offset != container.nodeValue.length) {
                        hasContentAfter = true;
                    }

                    node = node.parentNode;
                }

                while (node) {
                    if (matchNode(node, name, vars, similar)) {
                        formatNode = node;
                        break;
                    }

                    if (node.nextSibling) {
                        hasContentAfter = true;
                    }

                    parents.push(node);
                    node = node.parentNode;
                }

                // Node doesn't have the specified format
                if (!formatNode) {
                    return;
                }

                // Is there contents after the caret then remove the format on the element
                if (hasContentAfter) {
                    // Get bookmark of caret position
                    bookmark = selection.getBookmark();

                    // Collapse bookmark range (WebKit)
                    rng.collapse(true);

                    // Expand the range to the closest word and split it at those points
                    rng = expandRng(rng, get(name), true);
                    rng = rangeUtils.split(rng);

                    // Remove the format from the range
                    remove(name, vars, rng);

                    // Move selection back to caret position
                    selection.moveToBookmark(bookmark);
                } else {
                    caretContainer = createCaretContainer();

                    node = caretContainer;
                    for (i = parents.length - 1; i >= 0; i--) {
                        node.appendChild(dom.clone(parents[i], false));
                        node = node.firstChild;
                    }

                    // Insert invisible character into inner most format element
                    node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
                    node = node.firstChild;

                    var block = dom.getParent(formatNode, isTextBlock);

                    if (block && dom.isEmpty(block)) {
                        // Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p>
                        formatNode.parentNode.replaceChild(caretContainer, formatNode);
                    } else {
                        // Insert caret container after the formated node
                        dom.insertAfter(caretContainer, formatNode);
                    }

                    // Move selection to text node
                    selection.setCursorLocation(node, 1);

                    // If the formatNode is empty, we can remove it safely.
                    if (dom.isEmpty(formatNode)) {
                        dom.remove(formatNode);
                    }
                }
            }

            // Checks if the parent caret container node isn't empty if that is the case it
            // will remove the bogus state on all children that isn't empty
            function unmarkBogusCaretParents() {
                var caretContainer;

                caretContainer = getParentCaretContainer(selection.getStart());
                if (caretContainer && !dom.isEmpty(caretContainer)) {
                    walk(caretContainer, function(node) {
                        if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
                            dom.setAttrib(node, 'data-mce-bogus', null);
                        }
                    }, 'childNodes');
                }
            }

            // Only bind the caret events once
            if (!ed._hasCaretEvents) {
                // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
                markCaretContainersBogus = function() {
                    var nodes = [], i;

                    if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
                        // Mark children
                        i = nodes.length;
                        while (i--) {
                            dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
                        }
                    }
                };

                disableCaretContainer = function(e) {
                    var keyCode = e.keyCode;

                    removeCaretContainer();

                    // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
                    if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
                        removeCaretContainer(getParentCaretContainer(selection.getStart()));
                    }

                    unmarkBogusCaretParents();
                };

                // Remove bogus state if they got filled by contents using editor.selection.setContent
                ed.on('SetContent', function(e) {
                    if (e.selection) {
                        unmarkBogusCaretParents();
                    }
                });
                ed._hasCaretEvents = true;
            }

            // Do apply or remove caret format
            if (type == "apply") {
                applyCaretFormat();
            } else {
                removeCaretFormat();
            }
        }

        /**
         * Moves the start to the first suitable text node.
         */
        function moveStart(rng) {
            var container = rng.startContainer,
                    offset = rng.startOffset, isAtEndOfText,
                    walker, node, nodes, tmpNode;

            // Convert text node into index if possible
            if (container.nodeType == 3 && offset >= container.nodeValue.length) {
                // Get the parent container location and walk from there
                offset = nodeIndex(container);
                container = container.parentNode;
                isAtEndOfText = true;
            }

            // Move startContainer/startOffset in to a suitable node
            if (container.nodeType == 1) {
                nodes = container.childNodes;
                container = nodes[Math.min(offset, nodes.length - 1)];
                walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));

                // If offset is at end of the parent node walk to the next one
                if (offset > nodes.length - 1 || isAtEndOfText) {
                    walker.next();
                }

                for (node = walker.current(); node; node = walker.next()) {
                    if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
                        // IE has a "neat" feature where it moves the start node into the closest element
                        // we can avoid this by inserting an element before it and then remove it after we set the selection
                        tmpNode = dom.create('a', {'data-mce-bogus': 'all'}, INVISIBLE_CHAR);
                        node.parentNode.insertBefore(tmpNode, node);

                        // Set selection and remove tmpNode
                        rng.setStart(node, 0);
                        selection.setRng(rng);
                        dom.remove(tmpNode);

                        return;
                    }
                }
            }
        }
    };
});

// Included from: js/tinymce/classes/UndoManager.js

/**
 * UndoManager.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class handles the undo/redo history levels for the editor. Since the build in undo/redo has major drawbacks a custom one was needed.
 *
 * @class tinymce.UndoManager
 */
define("tinymce/UndoManager", [
    "tinymce/Env",
    "tinymce/util/Tools",
    "tinymce/html/SaxParser"
], function(Env, Tools, SaxParser) {
    var trim = Tools.trim, trimContentRegExp;

    trimContentRegExp = new RegExp([
        '<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\\/span>', // Trim bogus spans like caret containers
        '\\s?data-mce-selected="[^"]+"' // Trim temporaty data-mce prefixed attributes like data-mce-selected
    ].join('|'), 'gi');

    return function(editor) {
        var self = this, index = 0, data = [], beforeBookmark, isFirstTypedCharacter, locks = 0;

        /**
         * Returns a trimmed version of the editor contents to be used for the undo level. This
         * will remove any data-mce-bogus="all" marked elements since these are used for UI it will also
         * remove the data-mce-selected attributes used for selection of objects and caret containers.
         * It will keep all data-mce-bogus="1" elements since these can be used to place the caret etc and will
         * be removed by the serialization logic when you save.
         *
         * @private
         * @return {String} HTML contents of the editor excluding some internal bogus elements.
         */
        function getContent() {
            var content = trim(editor.getContent({format: 'raw', no_events: 1}));
            var bogusAllRegExp = /<(\w+) [^>]*data-mce-bogus="all"[^>]*>/g;
            var endTagIndex, index, matchLength, matches, shortEndedElements, schema = editor.schema;

            content = content.replace(trimContentRegExp, '');
            shortEndedElements = schema.getShortEndedElements();

            // Remove all bogus elements marked with "all"
            while ((matches = bogusAllRegExp.exec(content))) {
                index = bogusAllRegExp.lastIndex;
                matchLength = matches[0].length;

                if (shortEndedElements[matches[1]]) {
                    endTagIndex = index;
                } else {
                    endTagIndex = SaxParser.findEndTag(schema, content, index);
                }

                content = content.substring(0, index - matchLength) + content.substring(endTagIndex);
                bogusAllRegExp.lastIndex = index - matchLength;
            }

            return content;
        }

        function addNonTypingUndoLevel(e) {
            self.typing = false;
            self.add({}, e);
        }

        // Add initial undo level when the editor is initialized
        editor.on('init', function() {
            self.add();
        });

        // Get position before an execCommand is processed
        editor.on('BeforeExecCommand', function(e) {
            var cmd = e.command;

            if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
                self.beforeChange();
            }
        });

        // Add undo level after an execCommand call was made
        editor.on('ExecCommand', function(e) {
            var cmd = e.command;

            if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
                addNonTypingUndoLevel(e);
            }
        });

        editor.on('ObjectResizeStart', function() {
            self.beforeChange();
        });

        editor.on('SaveContent ObjectResized blur', addNonTypingUndoLevel);
        editor.on('DragEnd', addNonTypingUndoLevel);

        editor.on('KeyUp', function(e) {
            var keyCode = e.keyCode;

            if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
                addNonTypingUndoLevel();
                editor.nodeChanged();
            }

            if (keyCode == 46 || keyCode == 8 || (Env.mac && (keyCode == 91 || keyCode == 93))) {
                editor.nodeChanged();
            }

            // Fire a TypingUndo event on the first character entered
            if (isFirstTypedCharacter && self.typing) {
                // Make the it dirty if the content was changed after typing the first character
                if (!editor.isDirty()) {
                    editor.isNotDirty = !data[0] || getContent() == data[0].content;

                    // Fire initial change event
                    if (!editor.isNotDirty) {
                        editor.fire('change', {level: data[0], lastLevel: null});
                    }
                }

                editor.fire('TypingUndo');
                isFirstTypedCharacter = false;
                editor.nodeChanged();
            }
        });

        editor.on('KeyDown', function(e) {
            var keyCode = e.keyCode;

            // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
            if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
                if (self.typing) {
                    addNonTypingUndoLevel(e);
                }

                return;
            }

            // If key isn't shift,ctrl,alt,capslock,metakey
            if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) {
                self.beforeChange();
                self.typing = true;
                self.add({}, e);
                isFirstTypedCharacter = true;
            }
        });

        editor.on('MouseDown', function(e) {
            if (self.typing) {
                addNonTypingUndoLevel(e);
            }
        });

        // Add keyboard shortcuts for undo/redo keys
        editor.addShortcut('ctrl+z', '', 'Undo');
        editor.addShortcut('ctrl+y,ctrl+shift+z', '', 'Redo');

        editor.on('AddUndo Undo Redo ClearUndos', function(e) {
            if (!e.isDefaultPrevented()) {
                editor.nodeChanged();
            }
        });

        self = {
            // Explose for debugging reasons
            data: data,

            /**
             * State if the user is currently typing or not. This will add a typing operation into one undo
             * level instead of one new level for each keystroke.
             *
             * @field {Boolean} typing
             */
            typing: false,

            /**
             * Stores away a bookmark to be used when performing an undo action so that the selection is before
             * the change has been made.
             *
             * @method beforeChange
             */
            beforeChange: function() {
                if (!locks) {
                    beforeBookmark = editor.selection.getBookmark(2, true);
                }
            },

            /**
             * Adds a new undo level/snapshot to the undo list.
             *
             * @method add
             * @param {Object} level Optional undo level object to add.
             * @param {DOMEvent} Event Optional event responsible for the creation of the undo level.
             * @return {Object} Undo level that got added or null it a level wasn't needed.
             */
            add: function(level, event) {
                var i, settings = editor.settings, lastLevel;

                level = level || {};
                level.content = getContent();

                if (locks || editor.removed) {
                    return null;
                }

                lastLevel = data[index];
                if (editor.fire('BeforeAddUndo', {level: level, lastLevel: lastLevel, originalEvent: event}).isDefaultPrevented()) {
                    return null;
                }

                // Add undo level if needed
                if (lastLevel && lastLevel.content == level.content) {
                    return null;
                }

                // Set before bookmark on previous level
                if (data[index]) {
                    data[index].beforeBookmark = beforeBookmark;
                }

                // Time to compress
                if (settings.custom_undo_redo_levels) {
                    if (data.length > settings.custom_undo_redo_levels) {
                        for (i = 0; i < data.length - 1; i++) {
                            data[i] = data[i + 1];
                        }

                        data.length--;
                        index = data.length;
                    }
                }

                // Get a non intrusive normalized bookmark
                level.bookmark = editor.selection.getBookmark(2, true);

                // Crop array if needed
                if (index < data.length - 1) {
                    data.length = index + 1;
                }

                data.push(level);
                index = data.length - 1;

                var args = {level: level, lastLevel: lastLevel, originalEvent: event};

                editor.fire('AddUndo', args);

                if (index > 0) {
                    editor.isNotDirty = false;
                    editor.fire('change', args);
                }

                return level;
            },

            /**
             * Undoes the last action.
             *
             * @method undo
             * @return {Object} Undo level or null if no undo was performed.
             */
            undo: function() {
                var level;

                if (self.typing) {
                    self.add();
                    self.typing = false;
                }

                if (index > 0) {
                    level = data[--index];

                    // Undo to first index then set dirty state to false
                    if (index === 0) {
                        editor.isNotDirty = true;
                    }

                    editor.setContent(level.content, {format: 'raw'});
                    editor.selection.moveToBookmark(level.beforeBookmark);

                    editor.fire('undo', {level: level});
                }

                return level;
            },

            /**
             * Redoes the last action.
             *
             * @method redo
             * @return {Object} Redo level or null if no redo was performed.
             */
            redo: function() {
                var level;

                if (index < data.length - 1) {
                    level = data[++index];

                    editor.setContent(level.content, {format: 'raw'});
                    editor.selection.moveToBookmark(level.bookmark);

                    editor.fire('redo', {level: level});
                }

                return level;
            },

            /**
             * Removes all undo levels.
             *
             * @method clear
             */
            clear: function() {
                data = [];
                index = 0;
                self.typing = false;
                editor.fire('ClearUndos');
            },

            /**
             * Returns true/false if the undo manager has any undo levels.
             *
             * @method hasUndo
             * @return {Boolean} true/false if the undo manager has any undo levels.
             */
            hasUndo: function() {
                // Has undo levels or typing and content isn't the same as the initial level
                return index > 0 || (self.typing && data[0] && getContent() != data[0].content);
            },

            /**
             * Returns true/false if the undo manager has any redo levels.
             *
             * @method hasRedo
             * @return {Boolean} true/false if the undo manager has any redo levels.
             */
            hasRedo: function() {
                return index < data.length - 1 && !this.typing;
            },

            /**
             * Executes the specified function in an undo transation. The selection
             * before the modification will be stored to the undo stack and if the DOM changes
             * it will add a new undo level. Any methods within the transation that adds undo levels will
             * be ignored. So a transation can include calls to execCommand or editor.insertContent.
             *
             * @method transact
             * @param {function} callback Function to execute dom manipulation logic in.
             */
            transact: function(callback) {
                self.beforeChange();

                try {
                    locks++;
                    callback();
                } finally {
                    locks--;
                }

                self.add();
            }
        };

        return self;
    };
});

// Included from: js/tinymce/classes/EnterKey.js

/**
 * EnterKey.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Contains logic for handling the enter key to split/generate block elements.
 */
define("tinymce/EnterKey", [
    "tinymce/dom/TreeWalker",
    "tinymce/dom/RangeUtils",
    "tinymce/Env"
], function(TreeWalker, RangeUtils, Env) {
    var isIE = Env.ie && Env.ie < 11;

    return function(editor) {
        var dom = editor.dom, selection = editor.selection, settings = editor.settings;
        var undoManager = editor.undoManager, schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements();

        function handleEnterKey(evt) {
            var rng, tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
                newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;

            // Returns true if the block can be split into two blocks or not
            function canSplitBlock(node) {
                return node &&
                    dom.isBlock(node) &&
                    !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
                    !/^(fixed|absolute)/i.test(node.style.position) &&
                    dom.getContentEditable(node) !== "true";
            }

            // Renders empty block on IE
            function renderBlockOnIE(block) {
                var oldRng;

                if (dom.isBlock(block)) {
                    oldRng = selection.getRng();
                    block.appendChild(dom.create('span', null, '\u00a0'));
                    selection.select(block);
                    block.lastChild.outerHTML = '';
                    selection.setRng(oldRng);
                }
            }

            // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
            function trimInlineElementsOnLeftSideOfBlock(block) {
                var node = block, firstChilds = [], i;

                if (!node) {
                    return;
                }

                // Find inner most first child ex: <p><i><b>*</b></i></p>
                while ((node = node.firstChild)) {
                    if (dom.isBlock(node)) {
                        return;
                    }

                    if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
                        firstChilds.push(node);
                    }
                }

                i = firstChilds.length;
                while (i--) {
                    node = firstChilds[i];
                    if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
                        dom.remove(node);
                    } else {
                        // Remove <a> </a> see #5381
                        if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') {
                            dom.remove(node);
                        }
                    }
                }
            }

            // Moves the caret to a suitable position within the root for example in the first non
            // pure whitespace text node or before an image
            function moveToCaretPosition(root) {
                var walker, node, rng, lastNode = root, tempElm;

                function firstNonWhiteSpaceNodeSibling(node) {
                    while (node) {
                        if (node.nodeType == 1 || (node.nodeType == 3 && node.data && /[\r\n\s]/.test(node.data))) {
                            return node;
                        }

                        node = node.nextSibling;
                    }
                }

                if (!root) {
                    return;
                }

                // Old IE versions doesn't properly render blocks with br elements in them
                // For example <p><br></p> wont be rendered correctly in a contentEditable area
                // until you remove the br producing <p></p>
                if (Env.ie && Env.ie < 9 && parentBlock && parentBlock.firstChild) {
                    if (parentBlock.firstChild == parentBlock.lastChild && parentBlock.firstChild.tagName == 'BR') {
                        dom.remove(parentBlock.firstChild);
                    }
                }

                if (/^(LI|DT|DD)$/.test(root.nodeName)) {
                    var firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild);

                    if (firstChild && /^(UL|OL|DL)$/.test(firstChild.nodeName)) {
                        root.insertBefore(dom.doc.createTextNode('\u00a0'), root.firstChild);
                    }
                }

                rng = dom.createRng();

                // Normalize whitespace to remove empty text nodes. Fix for: #6904
                // Gecko will be able to place the caret in empty text nodes but it won't render propery
                // Older IE versions will sometimes crash so for now ignore all IE versions
                if (!Env.ie) {
                    root.normalize();
                }

                if (root.hasChildNodes()) {
                    walker = new TreeWalker(root, root);

                    while ((node = walker.current())) {
                        if (node.nodeType == 3) {
                            rng.setStart(node, 0);
                            rng.setEnd(node, 0);
                            break;
                        }

                        if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
                            rng.setStartBefore(node);
                            rng.setEndBefore(node);
                            break;
                        }

                        lastNode = node;
                        node = walker.next();
                    }

                    if (!node) {
                        rng.setStart(lastNode, 0);
                        rng.setEnd(lastNode, 0);
                    }
                } else {
                    if (root.nodeName == 'BR') {
                        if (root.nextSibling && dom.isBlock(root.nextSibling)) {
                            // Trick on older IE versions to render the caret before the BR between two lists
                            if (!documentMode || documentMode < 9) {
                                tempElm = dom.create('br');
                                root.parentNode.insertBefore(tempElm, root);
                            }

                            rng.setStartBefore(root);
                            rng.setEndBefore(root);
                        } else {
                            rng.setStartAfter(root);
                            rng.setEndAfter(root);
                        }
                    } else {
                        rng.setStart(root, 0);
                        rng.setEnd(root, 0);
                    }
                }

                selection.setRng(rng);

                // Remove tempElm created for old IE:s
                dom.remove(tempElm);
                selection.scrollIntoView(root);
            }

            function setForcedBlockAttrs(node) {
                var forcedRootBlockName = settings.forced_root_block;

                if (forcedRootBlockName && forcedRootBlockName.toLowerCase() === node.tagName.toLowerCase()) {
                    dom.setAttribs(node, settings.forced_root_block_attrs);
                }
            }

            // Creates a new block element by cloning the current one or creating a new one if the name is specified
            // This function will also copy any text formatting from the parent block and add it to the new one
            function createNewBlock(name) {
                var node = container, block, clonedNode, caretNode, textInlineElements = schema.getTextInlineElements();

                if (name || parentBlockName == "TABLE") {
                    block = dom.create(name || newBlockName);
                    setForcedBlockAttrs(block);
                } else {
                    block = parentBlock.cloneNode(false);
                }

                caretNode = block;

                // Clone any parent styles
                if (settings.keep_styles !== false) {
                    do {
                        if (textInlineElements[node.nodeName]) {
                            // Never clone a caret containers
                            if (node.id == '_mce_caret') {
                                continue;
                            }

                            clonedNode = node.cloneNode(false);
                            dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique

                            if (block.hasChildNodes()) {
                                clonedNode.appendChild(block.firstChild);
                                block.appendChild(clonedNode);
                            } else {
                                caretNode = clonedNode;
                                block.appendChild(clonedNode);
                            }
                        }
                    } while ((node = node.parentNode));
                }

                // BR is needed in empty blocks on non IE browsers
                if (!isIE) {
                    caretNode.innerHTML = '<br data-mce-bogus="1">';
                }

                return block;
            }

            // Returns true/false if the caret is at the start/end of the parent block element
            function isCaretAtStartOrEndOfBlock(start) {
                var walker, node, name;

                // Caret is in the middle of a text node like "a|b"
                if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {
                    return false;
                }

                // If after the last element in block node edge case for #5091
                if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
                    return true;
                }

                // If the caret if before the first element in parentBlock
                if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
                    return true;
                }

                // Caret can be before/after a table
                if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
                    return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
                }

                // Walk the DOM and look for text nodes or non empty elements
                walker = new TreeWalker(container, parentBlock);

                // If caret is in beginning or end of a text block then jump to the next/previous node
                if (container.nodeType == 3) {
                    if (start && offset === 0) {
                        walker.prev();
                    } else if (!start && offset == container.nodeValue.length) {
                        walker.next();
                    }
                }

                while ((node = walker.current())) {
                    if (node.nodeType === 1) {
                        // Ignore bogus elements
                        if (!node.getAttribute('data-mce-bogus')) {
                            // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
                            name = node.nodeName.toLowerCase();
                            if (nonEmptyElementsMap[name] && name !== 'br') {
                                return false;
                            }
                        }
                    } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
                        return false;
                    }

                    if (start) {
                        walker.prev();
                    } else {
                        walker.next();
                    }
                }

                return true;
            }

            // Wraps any text nodes or inline elements in the specified forced root block name
            function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
                var newBlock, parentBlock, startNode, node, next, rootBlockName, blockName = newBlockName || 'P';

                // Not in a block element or in a table cell or caption
                parentBlock = dom.getParent(container, dom.isBlock);
                rootBlockName = editor.getBody().nodeName.toLowerCase();
                if (!parentBlock || !canSplitBlock(parentBlock)) {
                    parentBlock = parentBlock || editableRoot;

                    if (!parentBlock.hasChildNodes()) {
                        newBlock = dom.create(blockName);
                        setForcedBlockAttrs(newBlock);
                        parentBlock.appendChild(newBlock);
                        rng.setStart(newBlock, 0);
                        rng.setEnd(newBlock, 0);
                        return newBlock;
                    }

                    // Find parent that is the first child of parentBlock
                    node = container;
                    while (node.parentNode != parentBlock) {
                        node = node.parentNode;
                    }

                    // Loop left to find start node start wrapping at
                    while (node && !dom.isBlock(node)) {
                        startNode = node;
                        node = node.previousSibling;
                    }

                    if (startNode && schema.isValidChild(rootBlockName, blockName.toLowerCase())) {
                        newBlock = dom.create(blockName);
                        setForcedBlockAttrs(newBlock);
                        startNode.parentNode.insertBefore(newBlock, startNode);

                        // Start wrapping until we hit a block
                        node = startNode;
                        while (node && !dom.isBlock(node)) {
                            next = node.nextSibling;
                            newBlock.appendChild(node);
                            node = next;
                        }

                        // Restore range to it's past location
                        rng.setStart(container, offset);
                        rng.setEnd(container, offset);
                    }
                }

                return container;
            }

            // Inserts a block or br before/after or in the middle of a split list of the LI is empty
            function handleEmptyListItem() {
                function isFirstOrLastLi(first) {
                    var node = containerBlock[first ? 'firstChild' : 'lastChild'];

                    // Find first/last element since there might be whitespace there
                    while (node) {
                        if (node.nodeType == 1) {
                            break;
                        }

                        node = node[first ? 'nextSibling' : 'previousSibling'];
                    }

                    return node === parentBlock;
                }

                function getContainerBlock() {
                    var containerBlockParent = containerBlock.parentNode;

                    if (/^(LI|DT|DD)$/.test(containerBlockParent.nodeName)) {
                        return containerBlockParent;
                    }

                    return containerBlock;
                }

                // Check if we are in an nested list
                var containerBlockParentName = containerBlock.parentNode.nodeName;
                if (/^(OL|UL|LI)$/.test(containerBlockParentName)) {
                    newBlockName = 'LI';
                }

                newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');

                if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
                    if (containerBlockParentName == 'LI') {
                        // Nested list is inside a LI
                        dom.insertAfter(newBlock, getContainerBlock());
                    } else {
                        // Is first and last list item then replace the OL/UL with a text block
                        dom.replace(newBlock, containerBlock);
                    }
                } else if (isFirstOrLastLi(true)) {
                    if (containerBlockParentName == 'LI') {
                        // List nested in an LI then move the list to a new sibling LI
                        dom.insertAfter(newBlock, getContainerBlock());
                        newBlock.appendChild(dom.doc.createTextNode(' ')); // Needed for IE so the caret can be placed
                        newBlock.appendChild(containerBlock);
                    } else {
                        // First LI in list then remove LI and add text block before list
                        containerBlock.parentNode.insertBefore(newBlock, containerBlock);
                    }
                } else if (isFirstOrLastLi()) {
                    // Last LI in list then remove LI and add text block after list
                    dom.insertAfter(newBlock, getContainerBlock());
                    renderBlockOnIE(newBlock);
                } else {
                    // Middle LI in list the split the list and insert a text block in the middle
                    // Extract after fragment and insert it after the current block
                    containerBlock = getContainerBlock();
                    tmpRng = rng.cloneRange();
                    tmpRng.setStartAfter(parentBlock);
                    tmpRng.setEndAfter(containerBlock);
                    fragment = tmpRng.extractContents();

                    if (newBlockName == 'LI' && fragment.firstChild.nodeName == 'LI') {
                        newBlock = fragment.firstChild;
                        dom.insertAfter(fragment, containerBlock);
                    } else {
                        dom.insertAfter(fragment, containerBlock);
                        dom.insertAfter(newBlock, containerBlock);
                    }
                }

                dom.remove(parentBlock);
                moveToCaretPosition(newBlock);
                undoManager.add();
            }

            // Inserts a BR element if the forced_root_block option is set to false or empty string
            function insertBr() {
                editor.execCommand("InsertLineBreak", false, evt);
            }

            // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
            function trimLeadingLineBreaks(node) {
                do {
                    if (node.nodeType === 3) {
                        node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
                    }

                    node = node.firstChild;
                } while (node);
            }

            function getEditableRoot(node) {
                var root = dom.getRoot(), parent, editableRoot;

                // Get all parents until we hit a non editable parent or the root
                parent = node;
                while (parent !== root && dom.getContentEditable(parent) !== "false") {
                    if (dom.getContentEditable(parent) === "true") {
                        editableRoot = parent;
                    }

                    parent = parent.parentNode;
                }

                return parent !== root ? editableRoot : root;
            }

            // Adds a BR at the end of blocks that only contains an IMG or INPUT since
            // these might be floated and then they won't expand the block
            function addBrToBlockIfNeeded(block) {
                var lastChild;

                // IE will render the blocks correctly other browsers needs a BR
                if (!isIE) {
                    block.normalize(); // Remove empty text nodes that got left behind by the extract

                    // Check if the block is empty or contains a floated last child
                    lastChild = block.lastChild;
                    if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
                        dom.add(block, 'br');
                    }
                }
            }

            rng = selection.getRng(true);

            // Event is blocked by some other handler for example the lists plugin
            if (evt.isDefaultPrevented()) {
                return;
            }

            // Delete any selected contents
            if (!rng.collapsed) {
                editor.execCommand('Delete');
                return;
            }

            // Setup range items and newBlockName
            new RangeUtils(dom).normalize(rng);
            container = rng.startContainer;
            offset = rng.startOffset;
            newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block;
            newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
            documentMode = dom.doc.documentMode;
            shiftKey = evt.shiftKey;

            // Resolve node index
            if (container.nodeType == 1 && container.hasChildNodes()) {
                isAfterLastNodeInContainer = offset > container.childNodes.length - 1;

                container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
                if (isAfterLastNodeInContainer && container.nodeType == 3) {
                    offset = container.nodeValue.length;
                } else {
                    offset = 0;
                }
            }

            // Get editable root node normaly the body element but sometimes a div or span
            editableRoot = getEditableRoot(container);

            // If there is no editable root then enter is done inside a contentEditable false element
            if (!editableRoot) {
                return;
            }

            undoManager.beforeChange();

            // If editable root isn't block nor the root of the editor
            if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
                if (!newBlockName || shiftKey) {
                    insertBr();
                }

                return;
            }

            // Wrap the current node and it's sibling in a default block if it's needed.
            // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
            // This won't happen if root blocks are disabled or the shiftKey is pressed
            if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) {
                container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
            }

            // Find parent block and setup empty block paddings
            parentBlock = dom.getParent(container, dom.isBlock);
            containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;

            // Setup block names
            parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
            containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5

            // Enter inside block contained within a LI then split or insert before/after LI
            if (containerBlockName == 'LI' && !evt.ctrlKey) {
                parentBlock = containerBlock;
                parentBlockName = containerBlockName;
            }

            // Handle enter in list item
            if (/^(LI|DT|DD)$/.test(parentBlockName)) {
                if (!newBlockName && shiftKey) {
                    insertBr();
                    return;
                }

                // Handle enter inside an empty list item
                if (dom.isEmpty(parentBlock)) {
                    handleEmptyListItem();
                    return;
                }
            }

            // Don't split PRE tags but insert a BR instead easier when writing code samples etc
            if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
                if (!shiftKey) {
                    insertBr();
                    return;
                }
            } else {
                // If no root block is configured then insert a BR by default or if the shiftKey is pressed
                if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) {
                    insertBr();
                    return;
                }
            }

            // If parent block is root then never insert new blocks
            if (newBlockName && parentBlock === editor.getBody()) {
                return;
            }

            // Default block name if it's not configured
            newBlockName = newBlockName || 'P';

            // Insert new block before/after the parent block depending on caret location
            if (isCaretAtStartOrEndOfBlock()) {
                // If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup
                if (/^(H[1-6]|PRE|FIGURE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
                    newBlock = createNewBlock(newBlockName);
                } else {
                    newBlock = createNewBlock();
                }

                // Split the current container block element if enter is pressed inside an empty inner block element
                if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
                    // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
                    newBlock = dom.split(containerBlock, parentBlock);
                } else {
                    dom.insertAfter(newBlock, parentBlock);
                }

                moveToCaretPosition(newBlock);
            } else if (isCaretAtStartOrEndOfBlock(true)) {
                // Insert new block before
                newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
                renderBlockOnIE(newBlock);
                moveToCaretPosition(parentBlock);
            } else {
                // Extract after fragment and insert it after the current block
                tmpRng = rng.cloneRange();
                tmpRng.setEndAfter(parentBlock);
                fragment = tmpRng.extractContents();
                trimLeadingLineBreaks(fragment);
                newBlock = fragment.firstChild;
                dom.insertAfter(fragment, parentBlock);
                trimInlineElementsOnLeftSideOfBlock(newBlock);
                addBrToBlockIfNeeded(parentBlock);
                moveToCaretPosition(newBlock);
            }

            dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique

            // Allow custom handling of new blocks
            editor.fire('NewBlock', { newBlock: newBlock });

            undoManager.add();
        }

        editor.on('keydown', function(evt) {
            if (evt.keyCode == 13) {
                if (handleEnterKey(evt) !== false) {
                    evt.preventDefault();
                }
            }
        });
    };
});

// Included from: js/tinymce/classes/ForceBlocks.js

/**
 * ForceBlocks.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

define("tinymce/ForceBlocks", [], function() {
    return function(editor) {
        var settings = editor.settings, dom = editor.dom, selection = editor.selection;
        var schema = editor.schema, blockElements = schema.getBlockElements();

        function addRootBlocks() {
            var node = selection.getStart(), rootNode = editor.getBody(), rng;
            var startContainer, startOffset, endContainer, endOffset, rootBlockNode;
            var tempNode, offset = -0xFFFFFF, wrapped, restoreSelection;
            var tmpRng, rootNodeName, forcedRootBlock;

            forcedRootBlock = settings.forced_root_block;

            if (!node || node.nodeType !== 1 || !forcedRootBlock) {
                return;
            }

            // Check if node is wrapped in block
            while (node && node != rootNode) {
                if (blockElements[node.nodeName]) {
                    return;
                }

                node = node.parentNode;
            }

            // Get current selection
            rng = selection.getRng();
            if (rng.setStart) {
                startContainer = rng.startContainer;
                startOffset = rng.startOffset;
                endContainer = rng.endContainer;
                endOffset = rng.endOffset;

                try {
                    restoreSelection = editor.getDoc().activeElement === rootNode;
                } catch (ex) {
                    // IE throws unspecified error here sometimes
                }
            } else {
                // Force control range into text range
                if (rng.item) {
                    node = rng.item(0);
                    rng = editor.getDoc().body.createTextRange();
                    rng.moveToElementText(node);
                }

                restoreSelection = rng.parentElement().ownerDocument === editor.getDoc();
                tmpRng = rng.duplicate();
                tmpRng.collapse(true);
                startOffset = tmpRng.move('character', offset) * -1;

                if (!tmpRng.collapsed) {
                    tmpRng = rng.duplicate();
                    tmpRng.collapse(false);
                    endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
                }
            }

            // Wrap non block elements and text nodes
            node = rootNode.firstChild;
            rootNodeName = rootNode.nodeName.toLowerCase();
            while (node) {
                // TODO: Break this up, too complex
                if (((node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName]))) &&
                    schema.isValidChild(rootNodeName, forcedRootBlock.toLowerCase())) {
                    // Remove empty text nodes
                    if (node.nodeType === 3 && node.nodeValue.length === 0) {
                        tempNode = node;
                        node = node.nextSibling;
                        dom.remove(tempNode);
                        continue;
                    }

                    if (!rootBlockNode) {
                        rootBlockNode = dom.create(forcedRootBlock, editor.settings.forced_root_block_attrs);
                        node.parentNode.insertBefore(rootBlockNode, node);
                        wrapped = true;
                    }

                    tempNode = node;
                    node = node.nextSibling;
                    rootBlockNode.appendChild(tempNode);
                } else {
                    rootBlockNode = null;
                    node = node.nextSibling;
                }
            }

            if (wrapped && restoreSelection) {
                if (rng.setStart) {
                    rng.setStart(startContainer, startOffset);
                    rng.setEnd(endContainer, endOffset);
                    selection.setRng(rng);
                } else {
                    // Only select if the previous selection was inside the document to prevent auto focus in quirks mode
                    try {
                        rng = editor.getDoc().body.createTextRange();
                        rng.moveToElementText(rootNode);
                        rng.collapse(true);
                        rng.moveStart('character', startOffset);

                        if (endOffset > 0) {
                            rng.moveEnd('character', endOffset);
                        }

                        rng.select();
                    } catch (ex) {
                        // Ignore
                    }
                }

                editor.nodeChanged();
            }
        }

        // Force root blocks
        if (settings.forced_root_block) {
            editor.on('NodeChange', addRootBlocks);
        }
    };
});

// Included from: js/tinymce/classes/EditorCommands.js

/**
 * EditorCommands.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class enables you to add custom editor commands and it contains
 * overrides for native browser commands to address various bugs and issues.
 *
 * @class tinymce.EditorCommands
 */
define("tinymce/EditorCommands", [
    "tinymce/html/Serializer",
    "tinymce/Env",
    "tinymce/util/Tools",
    "tinymce/dom/ElementUtils",
    "tinymce/dom/RangeUtils",
    "tinymce/dom/TreeWalker"
], function(Serializer, Env, Tools, ElementUtils, RangeUtils, TreeWalker) {
    // Added for compression purposes
    var each = Tools.each, extend = Tools.extend;
    var map = Tools.map, inArray = Tools.inArray, explode = Tools.explode;
    var isGecko = Env.gecko, isIE = Env.ie, isOldIE = Env.ie && Env.ie < 11;
    var TRUE = true, FALSE = false;

    return function(editor) {
        var dom = editor.dom,
            selection = editor.selection,
            commands = {state: {}, exec: {}, value: {}},
            settings = editor.settings,
            formatter = editor.formatter,
            bookmark;

        /**
         * Executes the specified command.
         *
         * @method execCommand
         * @param {String} command Command to execute.
         * @param {Boolean} ui Optional user interface state.
         * @param {Object} value Optional value for command.
         * @return {Boolean} true/false if the command was found or not.
         */
        function execCommand(command, ui, value) {
            var func;

            command = command.toLowerCase();
            if ((func = commands.exec[command])) {
                func(command, ui, value);
                return TRUE;
            }

            return FALSE;
        }

        /**
         * Queries the current state for a command for example if the current selection is "bold".
         *
         * @method queryCommandState
         * @param {String} command Command to check the state of.
         * @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found.
         */
        function queryCommandState(command) {
            var func;

            command = command.toLowerCase();
            if ((func = commands.state[command])) {
                return func(command);
            }

            return -1;
        }

        /**
         * Queries the command value for example the current fontsize.
         *
         * @method queryCommandValue
         * @param {String} command Command to check the value of.
         * @return {Object} Command value of false if it's not found.
         */
        function queryCommandValue(command) {
            var func;

            command = command.toLowerCase();
            if ((func = commands.value[command])) {
                return func(command);
            }

            return FALSE;
        }

        /**
         * Adds commands to the command collection.
         *
         * @method addCommands
         * @param {Object} command_list Name/value collection with commands to add, the names can also be comma separated.
         * @param {String} type Optional type to add, defaults to exec. Can be value or state as well.
         */
        function addCommands(command_list, type) {
            type = type || 'exec';

            each(command_list, function(callback, command) {
                each(command.toLowerCase().split(','), function(command) {
                    commands[type][command] = callback;
                });
            });
        }

        // Expose public methods
        extend(this, {
            execCommand: execCommand,
            queryCommandState: queryCommandState,
            queryCommandValue: queryCommandValue,
            addCommands: addCommands
        });

        // Private methods

        function execNativeCommand(command, ui, value) {
            if (ui === undefined) {
                ui = FALSE;
            }

            if (value === undefined) {
                value = null;
            }

            return editor.getDoc().execCommand(command, ui, value);
        }

        function isFormatMatch(name) {
            return formatter.match(name);
        }

        function toggleFormat(name, value) {
            formatter.toggle(name, value ? {value: value} : undefined);
            editor.nodeChanged();
        }

        function storeSelection(type) {
            bookmark = selection.getBookmark(type);
        }

        function restoreSelection() {
            selection.moveToBookmark(bookmark);
        }

        // Add execCommand overrides
        addCommands({
            // Ignore these, added for compatibility
            'mceResetDesignMode,mceBeginUndoLevel': function() {},

            // Add undo manager logic
            'mceEndUndoLevel,mceAddUndoLevel': function() {
                editor.undoManager.add();
            },

            'Cut,Copy,Paste': function(command) {
                var doc = editor.getDoc(), failed;

                // Try executing the native command
                try {
                    execNativeCommand(command);
                } catch (ex) {
                    // Command failed
                    failed = TRUE;
                }

                // Present alert message about clipboard access not being available
                if (failed || !doc.queryCommandSupported(command)) {
                    var msg = editor.translate(
                        "Your browser doesn't support direct access to the clipboard. " +
                        "Please use the Ctrl+X/C/V keyboard shortcuts instead."
                    );

                    if (Env.mac) {
                        msg = msg.replace(/Ctrl\+/g, '\u2318+');
                    }

                    editor.windowManager.alert(msg);
                }
            },

            // Override unlink command
            unlink: function() {
                if (selection.isCollapsed()) {
                    var elm = selection.getNode();
                    if (elm.tagName == 'A') {
                        editor.dom.remove(elm, true);
                    }

                    return;
                }

                formatter.remove("link");
            },

            // Override justify commands to use the text formatter engine
            'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) {
                var align = command.substring(7);

                if (align == 'full') {
                    align = 'justify';
                }

                // Remove all other alignments first
                each('left,center,right,justify'.split(','), function(name) {
                    if (align != name) {
                        formatter.remove('align' + name);
                    }
                });

                toggleFormat('align' + align);
                execCommand('mceRepaint');
            },

            // Override list commands to fix WebKit bug
            'InsertUnorderedList,InsertOrderedList': function(command) {
                var listElm, listParent;

                execNativeCommand(command);

                // WebKit produces lists within block elements so we need to split them
                // we will replace the native list creation logic to custom logic later on
                // TODO: Remove this when the list creation logic is removed
                listElm = dom.getParent(selection.getNode(), 'ol,ul');
                if (listElm) {
                    listParent = listElm.parentNode;

                    // If list is within a text block then split that block
                    if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
                        storeSelection();
                        dom.split(listParent, listElm);
                        restoreSelection();
                    }
                }
            },

            // Override commands to use the text formatter engine
            'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
                toggleFormat(command);
            },

            // Override commands to use the text formatter engine
            'ForeColor,HiliteColor,FontName': function(command, ui, value) {
                toggleFormat(command, value);
            },

            FontSize: function(command, ui, value) {
                var fontClasses, fontSizes;

                // Convert font size 1-7 to styles
                if (value >= 1 && value <= 7) {
                    fontSizes = explode(settings.font_size_style_values);
                    fontClasses = explode(settings.font_size_classes);

                    if (fontClasses) {
                        value = fontClasses[value - 1] || value;
                    } else {
                        value = fontSizes[value - 1] || value;
                    }
                }

                toggleFormat(command, value);
            },

            RemoveFormat: function(command) {
                formatter.remove(command);
            },

            mceBlockQuote: function() {
                toggleFormat('blockquote');
            },

            FormatBlock: function(command, ui, value) {
                return toggleFormat(value || 'p');
            },

            mceCleanup: function() {
                var bookmark = selection.getBookmark();

                editor.setContent(editor.getContent({cleanup: TRUE}), {cleanup: TRUE});

                selection.moveToBookmark(bookmark);
            },

            mceRemoveNode: function(command, ui, value) {
                var node = value || selection.getNode();

                // Make sure that the body node isn't removed
                if (node != editor.getBody()) {
                    storeSelection();
                    editor.dom.remove(node, TRUE);
                    restoreSelection();
                }
            },

            mceSelectNodeDepth: function(command, ui, value) {
                var counter = 0;

                dom.getParent(selection.getNode(), function(node) {
                    if (node.nodeType == 1 && counter++ == value) {
                        selection.select(node);
                        return FALSE;
                    }
                }, editor.getBody());
            },

            mceSelectNode: function(command, ui, value) {
                selection.select(value);
            },

            mceInsertContent: function(command, ui, value) {
                var parser, serializer, parentNode, rootNode, fragment, args;
                var marker, rng, node, node2, bookmarkHtml, merge;
                var textInlineElements = editor.schema.getTextInlineElements();

                function trimOrPaddLeftRight(html) {
                    var rng, container, offset;

                    rng = selection.getRng(true);
                    container = rng.startContainer;
                    offset = rng.startOffset;

                    function hasSiblingText(siblingName) {
                        return container[siblingName] && container[siblingName].nodeType == 3;
                    }

                    if (container.nodeType == 3) {
                        if (offset > 0) {
                            html = html.replace(/^&nbsp;/, ' ');
                        } else if (!hasSiblingText('previousSibling')) {
                            html = html.replace(/^ /, '&nbsp;');
                        }

                        if (offset < container.length) {
                            html = html.replace(/&nbsp;(<br>|)$/, ' ');
                        } else if (!hasSiblingText('nextSibling')) {
                            html = html.replace(/(&nbsp;| )(<br>|)$/, '&nbsp;');
                        }
                    }

                    return html;
                }

                function markInlineFormatElements(fragment) {
                    if (merge) {
                        for (node = fragment.firstChild; node; node = node.walk(true)) {
                            if (textInlineElements[node.name]) {
                                node.attr('data-mce-new', "true");
                            }
                        }
                    }
                }

                function reduceInlineTextElements() {
                    if (merge) {
                        var root = editor.getBody(), elementUtils = new ElementUtils(dom);

                        each(dom.select('*[data-mce-new]'), function(node) {
                            node.removeAttribute('data-mce-new');

                            for (var testNode = node.parentNode; testNode && testNode != root; testNode = testNode.parentNode) {
                                if (elementUtils.compare(testNode, node)) {
                                    dom.remove(node, true);
                                }
                            }
                        });
                    }
                }

                if (typeof(value) != 'string') {
                    merge = value.merge;
                    value = value.content;
                }

                // Check for whitespace before/after value
                if (/^ | $/.test(value)) {
                    value = trimOrPaddLeftRight(value);
                }

                // Setup parser and serializer
                parser = editor.parser;
                serializer = new Serializer({}, editor.schema);
                bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">&#xFEFF;&#200B;</span>';

                // Run beforeSetContent handlers on the HTML to be inserted
                args = {content: value, format: 'html', selection: true};
                editor.fire('BeforeSetContent', args);
                value = args.content;

                // Add caret at end of contents if it's missing
                if (value.indexOf('{$caret}') == -1) {
                    value += '{$caret}';
                }

                // Replace the caret marker with a span bookmark element
                value = value.replace(/\{\$caret\}/, bookmarkHtml);

                // If selection is at <body>|<p></p> then move it into <body><p>|</p>
                rng = selection.getRng();
                var caretElement = rng.startContainer || (rng.parentElement ? rng.parentElement() : null);
                var body = editor.getBody();
                if (caretElement === body && selection.isCollapsed()) {
                    if (dom.isBlock(body.firstChild) && dom.isEmpty(body.firstChild)) {
                        rng = dom.createRng();
                        rng.setStart(body.firstChild, 0);
                        rng.setEnd(body.firstChild, 0);
                        selection.setRng(rng);
                    }
                }

                // Insert node maker where we will insert the new HTML and get it's parent
                if (!selection.isCollapsed()) {
                    editor.getDoc().execCommand('Delete', false, null);
                }

                parentNode = selection.getNode();

                // Parse the fragment within the context of the parent node
                var parserArgs = {context: parentNode.nodeName.toLowerCase()};
                fragment = parser.parse(value, parserArgs);

                markInlineFormatElements(fragment);

                // Move the caret to a more suitable location
                node = fragment.lastChild;
                if (node.attr('id') == 'mce_marker') {
                    marker = node;

                    for (node = node.prev; node; node = node.walk(true)) {
                        if (node.type == 3 || !dom.isBlock(node.name)) {
                            node.parent.insert(marker, node, node.name === 'br');
                            break;
                        }
                    }
                }

                // If parser says valid we can insert the contents into that parent
                if (!parserArgs.invalid) {
                    value = serializer.serialize(fragment);

                    // Check if parent is empty or only has one BR element then set the innerHTML of that parent
                    node = parentNode.firstChild;
                    node2 = parentNode.lastChild;
                    if (!node || (node === node2 && node.nodeName === 'BR')) {
                        dom.setHTML(parentNode, value);
                    } else {
                        selection.setContent(value);
                    }
                } else {
                    // If the fragment was invalid within that context then we need
                    // to parse and process the parent it's inserted into

                    // Insert bookmark node and get the parent
                    selection.setContent(bookmarkHtml);
                    parentNode = selection.getNode();
                    rootNode = editor.getBody();

                    // Opera will return the document node when selection is in root
                    if (parentNode.nodeType == 9) {
                        parentNode = node = rootNode;
                    } else {
                        node = parentNode;
                    }

                    // Find the ancestor just before the root element
                    while (node !== rootNode) {
                        parentNode = node;
                        node = node.parentNode;
                    }

                    // Get the outer/inner HTML depending on if we are in the root and parser and serialize that
                    value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
                    value = serializer.serialize(
                        parser.parse(
                            // Need to replace by using a function since $ in the contents would otherwise be a problem
                            value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
                                return serializer.serialize(fragment);
                            })
                        )
                    );

                    // Set the inner/outer HTML depending on if we are in the root or not
                    if (parentNode == rootNode) {
                        dom.setHTML(rootNode, value);
                    } else {
                        dom.setOuterHTML(parentNode, value);
                    }
                }

                reduceInlineTextElements();

                marker = dom.get('mce_marker');
                selection.scrollIntoView(marker);

                // Move selection before marker and remove it
                rng = dom.createRng();

                // If previous sibling is a text node set the selection to the end of that node
                node = marker.previousSibling;
                if (node && node.nodeType == 3) {
                    rng.setStart(node, node.nodeValue.length);

                    // TODO: Why can't we normalize on IE
                    if (!isIE) {
                        node2 = marker.nextSibling;
                        if (node2 && node2.nodeType == 3) {
                            node.appendData(node2.data);
                            node2.parentNode.removeChild(node2);
                        }
                    }
                } else {
                    // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
                    rng.setStartBefore(marker);
                    rng.setEndBefore(marker);
                }

                // Remove the marker node and set the new range
                dom.remove(marker);
                selection.setRng(rng);

                // Dispatch after event and add any visual elements needed
                editor.fire('SetContent', args);
                editor.addVisual();
            },

            mceInsertRawHTML: function(command, ui, value) {
                selection.setContent('tiny_mce_marker');
                editor.setContent(
                    editor.getContent().replace(/tiny_mce_marker/g, function() {
                        return value;
                    })
                );
            },

            mceToggleFormat: function(command, ui, value) {
                toggleFormat(value);
            },

            mceSetContent: function(command, ui, value) {
                editor.setContent(value);
            },

            'Indent,Outdent': function(command) {
                var intentValue, indentUnit, value;

                // Setup indent level
                intentValue = settings.indentation;
                indentUnit = /[a-z%]+$/i.exec(intentValue);
                intentValue = parseInt(intentValue, 10);

                if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
                    // If forced_root_blocks is set to false we don't have a block to indent so lets create a div
                    if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
                        formatter.apply('div');
                    }

                    each(selection.getSelectedBlocks(), function(element) {
                        if (element.nodeName != "LI") {
                            var indentStyleName = editor.getParam('indent_use_margin', false) ? 'margin' : 'padding';

                            indentStyleName += dom.getStyle(element, 'direction', true) == 'rtl' ? 'Right' : 'Left';

                            if (command == 'outdent') {
                                value = Math.max(0, parseInt(element.style[indentStyleName] || 0, 10) - intentValue);
                                dom.setStyle(element, indentStyleName, value ? value + indentUnit : '');
                            } else {
                                value = (parseInt(element.style[indentStyleName] || 0, 10) + intentValue) + indentUnit;
                                dom.setStyle(element, indentStyleName, value);
                            }
                        }
                    });
                } else {
                    execNativeCommand(command);
                }
            },

            mceRepaint: function() {
                if (isGecko) {
                    try {
                        storeSelection(TRUE);

                        if (selection.getSel()) {
                            selection.getSel().selectAllChildren(editor.getBody());
                        }

                        selection.collapse(TRUE);
                        restoreSelection();
                    } catch (ex) {
                        // Ignore
                    }
                }
            },

            InsertHorizontalRule: function() {
                editor.execCommand('mceInsertContent', false, '<hr />');
            },

            mceToggleVisualAid: function() {
                editor.hasVisual = !editor.hasVisual;
                editor.addVisual();
            },

            mceReplaceContent: function(command, ui, value) {
                editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format: 'text'})));
            },

            mceInsertLink: function(command, ui, value) {
                var anchor;

                if (typeof(value) == 'string') {
                    value = {href: value};
                }

                anchor = dom.getParent(selection.getNode(), 'a');

                // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
                value.href = value.href.replace(' ', '%20');

                // Remove existing links if there could be child links or that the href isn't specified
                if (!anchor || !value.href) {
                    formatter.remove('link');
                }

                // Apply new link to selection
                if (value.href) {
                    formatter.apply('link', value, anchor);
                }
            },

            selectAll: function() {
                var root = dom.getRoot(), rng;

                if (selection.getRng().setStart) {
                    rng = dom.createRng();
                    rng.setStart(root, 0);
                    rng.setEnd(root, root.childNodes.length);
                    selection.setRng(rng);
                } else {
                    // IE will render it's own root level block elements and sometimes
                    // even put font elements in them when the user starts typing. So we need to
                    // move the selection to a more suitable element from this:
                    // <body>|<p></p></body> to this: <body><p>|</p></body>
                    rng = selection.getRng();
                    if (!rng.item) {
                        rng.moveToElementText(root);
                        rng.select();
                    }
                }
            },

            "delete": function() {
                execNativeCommand("Delete");

                // Check if body is empty after the delete call if so then set the contents
                // to an empty string and move the caret to any block produced by that operation
                // this fixes the issue with root blocks not being properly produced after a delete call on IE
                var body = editor.getBody();

                if (dom.isEmpty(body)) {
                    editor.setContent('');

                    if (body.firstChild && dom.isBlock(body.firstChild)) {
                        editor.selection.setCursorLocation(body.firstChild, 0);
                    } else {
                        editor.selection.setCursorLocation(body, 0);
                    }
                }
            },

            mceNewDocument: function() {
                editor.setContent('');
            },

            InsertLineBreak: function(command, ui, value) {
                // We load the current event in from EnterKey.js when appropriate to heed
                // certain event-specific variations such as ctrl-enter in a list
                var evt = value;
                var brElm, extraBr, marker;
                var rng = selection.getRng(true);
                new RangeUtils(dom).normalize(rng);

                var offset = rng.startOffset;
                var container = rng.startContainer;

                // Resolve node index
                if (container.nodeType == 1 && container.hasChildNodes()) {
                    var isAfterLastNodeInContainer = offset > container.childNodes.length - 1;

                    container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
                    if (isAfterLastNodeInContainer && container.nodeType == 3) {
                        offset = container.nodeValue.length;
                    } else {
                        offset = 0;
                    }
                }

                var parentBlock = dom.getParent(container, dom.isBlock);
                var parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
                var containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
                var containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5

                // Enter inside block contained within a LI then split or insert before/after LI
                var isControlKey = evt && evt.ctrlKey;
                if (containerBlockName == 'LI' && !isControlKey) {
                    parentBlock = containerBlock;
                    parentBlockName = containerBlockName;
                }

                // Walks the parent block to the right and look for BR elements
                function hasRightSideContent() {
                    var walker = new TreeWalker(container, parentBlock), node;
                    var nonEmptyElementsMap = editor.schema.getNonEmptyElements();

                    while ((node = walker.next())) {
                        if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) {
                            return true;
                        }
                    }
                }

                if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
                    // Insert extra BR element at the end block elements
                    if (!isOldIE && !hasRightSideContent()) {
                        brElm = dom.create('br');
                        rng.insertNode(brElm);
                        rng.setStartAfter(brElm);
                        rng.setEndAfter(brElm);
                        extraBr = true;
                    }
                }

                brElm = dom.create('br');
                rng.insertNode(brElm);

                // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
                var documentMode = dom.doc.documentMode;
                if (isOldIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
                    brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
                }

                // Insert temp marker and scroll to that
                marker = dom.create('span', {}, '&nbsp;');
                brElm.parentNode.insertBefore(marker, brElm);
                selection.scrollIntoView(marker);
                dom.remove(marker);

                if (!extraBr) {
                    rng.setStartAfter(brElm);
                    rng.setEndAfter(brElm);
                } else {
                    rng.setStartBefore(brElm);
                    rng.setEndBefore(brElm);
                }

                selection.setRng(rng);
                editor.undoManager.add();

                return TRUE;
            }
        });

        // Add queryCommandState overrides
        addCommands({
            // Override justify commands
            'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) {
                var name = 'align' + command.substring(7);
                var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
                var matches = map(nodes, function(node) {
                    return !!formatter.matchNode(node, name);
                });
                return inArray(matches, TRUE) !== -1;
            },

            'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
                return isFormatMatch(command);
            },

            mceBlockQuote: function() {
                return isFormatMatch('blockquote');
            },

            Outdent: function() {
                var node;

                if (settings.inline_styles) {
                    if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
                        return TRUE;
                    }

                    if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
                        return TRUE;
                    }
                }

                return (
                    queryCommandState('InsertUnorderedList') ||
                    queryCommandState('InsertOrderedList') ||
                    (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'))
                );
            },

            'InsertUnorderedList,InsertOrderedList': function(command) {
                var list = dom.getParent(selection.getNode(), 'ul,ol');

                return list &&
                    (
                        command === 'insertunorderedlist' && list.tagName === 'UL' ||
                        command === 'insertorderedlist' && list.tagName === 'OL'
                    );
            }
        }, 'state');

        // Add queryCommandValue overrides
        addCommands({
            'FontSize,FontName': function(command) {
                var value = 0, parent;

                if ((parent = dom.getParent(selection.getNode(), 'span'))) {
                    if (command == 'fontsize') {
                        value = parent.style.fontSize;
                    } else {
                        value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
                    }
                }

                return value;
            }
        }, 'value');

        // Add undo manager logic
        addCommands({
            Undo: function() {
                editor.undoManager.undo();
            },

            Redo: function() {
                editor.undoManager.redo();
            }
        });
    };
});

// Included from: js/tinymce/classes/util/URI.js

/**
 * URI.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class handles parsing, modification and serialization of URI/URL strings.
 * @class tinymce.util.URI
 */
define("tinymce/util/URI", [
    "tinymce/util/Tools"
], function(Tools) {
    var each = Tools.each, trim = Tools.trim;
    var queryParts = "source protocol authority userInfo user password host port relative path directory file query anchor".split(' ');
    var DEFAULT_PORTS = {
        'ftp': 21,
        'http': 80,
        'https': 443,
        'mailto': 25
    };

    /**
     * Constructs a new URI instance.
     *
     * @constructor
     * @method URI
     * @param {String} url URI string to parse.
     * @param {Object} settings Optional settings object.
     */
    function URI(url, settings) {
        var self = this, baseUri, base_url;

        url = trim(url);
        settings = self.settings = settings || {};
        baseUri = settings.base_uri;

        // Strange app protocol that isn't http/https or local anchor
        // For example: mailto,skype,tel etc.
        if (/^([\w\-]+):([^\/]{2})/i.test(url) || /^\s*#/.test(url)) {
            self.source = url;
            return;
        }

        var isProtocolRelative = url.indexOf('//') === 0;

        // Absolute path with no host, fake host and protocol
        if (url.indexOf('/') === 0 && !isProtocolRelative) {
            url = (baseUri ? baseUri.protocol || 'http' : 'http') + '://mce_host' + url;
        }

        // Relative path http:// or protocol relative //path
        if (!/^[\w\-]*:?\/\//.test(url)) {
            base_url = settings.base_uri ? settings.base_uri.path : new URI(location.href).directory;
            if (settings.base_uri.protocol === "") {
                url = '//mce_host' + self.toAbsPath(base_url, url);
            } else {
                url = /([^#?]*)([#?]?.*)/.exec(url);
                url = ((baseUri && baseUri.protocol) || 'http') + '://mce_host' + self.toAbsPath(base_url, url[1]) + url[2];
            }
        }

        // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
        url = url.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something

        /*jshint maxlen: 255 */
        /*eslint max-len: 0 */
        url = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(url);

        each(queryParts, function(v, i) {
            var part = url[i];

            // Zope 3 workaround, they use @@something
            if (part) {
                part = part.replace(/\(mce_at\)/g, '@@');
            }

            self[v] = part;
        });

        if (baseUri) {
            if (!self.protocol) {
                self.protocol = baseUri.protocol;
            }

            if (!self.userInfo) {
                self.userInfo = baseUri.userInfo;
            }

            if (!self.port && self.host === 'mce_host') {
                self.port = baseUri.port;
            }

            if (!self.host || self.host === 'mce_host') {
                self.host = baseUri.host;
            }

            self.source = '';
        }

        if (isProtocolRelative) {
            self.protocol = '';
        }

        //t.path = t.path || '/';
    }

    URI.prototype = {
        /**
         * Sets the internal path part of the URI.
         *
         * @method setPath
         * @param {string} path Path string to set.
         */
        setPath: function(path) {
            var self = this;

            path = /^(.*?)\/?(\w+)?$/.exec(path);

            // Update path parts
            self.path = path[0];
            self.directory = path[1];
            self.file = path[2];

            // Rebuild source
            self.source = '';
            self.getURI();
        },

        /**
         * Converts the specified URI into a relative URI based on the current URI instance location.
         *
         * @method toRelative
         * @param {String} uri URI to convert into a relative path/URI.
         * @return {String} Relative URI from the point specified in the current URI instance.
         * @example
         * // Converts an absolute URL to an relative URL url will be somedir/somefile.htm
         * var url = new tinymce.util.URI('http://www.site.com/dir/').toRelative('http://www.site.com/dir/somedir/somefile.htm');
         */
        toRelative: function(uri) {
            var self = this, output;

            if (uri === "./") {
                return uri;
            }

            uri = new URI(uri, {base_uri: self});

            // Not on same domain/port or protocol
            if ((uri.host != 'mce_host' && self.host != uri.host && uri.host) || self.port != uri.port ||
                (self.protocol != uri.protocol && uri.protocol !== "")) {
                return uri.getURI();
            }

            var tu = self.getURI(), uu = uri.getURI();

            // Allow usage of the base_uri when relative_urls = true
            if (tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) {
                return tu;
            }

            output = self.toRelPath(self.path, uri.path);

            // Add query
            if (uri.query) {
                output += '?' + uri.query;
            }

            // Add anchor
            if (uri.anchor) {
                output += '#' + uri.anchor;
            }

            return output;
        },

        /**
         * Converts the specified URI into a absolute URI based on the current URI instance location.
         *
         * @method toAbsolute
         * @param {String} uri URI to convert into a relative path/URI.
         * @param {Boolean} noHost No host and protocol prefix.
         * @return {String} Absolute URI from the point specified in the current URI instance.
         * @example
         * // Converts an relative URL to an absolute URL url will be http://www.site.com/dir/somedir/somefile.htm
         * var url = new tinymce.util.URI('http://www.site.com/dir/').toAbsolute('somedir/somefile.htm');
         */
        toAbsolute: function(uri, noHost) {
            uri = new URI(uri, {base_uri: this});

            return uri.getURI(noHost && this.isSameOrigin(uri));
        },

        /**
         * Determine whether the given URI has the same origin as this URI.  Based on RFC-6454.
         * Supports default ports for protocols listed in DEFAULT_PORTS.  Unsupported protocols will fail safe: they
         * won't match, if the port specifications differ.
         *
         * @method isSameOrigin
         * @param {tinymce.util.URI} uri Uri instance to compare.
         * @returns {Boolean} True if the origins are the same.
         */
        isSameOrigin: function(uri) {
            if (this.host == uri.host && this.protocol == uri.protocol) {
                if (this.port == uri.port) {
                    return true;
                }

                var defaultPort = DEFAULT_PORTS[this.protocol];
                if (defaultPort && ((this.port || defaultPort) == (uri.port || defaultPort))) {
                    return true;
                }
            }

            return false;
        },

        /**
         * Converts a absolute path into a relative path.
         *
         * @method toRelPath
         * @param {String} base Base point to convert the path from.
         * @param {String} path Absolute path to convert into a relative path.
         */
        toRelPath: function(base, path) {
            var items, breakPoint = 0, out = '', i, l;

            // Split the paths
            base = base.substring(0, base.lastIndexOf('/'));
            base = base.split('/');
            items = path.split('/');

            if (base.length >= items.length) {
                for (i = 0, l = base.length; i < l; i++) {
                    if (i >= items.length || base[i] != items[i]) {
                        breakPoint = i + 1;
                        break;
                    }
                }
            }

            if (base.length < items.length) {
                for (i = 0, l = items.length; i < l; i++) {
                    if (i >= base.length || base[i] != items[i]) {
                        breakPoint = i + 1;
                        break;
                    }
                }
            }

            if (breakPoint === 1) {
                return path;
            }

            for (i = 0, l = base.length - (breakPoint - 1); i < l; i++) {
                out += "../";
            }

            for (i = breakPoint - 1, l = items.length; i < l; i++) {
                if (i != breakPoint - 1) {
                    out += "/" + items[i];
                } else {
                    out += items[i];
                }
            }

            return out;
        },

        /**
         * Converts a relative path into a absolute path.
         *
         * @method toAbsPath
         * @param {String} base Base point to convert the path from.
         * @param {String} path Relative path to convert into an absolute path.
         */
        toAbsPath: function(base, path) {
            var i, nb = 0, o = [], tr, outPath;

            // Split paths
            tr = /\/$/.test(path) ? '/' : '';
            base = base.split('/');
            path = path.split('/');

            // Remove empty chunks
            each(base, function(k) {
                if (k) {
                    o.push(k);
                }
            });

            base = o;

            // Merge relURLParts chunks
            for (i = path.length - 1, o = []; i >= 0; i--) {
                // Ignore empty or .
                if (path[i].length === 0 || path[i] === ".") {
                    continue;
                }

                // Is parent
                if (path[i] === '..') {
                    nb++;
                    continue;
                }

                // Move up
                if (nb > 0) {
                    nb--;
                    continue;
                }

                o.push(path[i]);
            }

            i = base.length - nb;

            // If /a/b/c or /
            if (i <= 0) {
                outPath = o.reverse().join('/');
            } else {
                outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
            }

            // Add front / if it's needed
            if (outPath.indexOf('/') !== 0) {
                outPath = '/' + outPath;
            }

            // Add traling / if it's needed
            if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) {
                outPath += tr;
            }

            return outPath;
        },

        /**
         * Returns the full URI of the internal structure.
         *
         * @method getURI
         * @param {Boolean} noProtoHost Optional no host and protocol part. Defaults to false.
         */
        getURI: function(noProtoHost) {
            var s, self = this;

            // Rebuild source
            if (!self.source || noProtoHost) {
                s = '';

                if (!noProtoHost) {
                    if (self.protocol) {
                        s += self.protocol + '://';
                    } else {
                        s += '//';
                    }

                    if (self.userInfo) {
                        s += self.userInfo + '@';
                    }

                    if (self.host) {
                        s += self.host;
                    }

                    if (self.port) {
                        s += ':' + self.port;
                    }
                }

                if (self.path) {
                    s += self.path;
                }

                if (self.query) {
                    s += '?' + self.query;
                }

                if (self.anchor) {
                    s += '#' + self.anchor;
                }

                self.source = s;
            }

            return self.source;
        }
    };

    return URI;
});

// Included from: js/tinymce/classes/util/Class.js

/**
 * Class.js
 *
 * Copyright 2003-2012, Moxiecode Systems AB, All rights reserved.
 */

/**
 * This utilitiy class is used for easier inheritage.
 *
 * Features:
 * * Exposed super functions: this._super();
 * * Mixins
 * * Dummy functions
 * * Property functions: var value = object.value(); and object.value(newValue);
 * * Static functions
 * * Defaults settings
 */
define("tinymce/util/Class", [
    "tinymce/util/Tools"
], function(Tools) {
    var each = Tools.each, extend = Tools.extend;

    var extendClass, initializing;

    function Class() {
    }

    // Provides classical inheritance, based on code made by John Resig
    Class.extend = extendClass = function(prop) {
        var self = this, _super = self.prototype, prototype, name, member;

        // The dummy class constructor
        function Class() {
            var i, mixins, mixin, self = this;

            // All construction is actually done in the init method
            if (!initializing) {
                // Run class constuctor
                if (self.init) {
                    self.init.apply(self, arguments);
                }

                // Run mixin constructors
                mixins = self.Mixins;
                if (mixins) {
                    i = mixins.length;
                    while (i--) {
                        mixin = mixins[i];
                        if (mixin.init) {
                            mixin.init.apply(self, arguments);
                        }
                    }
                }
            }
        }

        // Dummy function, needs to be extended in order to provide functionality
        function dummy() {
            return this;
        }

        // Creates a overloaded method for the class
        // this enables you to use this._super(); to call the super function
        function createMethod(name, fn) {
            return function() {
                var self = this, tmp = self._super, ret;

                self._super = _super[name];
                ret = fn.apply(self, arguments);
                self._super = tmp;

                return ret;
            };
        }

        // Instantiate a base class (but only create the instance,
        // don't run the init constructor)
        initializing = true;

        /*eslint new-cap:0 */
        prototype = new self();
        initializing = false;

        // Add mixins
        if (prop.Mixins) {
            each(prop.Mixins, function(mixin) {
                mixin = mixin;

                for (var name in mixin) {
                    if (name !== "init") {
                        prop[name] = mixin[name];
                    }
                }
            });

            if (_super.Mixins) {
                prop.Mixins = _super.Mixins.concat(prop.Mixins);
            }
        }

        // Generate dummy methods
        if (prop.Methods) {
            each(prop.Methods.split(','), function(name) {
                prop[name] = dummy;
            });
        }

        // Generate property methods
        if (prop.Properties) {
            each(prop.Properties.split(','), function(name) {
                var fieldName = '_' + name;

                prop[name] = function(value) {
                    var self = this, undef;

                    // Set value
                    if (value !== undef) {
                        self[fieldName] = value;

                        return self;
                    }

                    // Get value
                    return self[fieldName];
                };
            });
        }

        // Static functions
        if (prop.Statics) {
            each(prop.Statics, function(func, name) {
                Class[name] = func;
            });
        }

        // Default settings
        if (prop.Defaults && _super.Defaults) {
            prop.Defaults = extend({}, _super.Defaults, prop.Defaults);
        }

        // Copy the properties over onto the new prototype
        for (name in prop) {
            member = prop[name];

            if (typeof member == "function" && _super[name]) {
                prototype[name] = createMethod(name, member);
            } else {
                prototype[name] = member;
            }
        }

        // Populate our constructed prototype object
        Class.prototype = prototype;

        // Enforce the constructor to be what we expect
        Class.constructor = Class;

        // And make this class extendible
        Class.extend = extendClass;

        return Class;
    };

    return Class;
});

// Included from: js/tinymce/classes/util/EventDispatcher.js

/**
 * EventDispatcher.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class lets you add/remove and fire events by name on the specified scope. This makes
 * it easy to add event listener logic to any class.
 *
 * @class tinymce.util.EventDispatcher
 * @example
 *  var eventDispatcher = new EventDispatcher();
 *
 *  eventDispatcher.on('click', function() {console.log('data');});
 *  eventDispatcher.fire('click', {data: 123});
 */
define("tinymce/util/EventDispatcher", [
    "tinymce/util/Tools"
], function(Tools) {
    var nativeEvents = Tools.makeMap(
        "focus blur focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange " +
        "mouseout mouseenter mouseleave wheel keydown keypress keyup input contextmenu dragstart dragend dragover " +
        "draggesture dragdrop drop drag submit " +
        "compositionstart compositionend compositionupdate",
        ' '
    );

    function Dispatcher(settings) {
        var self = this, scope, bindings = {}, toggleEvent;

        function returnFalse() {
            return false;
        }

        function returnTrue() {
            return true;
        }

        settings = settings || {};
        scope = settings.scope || self;
        toggleEvent = settings.toggleEvent || returnFalse;

        /**
         * Fires the specified event by name.
         *
         * @method fire
         * @param {String} name Name of the event to fire.
         * @param {Object?} args Event arguments.
         * @return {Object} Event args instance passed in.
         * @example
         * instance.fire('event', {...});
         */
        function fire(name, args) {
            var handlers, i, l, callback;

            name = name.toLowerCase();
            args = args || {};
            args.type = name;

            // Setup target is there isn't one
            if (!args.target) {
                args.target = scope;
            }

            // Add event delegation methods if they are missing
            if (!args.preventDefault) {
                // Add preventDefault method
                args.preventDefault = function() {
                    args.isDefaultPrevented = returnTrue;
                };

                // Add stopPropagation
                args.stopPropagation = function() {
                    args.isPropagationStopped = returnTrue;
                };

                // Add stopImmediatePropagation
                args.stopImmediatePropagation = function() {
                    args.isImmediatePropagationStopped = returnTrue;
                };

                // Add event delegation states
                args.isDefaultPrevented = returnFalse;
                args.isPropagationStopped = returnFalse;
                args.isImmediatePropagationStopped = returnFalse;
            }

            if (settings.beforeFire) {
                settings.beforeFire(args);
            }

            handlers = bindings[name];
            if (handlers) {
                for (i = 0, l = handlers.length; i < l; i++) {
                    handlers[i] = callback = handlers[i];

                    // Unbind handlers marked with "once"
                    if (callback.once) {
                        off(name, callback);
                    }

                    // Stop immediate propagation if needed
                    if (args.isImmediatePropagationStopped()) {
                        args.stopPropagation();
                        return args;
                    }

                    // If callback returns false then prevent default and stop all propagation
                    if (callback.call(scope, args) === false) {
                        args.preventDefault();
                        return args;
                    }
                }
            }

            return args;
        }

        /**
         * Binds an event listener to a specific event by name.
         *
         * @method on
         * @param {String} name Event name or space separated list of events to bind.
         * @param {callback} callback Callback to be executed when the event occurs.
         * @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
         * @return {Object} Current class instance.
         * @example
         * instance.on('event', function(e) {
         *     // Callback logic
         * });
         */
        function on(name, callback, prepend) {
            var handlers, names, i;

            if (callback === false) {
                callback = returnFalse;
            }

            if (callback) {
                names = name.toLowerCase().split(' ');
                i = names.length;
                while (i--) {
                    name = names[i];
                    handlers = bindings[name];
                    if (!handlers) {
                        handlers = bindings[name] = [];
                        toggleEvent(name, true);
                    }

                    if (prepend) {
                        handlers.unshift(callback);
                    } else {
                        handlers.push(callback);
                    }
                }
            }

            return self;
        }

        /**
         * Unbinds an event listener to a specific event by name.
         *
         * @method off
         * @param {String?} name Name of the event to unbind.
         * @param {callback?} callback Callback to unbind.
         * @return {Object} Current class instance.
         * @example
         * // Unbind specific callback
         * instance.off('event', handler);
         *
         * // Unbind all listeners by name
         * instance.off('event');
         *
         * // Unbind all events
         * instance.off();
         */
        function off(name, callback) {
            var i, handlers, bindingName, names, hi;

            if (name) {
                names = name.toLowerCase().split(' ');
                i = names.length;
                while (i--) {
                    name = names[i];
                    handlers = bindings[name];

                    // Unbind all handlers
                    if (!name) {
                        for (bindingName in bindings) {
                            toggleEvent(bindingName, false);
                            delete bindings[bindingName];
                        }

                        return self;
                    }

                    if (handlers) {
                        // Unbind all by name
                        if (!callback) {
                            handlers.length = 0;
                        } else {
                            // Unbind specific ones
                            hi = handlers.length;
                            while (hi--) {
                                if (handlers[hi] === callback) {
                                    handlers = handlers.slice(0, hi).concat(handlers.slice(hi + 1));
                                    bindings[name] = handlers;
                                }
                            }
                        }

                        if (!handlers.length) {
                            toggleEvent(name, false);
                            delete bindings[name];
                        }
                    }
                }
            } else {
                for (name in bindings) {
                    toggleEvent(name, false);
                }

                bindings = {};
            }

            return self;
        }

        /**
         * Binds an event listener to a specific event by name
         * and automatically unbind the event once the callback fires.
         *
         * @method once
         * @param {String} name Event name or space separated list of events to bind.
         * @param {callback} callback Callback to be executed when the event occurs.
         * @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
         * @return {Object} Current class instance.
         * @example
         * instance.once('event', function(e) {
         *     // Callback logic
         * });
         */
        function once(name, callback, prepend) {
            callback.once = true;
            return on(name, callback, prepend);
        }

        /**
         * Returns true/false if the dispatcher has a event of the specified name.
         *
         * @method has
         * @param {String} name Name of the event to check for.
         * @return {Boolean} true/false if the event exists or not.
         */
        function has(name) {
            name = name.toLowerCase();
            return !(!bindings[name] || bindings[name].length === 0);
        }

        // Expose
        self.fire = fire;
        self.on = on;
        self.off = off;
        self.once = once;
        self.has = has;
    }

    /**
     * Returns true/false if the specified event name is a native browser event or not.
     *
     * @method isNative
     * @param {String} name Name to check if it's native.
     * @return {Boolean} true/false if the event is native or not.
     * @static
     */
    Dispatcher.isNative = function(name) {
        return !!nativeEvents[name.toLowerCase()];
    };

    return Dispatcher;
});

// Included from: js/tinymce/classes/ui/Selector.js

/**
 * Selector.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/*eslint no-nested-ternary:0 */

/**
 * Selector engine, enables you to select controls by using CSS like expressions.
 * We currently only support basic CSS expressions to reduce the size of the core
 * and the ones we support should be enough for most cases.
 *
 * @example
 * Supported expressions:
 *  element
 *  element#name
 *  element.class
 *  element[attr]
 *  element[attr*=value]
 *  element[attr~=value]
 *  element[attr!=value]
 *  element[attr^=value]
 *  element[attr$=value]
 *  element:<state>
 *  element:not(<expression>)
 *  element:first
 *  element:last
 *  element:odd
 *  element:even
 *  element element
 *  element > element
 *
 * @class tinymce.ui.Selector
 */
define("tinymce/ui/Selector", [
    "tinymce/util/Class"
], function(Class) {
    "use strict";

    /**
     * Produces an array with a unique set of objects. It will not compare the values
     * but the references of the objects.
     *
     * @private
     * @method unqiue
     * @param {Array} array Array to make into an array with unique items.
     * @return {Array} Array with unique items.
     */
    function unique(array) {
        var uniqueItems = [], i = array.length, item;

        while (i--) {
            item = array[i];

            if (!item.__checked) {
                uniqueItems.push(item);
                item.__checked = 1;
            }
        }

        i = uniqueItems.length;
        while (i--) {
            delete uniqueItems[i].__checked;
        }

        return uniqueItems;
    }

    var expression = /^([\w\\*]+)?(?:#([\w\\]+))?(?:\.([\w\\\.]+))?(?:\[\@?([\w\\]+)([\^\$\*!~]?=)([\w\\]+)\])?(?:\:(.+))?/i;

    /*jshint maxlen:255 */
    /*eslint max-len:0 */
    var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
        whiteSpace = /^\s*|\s*$/g,
        Collection;

    var Selector = Class.extend({
        /**
         * Constructs a new Selector instance.
         *
         * @constructor
         * @method init
         * @param {String} selector CSS like selector expression.
         */
        init: function(selector) {
            var match = this.match;

            function compileNameFilter(name) {
                if (name) {
                    name = name.toLowerCase();

                    return function(item) {
                        return name === '*' || item.type === name;
                    };
                }
            }

            function compileIdFilter(id) {
                if (id) {
                    return function(item) {
                        return item._name === id;
                    };
                }
            }

            function compileClassesFilter(classes) {
                if (classes) {
                    classes = classes.split('.');

                    return function(item) {
                        var i = classes.length;

                        while (i--) {
                            if (!item.hasClass(classes[i])) {
                                return false;
                            }
                        }

                        return true;
                    };
                }
            }

            function compileAttrFilter(name, cmp, check) {
                if (name) {
                    return function(item) {
                        var value = item[name] ? item[name]() : '';

                        return !cmp ? !!check :
                            cmp === "=" ? value === check :
                            cmp === "*=" ? value.indexOf(check) >= 0 :
                            cmp === "~=" ? (" " + value + " ").indexOf(" " + check + " ") >= 0 :
                            cmp === "!=" ? value != check :
                            cmp === "^=" ? value.indexOf(check) === 0 :
                            cmp === "$=" ? value.substr(value.length - check.length) === check :
                            false;
                    };
                }
            }

            function compilePsuedoFilter(name) {
                var notSelectors;

                if (name) {
                    name = /(?:not\((.+)\))|(.+)/i.exec(name);

                    if (!name[1]) {
                        name = name[2];

                        return function(item, index, length) {
                            return name === 'first' ? index === 0 :
                                name === 'last' ? index === length - 1 :
                                name === 'even' ? index % 2 === 0 :
                                name === 'odd' ? index % 2 === 1 :
                                item[name] ? item[name]() :
                                false;
                        };
                    } else {
                        // Compile not expression
                        notSelectors = parseChunks(name[1], []);

                        return function(item) {
                            return !match(item, notSelectors);
                        };
                    }
                }
            }

            function compile(selector, filters, direct) {
                var parts;

                function add(filter) {
                    if (filter) {
                        filters.push(filter);
                    }
                }

                // Parse expression into parts
                parts = expression.exec(selector.replace(whiteSpace, ''));

                add(compileNameFilter(parts[1]));
                add(compileIdFilter(parts[2]));
                add(compileClassesFilter(parts[3]));
                add(compileAttrFilter(parts[4], parts[5], parts[6]));
                add(compilePsuedoFilter(parts[7]));

                // Mark the filter with psuedo for performance
                filters.psuedo = !!parts[7];
                filters.direct = direct;

                return filters;
            }

            // Parser logic based on Sizzle by John Resig
            function parseChunks(selector, selectors) {
                var parts = [], extra, matches, i;

                do {
                    chunker.exec("");
                    matches = chunker.exec(selector);

                    if (matches) {
                        selector = matches[3];
                        parts.push(matches[1]);

                        if (matches[2]) {
                            extra = matches[3];
                            break;
                        }
                    }
                } while (matches);

                if (extra) {
                    parseChunks(extra, selectors);
                }

                selector = [];
                for (i = 0; i < parts.length; i++) {
                    if (parts[i] != '>') {
                        selector.push(compile(parts[i], [], parts[i - 1] === '>'));
                    }
                }

                selectors.push(selector);

                return selectors;
            }

            this._selectors = parseChunks(selector, []);
        },

        /**
         * Returns true/false if the selector matches the specified control.
         *
         * @method match
         * @param {tinymce.ui.Control} control Control to match agains the selector.
         * @param {Array} selectors Optional array of selectors, mostly used internally.
         * @return {Boolean} true/false state if the control matches or not.
         */
        match: function(control, selectors) {
            var i, l, si, sl, selector, fi, fl, filters, index, length, siblings, count, item;

            selectors = selectors || this._selectors;
            for (i = 0, l = selectors.length; i < l; i++) {
                selector = selectors[i];
                sl = selector.length;
                item = control;
                count = 0;

                for (si = sl - 1; si >= 0; si--) {
                    filters = selector[si];

                    while (item) {
                        // Find the index and length since a psuedo filter like :first needs it
                        if (filters.psuedo) {
                            siblings = item.parent().items();
                            index = length = siblings.length;
                            while (index--) {
                                if (siblings[index] === item) {
                                    break;
                                }
                            }
                        }

                        for (fi = 0, fl = filters.length; fi < fl; fi++) {
                            if (!filters[fi](item, index, length)) {
                                fi = fl + 1;
                                break;
                            }
                        }

                        if (fi === fl) {
                            count++;
                            break;
                        } else {
                            // If it didn't match the right most expression then
                            // break since it's no point looking at the parents
                            if (si === sl - 1) {
                                break;
                            }
                        }

                        item = item.parent();
                    }
                }

                // If we found all selectors then return true otherwise continue looking
                if (count === sl) {
                    return true;
                }
            }

            return false;
        },

        /**
         * Returns a tinymce.ui.Collection with matches of the specified selector inside the specified container.
         *
         * @method find
         * @param {tinymce.ui.Control} container Container to look for items in.
         * @return {tinymce.ui.Collection} Collection with matched elements.
         */
        find: function(container) {
            var matches = [], i, l, selectors = this._selectors;

            function collect(items, selector, index) {
                var i, l, fi, fl, item, filters = selector[index];

                for (i = 0, l = items.length; i < l; i++) {
                    item = items[i];

                    // Run each filter agains the item
                    for (fi = 0, fl = filters.length; fi < fl; fi++) {
                        if (!filters[fi](item, i, l)) {
                            fi = fl + 1;
                            break;
                        }
                    }

                    // All filters matched the item
                    if (fi === fl) {
                        // Matched item is on the last expression like: panel toolbar [button]
                        if (index == selector.length - 1) {
                            matches.push(item);
                        } else {
                            // Collect next expression type
                            if (item.items) {
                                collect(item.items(), selector, index + 1);
                            }
                        }
                    } else if (filters.direct) {
                        return;
                    }

                    // Collect child items
                    if (item.items) {
                        collect(item.items(), selector, index);
                    }
                }
            }

            if (container.items) {
                for (i = 0, l = selectors.length; i < l; i++) {
                    collect(container.items(), selectors[i], 0);
                }

                // Unique the matches if needed
                if (l > 1) {
                    matches = unique(matches);
                }
            }

            // Fix for circular reference
            if (!Collection) {
                // TODO: Fix me!
                Collection = Selector.Collection;
            }

            return new Collection(matches);
        }
    });

    return Selector;
});

// Included from: js/tinymce/classes/ui/Collection.js

/**
 * Collection.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Control collection, this class contains control instances and it enables you to
 * perform actions on all the contained items. This is very similar to how jQuery works.
 *
 * @example
 * someCollection.show().disabled(true);
 *
 * @class tinymce.ui.Collection
 */
define("tinymce/ui/Collection", [
    "tinymce/util/Tools",
    "tinymce/ui/Selector",
    "tinymce/util/Class"
], function(Tools, Selector, Class) {
    "use strict";

    var Collection, proto, push = Array.prototype.push, slice = Array.prototype.slice;

    proto = {
        /**
         * Current number of contained control instances.
         *
         * @field length
         * @type Number
         */
        length: 0,

        /**
         * Constructor for the collection.
         *
         * @constructor
         * @method init
         * @param {Array} items Optional array with items to add.
         */
        init: function(items) {
            if (items) {
                this.add(items);
            }
        },

        /**
         * Adds new items to the control collection.
         *
         * @method add
         * @param {Array} items Array if items to add to collection.
         * @return {tinymce.ui.Collection} Current collection instance.
         */
        add: function(items) {
            var self = this;

            // Force single item into array
            if (!Tools.isArray(items)) {
                if (items instanceof Collection) {
                    self.add(items.toArray());
                } else {
                    push.call(self, items);
                }
            } else {
                push.apply(self, items);
            }

            return self;
        },

        /**
         * Sets the contents of the collection. This will remove any existing items
         * and replace them with the ones specified in the input array.
         *
         * @method set
         * @param {Array} items Array with items to set into the Collection.
         * @return {tinymce.ui.Collection} Collection instance.
         */
        set: function(items) {
            var self = this, len = self.length, i;

            self.length = 0;
            self.add(items);

            // Remove old entries
            for (i = self.length; i < len; i++) {
                delete self[i];
            }

            return self;
        },

        /**
         * Filters the collection item based on the specified selector expression or selector function.
         *
         * @method filter
         * @param {String} selector Selector expression to filter items by.
         * @return {tinymce.ui.Collection} Collection containing the filtered items.
         */
        filter: function(selector) {
            var self = this, i, l, matches = [], item, match;

            // Compile string into selector expression
            if (typeof(selector) === "string") {
                selector = new Selector(selector);

                match = function(item) {
                    return selector.match(item);
                };
            } else {
                // Use selector as matching function
                match = selector;
            }

            for (i = 0, l = self.length; i < l; i++) {
                item = self[i];

                if (match(item)) {
                    matches.push(item);
                }
            }

            return new Collection(matches);
        },

        /**
         * Slices the items within the collection.
         *
         * @method slice
         * @param {Number} index Index to slice at.
         * @param {Number} len Optional length to slice.
         * @return {tinymce.ui.Collection} Current collection.
         */
        slice: function() {
            return new Collection(slice.apply(this, arguments));
        },

        /**
         * Makes the current collection equal to the specified index.
         *
         * @method eq
         * @param {Number} index Index of the item to set the collection to.
         * @return {tinymce.ui.Collection} Current collection.
         */
        eq: function(index) {
            return index === -1 ? this.slice(index) : this.slice(index, +index + 1);
        },

        /**
         * Executes the specified callback on each item in collection.
         *
         * @method each
         * @param {function} callback Callback to execute for each item in collection.
         * @return {tinymce.ui.Collection} Current collection instance.
         */
        each: function(callback) {
            Tools.each(this, callback);

            return this;
        },

        /**
         * Returns an JavaScript array object of the contents inside the collection.
         *
         * @method toArray
         * @return {Array} Array with all items from collection.
         */
        toArray: function() {
            return Tools.toArray(this);
        },

        /**
         * Finds the index of the specified control or return -1 if it isn't in the collection.
         *
         * @method indexOf
         * @param {Control} ctrl Control instance to look for.
         * @return {Number} Index of the specified control or -1.
         */
        indexOf: function(ctrl) {
            var self = this, i = self.length;

            while (i--) {
                if (self[i] === ctrl) {
                    break;
                }
            }

            return i;
        },

        /**
         * Returns a new collection of the contents in reverse order.
         *
         * @method reverse
         * @return {tinymce.ui.Collection} Collection instance with reversed items.
         */
        reverse: function() {
            return new Collection(Tools.toArray(this).reverse());
        },

        /**
         * Returns true/false if the class exists or not.
         *
         * @method hasClass
         * @param {String} cls Class to check for.
         * @return {Boolean} true/false state if the class exists or not.
         */
        hasClass: function(cls) {
            return this[0] ? this[0].hasClass(cls) : false;
        },

        /**
         * Sets/gets the specific property on the items in the collection. The same as executing control.<property>(<value>);
         *
         * @method prop
         * @param {String} name Property name to get/set.
         * @param {Object} value Optional object value to set.
         * @return {tinymce.ui.Collection} Current collection instance or value of the first item on a get operation.
         */
        prop: function(name, value) {
            var self = this, undef, item;

            if (value !== undef) {
                self.each(function(item) {
                    if (item[name]) {
                        item[name](value);
                    }
                });

                return self;
            }

            item = self[0];

            if (item && item[name]) {
                return item[name]();
            }
        },

        /**
         * Executes the specific function name with optional arguments an all items in collection if it exists.
         *
         * @example collection.exec("myMethod", arg1, arg2, arg3);
         * @method exec
         * @param {String} name Name of the function to execute.
         * @param {Object} ... Multiple arguments to pass to each function.
         * @return {tinymce.ui.Collection} Current collection.
         */
        exec: function(name) {
            var self = this, args = Tools.toArray(arguments).slice(1);

            self.each(function(item) {
                if (item[name]) {
                    item[name].apply(item, args);
                }
            });

            return self;
        },

        /**
         * Remove all items from collection and DOM.
         *
         * @method remove
         * @return {tinymce.ui.Collection} Current collection.
         */
        remove: function() {
            var i = this.length;

            while (i--) {
                this[i].remove();
            }

            return this;
        }

        /**
         * Fires the specified event by name and arguments on the control. This will execute all
         * bound event handlers.
         *
         * @method fire
         * @param {String} name Name of the event to fire.
         * @param {Object} args Optional arguments to pass to the event.
         * @return {tinymce.ui.Collection} Current collection instance.
         */
        // fire: function(event, args) {}, -- Generated by code below

        /**
         * Binds a callback to the specified event. This event can both be
         * native browser events like "click" or custom ones like PostRender.
         *
         * The callback function will have two parameters the first one being the control that received the event
         * the second one will be the event object either the browsers native event object or a custom JS object.
         *
         * @method on
         * @param {String} name Name of the event to bind. For example "click".
         * @param {String/function} callback Callback function to execute ones the event occurs.
         * @return {tinymce.ui.Collection} Current collection instance.
         */
        // on: function(name, callback) {}, -- Generated by code below

        /**
         * Unbinds the specified event and optionally a specific callback. If you omit the name
         * parameter all event handlers will be removed. If you omit the callback all event handles
         * by the specified name will be removed.
         *
         * @method off
         * @param {String} name Optional name for the event to unbind.
         * @param {function} callback Optional callback function to unbind.
         * @return {tinymce.ui.Collection} Current collection instance.
         */
        // off: function(name, callback) {}, -- Generated by code below

        /**
         * Shows the items in the current collection.
         *
         * @method show
         * @return {tinymce.ui.Collection} Current collection instance.
         */
        // show: function() {}, -- Generated by code below

        /**
         * Hides the items in the current collection.
         *
         * @method hide
         * @return {tinymce.ui.Collection} Current collection instance.
         */
        // hide: function() {}, -- Generated by code below

        /**
         * Sets/gets the text contents of the items in the current collection.
         *
         * @method text
         * @return {tinymce.ui.Collection} Current collection instance or text value of the first item on a get operation.
         */
        // text: function(value) {}, -- Generated by code below

        /**
         * Sets/gets the name contents of the items in the current collection.
         *
         * @method name
         * @return {tinymce.ui.Collection} Current collection instance or name value of the first item on a get operation.
         */
        // name: function(value) {}, -- Generated by code below

        /**
         * Sets/gets the disabled state on the items in the current collection.
         *
         * @method disabled
         * @return {tinymce.ui.Collection} Current collection instance or disabled state of the first item on a get operation.
         */
        // disabled: function(state) {}, -- Generated by code below

        /**
         * Sets/gets the active state on the items in the current collection.
         *
         * @method active
         * @return {tinymce.ui.Collection} Current collection instance or active state of the first item on a get operation.
         */
        // active: function(state) {}, -- Generated by code below

        /**
         * Sets/gets the selected state on the items in the current collection.
         *
         * @method selected
         * @return {tinymce.ui.Collection} Current collection instance or selected state of the first item on a get operation.
         */
        // selected: function(state) {}, -- Generated by code below

        /**
         * Sets/gets the selected state on the items in the current collection.
         *
         * @method visible
         * @return {tinymce.ui.Collection} Current collection instance or visible state of the first item on a get operation.
         */
        // visible: function(state) {}, -- Generated by code below

        /**
         * Adds a class to all items in the collection.
         *
         * @method addClass
         * @param {String} cls Class to add to each item.
         * @return {tinymce.ui.Collection} Current collection instance.
         */
        // addClass: function(cls) {}, -- Generated by code below

        /**
         * Removes the specified class from all items in collection.
         *
         * @method removeClass
         * @param {String} cls Class to remove from each item.
         * @return {tinymce.ui.Collection} Current collection instance.
         */
        // removeClass: function(cls) {}, -- Generated by code below
    };

    // Extend tinymce.ui.Collection prototype with some generated control specific methods
    Tools.each('fire on off show hide addClass removeClass append prepend before after reflow'.split(' '), function(name) {
        proto[name] = function() {
            var args = Tools.toArray(arguments);

            this.each(function(ctrl) {
                if (name in ctrl) {
                    ctrl[name].apply(ctrl, args);
                }
            });

            return this;
        };
    });

    // Extend tinymce.ui.Collection prototype with some property methods
    Tools.each('text name disabled active selected checked visible parent value data'.split(' '), function(name) {
        proto[name] = function(value) {
            return this.prop(name, value);
        };
    });

    // Create class based on the new prototype
    Collection = Class.extend(proto);

    // Stick Collection into Selector to prevent circual references
    Selector.Collection = Collection;

    return Collection;
});

// Included from: js/tinymce/classes/ui/DomUtils.js

/**
 * DOMUtils.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

define("tinymce/ui/DomUtils", [
    "tinymce/util/Tools",
    "tinymce/dom/DOMUtils"
], function(Tools, DOMUtils) {
    "use strict";

    var count = 0;

    return {
        id: function() {
            return 'mceu_' + (count++);
        },

        createFragment: function(html) {
            return DOMUtils.DOM.createFragment(html);
        },

        getWindowSize: function() {
            return DOMUtils.DOM.getViewPort();
        },

        getSize: function(elm) {
            var width, height;

            if (elm.getBoundingClientRect) {
                var rect = elm.getBoundingClientRect();

                width = Math.max(rect.width || (rect.right - rect.left), elm.offsetWidth);
                height = Math.max(rect.height || (rect.bottom - rect.bottom), elm.offsetHeight);
            } else {
                width = elm.offsetWidth;
                height = elm.offsetHeight;
            }

            return {width: width, height: height};
        },

        getPos: function(elm, root) {
            return DOMUtils.DOM.getPos(elm, root);
        },

        getViewPort: function(win) {
            return DOMUtils.DOM.getViewPort(win);
        },

        get: function(id) {
            return document.getElementById(id);
        },

        addClass : function(elm, cls) {
            return DOMUtils.DOM.addClass(elm, cls);
        },

        removeClass : function(elm, cls) {
            return DOMUtils.DOM.removeClass(elm, cls);
        },

        hasClass : function(elm, cls) {
            return DOMUtils.DOM.hasClass(elm, cls);
        },

        toggleClass: function(elm, cls, state) {
            return DOMUtils.DOM.toggleClass(elm, cls, state);
        },

        css: function(elm, name, value) {
            return DOMUtils.DOM.setStyle(elm, name, value);
        },

        on: function(target, name, callback, scope) {
            return DOMUtils.DOM.bind(target, name, callback, scope);
        },

        off: function(target, name, callback) {
            return DOMUtils.DOM.unbind(target, name, callback);
        },

        fire: function(target, name, args) {
            return DOMUtils.DOM.fire(target, name, args);
        },

        innerHtml: function(elm, html) {
            // Workaround for <div> in <p> bug on IE 8 #6178
            DOMUtils.DOM.setHTML(elm, html);
        }
    };
});

// Included from: js/tinymce/classes/ui/Control.js

/**
 * Control.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/*eslint consistent-this:0 */

/**
 * This is the base class for all controls and containers. All UI control instances inherit
 * from this one as it has the base logic needed by all of them.
 *
 * @class tinymce.ui.Control
 */
define("tinymce/ui/Control", [
    "tinymce/util/Class",
    "tinymce/util/Tools",
    "tinymce/util/EventDispatcher",
    "tinymce/ui/Collection",
    "tinymce/ui/DomUtils"
], function(Class, Tools, EventDispatcher, Collection, DomUtils) {
    "use strict";

    var elementIdCache = {};
    var hasMouseWheelEventSupport = "onmousewheel" in document;
    var hasWheelEventSupport = false;
    var classPrefix = "mce-";

    function getEventDispatcher(obj) {
        if (!obj._eventDispatcher) {
            obj._eventDispatcher = new EventDispatcher({
                scope: obj,
                toggleEvent: function(name, state) {
                    if (state && EventDispatcher.isNative(name)) {
                        if (!obj._nativeEvents) {
                            obj._nativeEvents = {};
                        }

                        obj._nativeEvents[name] = true;

                        if (obj._rendered) {
                            obj.bindPendingEvents();
                        }
                    }
                }
            });
        }

        return obj._eventDispatcher;
    }

    var Control = Class.extend({
        Statics: {
            elementIdCache: elementIdCache,
            classPrefix: classPrefix
        },

        isRtl: function() {
            return Control.rtl;
        },

        /**
         * Class/id prefix to use for all controls.
         *
         * @final
         * @field {String} classPrefix
         */
        classPrefix: classPrefix,

        /**
         * Constructs a new control instance with the specified settings.
         *
         * @constructor
         * @param {Object} settings Name/value object with settings.
         * @setting {String} style Style CSS properties to add.
         * @setting {String} border Border box values example: 1 1 1 1
         * @setting {String} padding Padding box values example: 1 1 1 1
         * @setting {String} margin Margin box values example: 1 1 1 1
         * @setting {Number} minWidth Minimal width for the control.
         * @setting {Number} minHeight Minimal height for the control.
         * @setting {String} classes Space separated list of classes to add.
         * @setting {String} role WAI-ARIA role to use for control.
         * @setting {Boolean} hidden Is the control hidden by default.
         * @setting {Boolean} disabled Is the control disabled by default.
         * @setting {String} name Name of the control instance.
         */
        init: function(settings) {
            var self = this, classes, i;

            self.settings = settings = Tools.extend({}, self.Defaults, settings);

            // Initial states
            self._id = settings.id || DomUtils.id();
            self._text = self._name = '';
            self._width = self._height = 0;
            self._aria = {role: settings.role};

            // Setup classes
            classes = settings.classes;
            if (classes) {
                classes = classes.split(' ');
                classes.map = {};
                i = classes.length;
                while (i--) {
                    classes.map[classes[i]] = true;
                }
            }

            self._classes = classes || [];
            self.visible(true);

            // Set some properties
            Tools.each('title text width height name classes visible disabled active value'.split(' '), function(name) {
                var value = settings[name], undef;

                if (value !== undef) {
                    self[name](value);
                } else if (self['_' + name] === undef) {
                    self['_' + name] = false;
                }
            });

            self.on('click', function() {
                if (self.disabled()) {
                    return false;
                }
            });

            // TODO: Is this needed duplicate code see above?
            if (settings.classes) {
                Tools.each(settings.classes.split(' '), function(cls) {
                    self.addClass(cls);
                });
            }

            /**
             * Name/value object with settings for the current control.
             *
             * @field {Object} settings
             */
            self.settings = settings;

            self._borderBox = self.parseBox(settings.border);
            self._paddingBox = self.parseBox(settings.padding);
            self._marginBox = self.parseBox(settings.margin);

            if (settings.hidden) {
                self.hide();
            }
        },

        // Will generate getter/setter methods for these properties
        Properties: 'parent,title,text,width,height,disabled,active,name,value',

        // Will generate empty dummy functions for these
        Methods: 'renderHtml',

        /**
         * Returns the root element to render controls into.
         *
         * @method getContainerElm
         * @return {Element} HTML DOM element to render into.
         */
        getContainerElm: function() {
            return document.body;
        },

        /**
         * Returns a control instance for the current DOM element.
         *
         * @method getParentCtrl
         * @param {Element} elm HTML dom element to get parent control from.
         * @return {tinymce.ui.Control} Control instance or undefined.
         */
        getParentCtrl: function(elm) {
            var ctrl, lookup = this.getRoot().controlIdLookup;

            while (elm && lookup) {
                ctrl = lookup[elm.id];
                if (ctrl) {
                    break;
                }

                elm = elm.parentNode;
            }

            return ctrl;
        },

        /**
         * Parses the specified box value. A box value contains 1-4 properties in clockwise order.
         *
         * @method parseBox
         * @param {String/Number} value Box value "0 1 2 3" or "0" etc.
         * @return {Object} Object with top/right/bottom/left properties.
         * @private
         */
        parseBox: function(value) {
            var len, radix = 10;

            if (!value) {
                return;
            }

            if (typeof(value) === "number") {
                value = value || 0;

                return {
                    top: value,
                    left: value,
                    bottom: value,
                    right: value
                };
            }

            value = value.split(' ');
            len = value.length;

            if (len === 1) {
                value[1] = value[2] = value[3] = value[0];
            } else if (len === 2) {
                value[2] = value[0];
                value[3] = value[1];
            } else if (len === 3) {
                value[3] = value[1];
            }

            return {
                top: parseInt(value[0], radix) || 0,
                right: parseInt(value[1], radix) || 0,
                bottom: parseInt(value[2], radix) || 0,
                left: parseInt(value[3], radix) || 0
            };
        },

        borderBox: function() {
            return this._borderBox;
        },

        paddingBox: function() {
            return this._paddingBox;
        },

        marginBox: function() {
            return this._marginBox;
        },

        measureBox: function(elm, prefix) {
            function getStyle(name) {
                var defaultView = document.defaultView;

                if (defaultView) {
                    // Remove camelcase
                    name = name.replace(/[A-Z]/g, function(a) {
                        return '-' + a;
                    });

                    return defaultView.getComputedStyle(elm, null).getPropertyValue(name);
                }

                return elm.currentStyle[name];
            }

            function getSide(name) {
                var val = parseFloat(getStyle(name), 10);

                return isNaN(val) ? 0 : val;
            }

            return {
                top: getSide(prefix + "TopWidth"),
                right: getSide(prefix + "RightWidth"),
                bottom: getSide(prefix + "BottomWidth"),
                left: getSide(prefix + "LeftWidth")
            };
        },

        /**
         * Initializes the current controls layout rect.
         * This will be executed by the layout managers to determine the
         * default minWidth/minHeight etc.
         *
         * @method initLayoutRect
         * @return {Object} Layout rect instance.
         */
        initLayoutRect: function() {
            var self = this, settings = self.settings, borderBox, layoutRect;
            var elm = self.getEl(), width, height, minWidth, minHeight, autoResize;
            var startMinWidth, startMinHeight, initialSize;

            // Measure the current element
            borderBox = self._borderBox = self._borderBox || self.measureBox(elm, 'border');
            self._paddingBox = self._paddingBox || self.measureBox(elm, 'padding');
            self._marginBox = self._marginBox || self.measureBox(elm, 'margin');
            initialSize = DomUtils.getSize(elm);

            // Setup minWidth/minHeight and width/height
            startMinWidth = settings.minWidth;
            startMinHeight = settings.minHeight;
            minWidth = startMinWidth || initialSize.width;
            minHeight = startMinHeight || initialSize.height;
            width = settings.width;
            height = settings.height;
            autoResize = settings.autoResize;
            autoResize = typeof(autoResize) != "undefined" ? autoResize : !width && !height;

            width = width || minWidth;
            height = height || minHeight;

            var deltaW = borderBox.left + borderBox.right;
            var deltaH = borderBox.top + borderBox.bottom;

            var maxW = settings.maxWidth || 0xFFFF;
            var maxH = settings.maxHeight || 0xFFFF;

            // Setup initial layout rect
            self._layoutRect = layoutRect = {
                x: settings.x || 0,
                y: settings.y || 0,
                w: width,
                h: height,
                deltaW: deltaW,
                deltaH: deltaH,
                contentW: width - deltaW,
                contentH: height - deltaH,
                innerW: width - deltaW,
                innerH: height - deltaH,
                startMinWidth: startMinWidth || 0,
                startMinHeight: startMinHeight || 0,
                minW: Math.min(minWidth, maxW),
                minH: Math.min(minHeight, maxH),
                maxW: maxW,
                maxH: maxH,
                autoResize: autoResize,
                scrollW: 0
            };

            self._lastLayoutRect = {};

            return layoutRect;
        },

        /**
         * Getter/setter for the current layout rect.
         *
         * @method layoutRect
         * @param {Object} [newRect] Optional new layout rect.
         * @return {tinymce.ui.Control/Object} Current control or rect object.
         */
        layoutRect: function(newRect) {
            var self = this, curRect = self._layoutRect, lastLayoutRect, size, deltaWidth, deltaHeight, undef, repaintControls;

            // Initialize default layout rect
            if (!curRect) {
                curRect = self.initLayoutRect();
            }

            // Set new rect values
            if (newRect) {
                // Calc deltas between inner and outer sizes
                deltaWidth = curRect.deltaW;
                deltaHeight = curRect.deltaH;

                // Set x position
                if (newRect.x !== undef) {
                    curRect.x = newRect.x;
                }

                // Set y position
                if (newRect.y !== undef) {
                    curRect.y = newRect.y;
                }

                // Set minW
                if (newRect.minW !== undef) {
                    curRect.minW = newRect.minW;
                }

                // Set minH
                if (newRect.minH !== undef) {
                    curRect.minH = newRect.minH;
                }

                // Set new width and calculate inner width
                size = newRect.w;
                if (size !== undef) {
                    size = size < curRect.minW ? curRect.minW : size;
                    size = size > curRect.maxW ? curRect.maxW : size;
                    curRect.w = size;
                    curRect.innerW = size - deltaWidth;
                }

                // Set new height and calculate inner height
                size = newRect.h;
                if (size !== undef) {
                    size = size < curRect.minH ? curRect.minH : size;
                    size = size > curRect.maxH ? curRect.maxH : size;
                    curRect.h = size;
                    curRect.innerH = size - deltaHeight;
                }

                // Set new inner width and calculate width
                size = newRect.innerW;
                if (size !== undef) {
                    size = size < curRect.minW - deltaWidth ? curRect.minW - deltaWidth : size;
                    size = size > curRect.maxW - deltaWidth ? curRect.maxW - deltaWidth : size;
                    curRect.innerW = size;
                    curRect.w = size + deltaWidth;
                }

                // Set new height and calculate inner height
                size = newRect.innerH;
                if (size !== undef) {
                    size = size < curRect.minH - deltaHeight ? curRect.minH - deltaHeight : size;
                    size = size > curRect.maxH - deltaHeight ? curRect.maxH - deltaHeight : size;
                    curRect.innerH = size;
                    curRect.h = size + deltaHeight;
                }

                // Set new contentW
                if (newRect.contentW !== undef) {
                    curRect.contentW = newRect.contentW;
                }

                // Set new contentH
                if (newRect.contentH !== undef) {
                    curRect.contentH = newRect.contentH;
                }

                // Compare last layout rect with the current one to see if we need to repaint or not
                lastLayoutRect = self._lastLayoutRect;
                if (lastLayoutRect.x !== curRect.x || lastLayoutRect.y !== curRect.y ||
                    lastLayoutRect.w !== curRect.w || lastLayoutRect.h !== curRect.h) {
                    repaintControls = Control.repaintControls;

                    if (repaintControls) {
                        if (repaintControls.map && !repaintControls.map[self._id]) {
                            repaintControls.push(self);
                            repaintControls.map[self._id] = true;
                        }
                    }

                    lastLayoutRect.x = curRect.x;
                    lastLayoutRect.y = curRect.y;
                    lastLayoutRect.w = curRect.w;
                    lastLayoutRect.h = curRect.h;
                }

                return self;
            }

            return curRect;
        },

        /**
         * Repaints the control after a layout operation.
         *
         * @method repaint
         */
        repaint: function() {
            var self = this, style, bodyStyle, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect, round;

            // Use Math.round on all values on IE < 9
            round = !document.createRange ? Math.round : function(value) {
                return value;
            };

            style = self.getEl().style;
            rect = self._layoutRect;
            lastRepaintRect = self._lastRepaintRect || {};

            borderBox = self._borderBox;
            borderW = borderBox.left + borderBox.right;
            borderH = borderBox.top + borderBox.bottom;

            if (rect.x !== lastRepaintRect.x) {
                style.left = round(rect.x) + 'px';
                lastRepaintRect.x = rect.x;
            }

            if (rect.y !== lastRepaintRect.y) {
                style.top = round(rect.y) + 'px';
                lastRepaintRect.y = rect.y;
            }

            if (rect.w !== lastRepaintRect.w) {
                style.width = round(rect.w - borderW) + 'px';
                lastRepaintRect.w = rect.w;
            }

            if (rect.h !== lastRepaintRect.h) {
                style.height = round(rect.h - borderH) + 'px';
                lastRepaintRect.h = rect.h;
            }

            // Update body if needed
            if (self._hasBody && rect.innerW !== lastRepaintRect.innerW) {
                bodyStyle = self.getEl('body').style;
                bodyStyle.width = round(rect.innerW) + 'px';
                lastRepaintRect.innerW = rect.innerW;
            }

            if (self._hasBody && rect.innerH !== lastRepaintRect.innerH) {
                bodyStyle = bodyStyle || self.getEl('body').style;
                bodyStyle.height = round(rect.innerH) + 'px';
                lastRepaintRect.innerH = rect.innerH;
            }

            self._lastRepaintRect = lastRepaintRect;
            self.fire('repaint', {}, false);
        },

        /**
         * Binds a callback to the specified event. This event can both be
         * native browser events like "click" or custom ones like PostRender.
         *
         * The callback function will be passed a DOM event like object that enables yout do stop propagation.
         *
         * @method on
         * @param {String} name Name of the event to bind. For example "click".
         * @param {String/function} callback Callback function to execute ones the event occurs.
         * @return {tinymce.ui.Control} Current control object.
         */
        on: function(name, callback) {
            var self = this;

            function resolveCallbackName(name) {
                var callback, scope;

                if (typeof(name) != 'string') {
                    return name;
                }

                return function(e) {
                    if (!callback) {
                        self.parentsAndSelf().each(function(ctrl) {
                            var callbacks = ctrl.settings.callbacks;

                            if (callbacks && (callback = callbacks[name])) {
                                scope = ctrl;
                                return false;
                            }
                        });
                    }

                    return callback.call(scope, e);
                };
            }

            getEventDispatcher(self).on(name, resolveCallbackName(callback));

            return self;
        },

        /**
         * Unbinds the specified event and optionally a specific callback. If you omit the name
         * parameter all event handlers will be removed. If you omit the callback all event handles
         * by the specified name will be removed.
         *
         * @method off
         * @param {String} [name] Name for the event to unbind.
         * @param {function} [callback] Callback function to unbind.
         * @return {mxex.ui.Control} Current control object.
         */
        off: function(name, callback) {
            getEventDispatcher(this).off(name, callback);
            return this;
        },

        /**
         * Fires the specified event by name and arguments on the control. This will execute all
         * bound event handlers.
         *
         * @method fire
         * @param {String} name Name of the event to fire.
         * @param {Object} [args] Arguments to pass to the event.
         * @param {Boolean} [bubble] Value to control bubbeling. Defaults to true.
         * @return {Object} Current arguments object.
         */
        fire: function(name, args, bubble) {
            var self = this;

            args = args || {};

            if (!args.control) {
                args.control = self;
            }

            args = getEventDispatcher(self).fire(name, args);

            // Bubble event up to parents
            if (bubble !== false && self.parent) {
                var parent = self.parent();
                while (parent && !args.isPropagationStopped()) {
                    parent.fire(name, args, false);
                    parent = parent.parent();
                }
            }

            return args;
        },

        /**
         * Returns true/false if the specified event has any listeners.
         *
         * @method hasEventListeners
         * @param {String} name Name of the event to check for.
         * @return {Boolean} True/false state if the event has listeners.
         */
        hasEventListeners: function(name) {
            return getEventDispatcher(this).has(name);
        },

        /**
         * Returns a control collection with all parent controls.
         *
         * @method parents
         * @param {String} selector Optional selector expression to find parents.
         * @return {tinymce.ui.Collection} Collection with all parent controls.
         */
        parents: function(selector) {
            var self = this, ctrl, parents = new Collection();

            // Add each parent to collection
            for (ctrl = self.parent(); ctrl; ctrl = ctrl.parent()) {
                parents.add(ctrl);
            }

            // Filter away everything that doesn't match the selector
            if (selector) {
                parents = parents.filter(selector);
            }

            return parents;
        },

        /**
         * Returns the current control and it's parents.
         *
         * @method parentsAndSelf
         * @param {String} selector Optional selector expression to find parents.
         * @return {tinymce.ui.Collection} Collection with all parent controls.
         */
        parentsAndSelf: function(selector) {
            return new Collection(this).add(this.parents(selector));
        },

        /**
         * Returns the control next to the current control.
         *
         * @method next
         * @return {tinymce.ui.Control} Next control instance.
         */
        next: function() {
            var parentControls = this.parent().items();

            return parentControls[parentControls.indexOf(this) + 1];
        },

        /**
         * Returns the control previous to the current control.
         *
         * @method prev
         * @return {tinymce.ui.Control} Previous control instance.
         */
        prev: function() {
            var parentControls = this.parent().items();

            return parentControls[parentControls.indexOf(this) - 1];
        },

        /**
         * Find the common ancestor for two control instances.
         *
         * @method findCommonAncestor
         * @param {tinymce.ui.Control} ctrl1 First control.
         * @param {tinymce.ui.Control} ctrl2 Second control.
         * @return {tinymce.ui.Control} Ancestor control instance.
         */
        findCommonAncestor: function(ctrl1, ctrl2) {
            var parentCtrl;

            while (ctrl1) {
                parentCtrl = ctrl2;

                while (parentCtrl && ctrl1 != parentCtrl) {
                    parentCtrl = parentCtrl.parent();
                }

                if (ctrl1 == parentCtrl) {
                    break;
                }

                ctrl1 = ctrl1.parent();
            }

            return ctrl1;
        },

        /**
         * Returns true/false if the specific control has the specific class.
         *
         * @method hasClass
         * @param {String} cls Class to check for.
         * @param {String} [group] Sub element group name.
         * @return {Boolean} True/false if the control has the specified class.
         */
        hasClass: function(cls, group) {
            var classes = this._classes[group || 'control'];

            cls = this.classPrefix + cls;

            return classes && !!classes.map[cls];
        },

        /**
         * Adds the specified class to the control
         *
         * @method addClass
         * @param {String} cls Class to check for.
         * @param {String} [group] Sub element group name.
         * @return {tinymce.ui.Control} Current control object.
         */
        addClass: function(cls, group) {
            var self = this, classes, elm;

            cls = this.classPrefix + cls;
            classes = self._classes[group || 'control'];

            if (!classes) {
                classes = [];
                classes.map = {};
                self._classes[group || 'control'] = classes;
            }

            if (!classes.map[cls]) {
                classes.map[cls] = cls;
                classes.push(cls);

                if (self._rendered) {
                    elm = self.getEl(group);

                    if (elm) {
                        elm.className = classes.join(' ');
                    }
                }
            }

            return self;
        },

        /**
         * Removes the specified class from the control.
         *
         * @method removeClass
         * @param {String} cls Class to remove.
         * @param {String} [group] Sub element group name.
         * @return {tinymce.ui.Control} Current control object.
         */
        removeClass: function(cls, group) {
            var self = this, classes, i, elm;

            cls = this.classPrefix + cls;
            classes = self._classes[group || 'control'];
            if (classes && classes.map[cls]) {
                delete classes.map[cls];

                i = classes.length;
                while (i--) {
                    if (classes[i] === cls) {
                        classes.splice(i, 1);
                    }
                }
            }

            if (self._rendered) {
                elm = self.getEl(group);

                if (elm) {
                    elm.className = classes.join(' ');
                }
            }

            return self;
        },

        /**
         * Toggles the specified class on the control.
         *
         * @method toggleClass
         * @param {String} cls Class to remove.
         * @param {Boolean} state True/false state to add/remove class.
         * @param {String} [group] Sub element group name.
         * @return {tinymce.ui.Control} Current control object.
         */
        toggleClass: function(cls, state, group) {
            var self = this;

            if (state) {
                self.addClass(cls, group);
            } else {
                self.removeClass(cls, group);
            }

            return self;
        },

        /**
         * Returns the class string for the specified group name.
         *
         * @method classes
         * @param {String} [group] Group to get clases by.
         * @return {String} Classes for the specified group.
         */
        classes: function(group) {
            var classes = this._classes[group || 'control'];

            return classes ? classes.join(' ') : '';
        },

        /**
         * Sets the inner HTML of the control element.
         *
         * @method innerHtml
         * @param {String} html Html string to set as inner html.
         * @return {tinymce.ui.Control} Current control object.
         */
        innerHtml: function(html) {
            DomUtils.innerHtml(this.getEl(), html);
            return this;
        },

        /**
         * Returns the control DOM element or sub element.
         *
         * @method getEl
         * @param {String} [suffix] Suffix to get element by.
         * @param {Boolean} [dropCache] True if the cache for the element should be dropped.
         * @return {Element} HTML DOM element for the current control or it's children.
         */
        getEl: function(suffix, dropCache) {
            var elm, id = suffix ? this._id + '-' + suffix : this._id;

            elm = elementIdCache[id] = (dropCache === true ? null : elementIdCache[id]) || DomUtils.get(id);

            return elm;
        },

        /**
         * Sets/gets the visible for the control.
         *
         * @method visible
         * @param {Boolean} state Value to set to control.
         * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
         */
        visible: function(state) {
            var self = this, parentCtrl;

            if (typeof(state) !== "undefined") {
                if (self._visible !== state) {
                    if (self._rendered) {
                        self.getEl().style.display = state ? '' : 'none';
                    }

                    self._visible = state;

                    // Parent container needs to reflow
                    parentCtrl = self.parent();
                    if (parentCtrl) {
                        parentCtrl._lastRect = null;
                    }

                    self.fire(state ? 'show' : 'hide');
                }

                return self;
            }

            return self._visible;
        },

        /**
         * Sets the visible state to true.
         *
         * @method show
         * @return {tinymce.ui.Control} Current control instance.
         */
        show: function() {
            return this.visible(true);
        },

        /**
         * Sets the visible state to false.
         *
         * @method hide
         * @return {tinymce.ui.Control} Current control instance.
         */
        hide: function() {
            return this.visible(false);
        },

        /**
         * Focuses the current control.
         *
         * @method focus
         * @return {tinymce.ui.Control} Current control instance.
         */
        focus: function() {
            try {
                this.getEl().focus();
            } catch (ex) {
                // Ignore IE error
            }

            return this;
        },

        /**
         * Blurs the current control.
         *
         * @method blur
         * @return {tinymce.ui.Control} Current control instance.
         */
        blur: function() {
            this.getEl().blur();

            return this;
        },

        /**
         * Sets the specified aria property.
         *
         * @method aria
         * @param {String} name Name of the aria property to set.
         * @param {String} value Value of the aria property.
         * @return {tinymce.ui.Control} Current control instance.
         */
        aria: function(name, value) {
            var self = this, elm = self.getEl(self.ariaTarget);

            if (typeof(value) === "undefined") {
                return self._aria[name];
            } else {
                self._aria[name] = value;
            }

            if (self._rendered) {
                elm.setAttribute(name == 'role' ? name : 'aria-' + name, value);
            }

            return self;
        },

        /**
         * Encodes the specified string with HTML entities. It will also
         * translate the string to different languages.
         *
         * @method encode
         * @param {String/Object/Array} text Text to entity encode.
         * @param {Boolean} [translate=true] False if the contents shouldn't be translated.
         * @return {String} Encoded and possible traslated string.
         */
        encode: function(text, translate) {
            if (translate !== false) {
                text = this.translate(text);
            }

            return (text || '').replace(/[&<>"]/g, function(match) {
                return '&#' + match.charCodeAt(0) + ';';
            });
        },

        /**
         * Returns the translated string.
         *
         * @method translate
         * @param {String} text Text to translate.
         * @return {String} Translated string or the same as the input.
         */
        translate: function(text) {
            return Control.translate ? Control.translate(text) : text;
        },

        /**
         * Adds items before the current control.
         *
         * @method before
         * @param {Array/tinymce.ui.Collection} items Array of items to prepend before this control.
         * @return {tinymce.ui.Control} Current control instance.
         */
        before: function(items) {
            var self = this, parent = self.parent();

            if (parent) {
                parent.insert(items, parent.items().indexOf(self), true);
            }

            return self;
        },

        /**
         * Adds items after the current control.
         *
         * @method after
         * @param {Array/tinymce.ui.Collection} items Array of items to append after this control.
         * @return {tinymce.ui.Control} Current control instance.
         */
        after: function(items) {
            var self = this, parent = self.parent();

            if (parent) {
                parent.insert(items, parent.items().indexOf(self));
            }

            return self;
        },

        /**
         * Removes the current control from DOM and from UI collections.
         *
         * @method remove
         * @return {tinymce.ui.Control} Current control instance.
         */
        remove: function() {
            var self = this, elm = self.getEl(), parent = self.parent(), newItems, i;

            if (self.items) {
                var controls = self.items().toArray();
                i = controls.length;
                while (i--) {
                    controls[i].remove();
                }
            }

            if (parent && parent.items) {
                newItems = [];

                parent.items().each(function(item) {
                    if (item !== self) {
                        newItems.push(item);
                    }
                });

                parent.items().set(newItems);
                parent._lastRect = null;
            }

            if (self._eventsRoot && self._eventsRoot == self) {
                DomUtils.off(elm);
            }

            var lookup = self.getRoot().controlIdLookup;
            if (lookup) {
                delete lookup[self._id];
            }

            delete elementIdCache[self._id];

            if (elm && elm.parentNode) {
                var nodes = elm.getElementsByTagName('*');

                i = nodes.length;
                while (i--) {
                    delete elementIdCache[nodes[i].id];
                }

                elm.parentNode.removeChild(elm);
            }

            self._rendered = false;

            return self;
        },

        /**
         * Renders the control before the specified element.
         *
         * @method renderBefore
         * @param {Element} elm Element to render before.
         * @return {tinymce.ui.Control} Current control instance.
         */
        renderBefore: function(elm) {
            var self = this;

            elm.parentNode.insertBefore(DomUtils.createFragment(self.renderHtml()), elm);
            self.postRender();

            return self;
        },

        /**
         * Renders the control to the specified element.
         *
         * @method renderBefore
         * @param {Element} elm Element to render to.
         * @return {tinymce.ui.Control} Current control instance.
         */
        renderTo: function(elm) {
            var self = this;

            elm = elm || self.getContainerElm();
            elm.appendChild(DomUtils.createFragment(self.renderHtml()));
            self.postRender();

            return self;
        },

        /**
         * Post render method. Called after the control has been rendered to the target.
         *
         * @method postRender
         * @return {tinymce.ui.Control} Current control instance.
         */
        postRender: function() {
            var self = this, settings = self.settings, elm, box, parent, name, parentEventsRoot;

            // Bind on<event> settings
            for (name in settings) {
                if (name.indexOf("on") === 0) {
                    self.on(name.substr(2), settings[name]);
                }
            }

            if (self._eventsRoot) {
                for (parent = self.parent(); !parentEventsRoot && parent; parent = parent.parent()) {
                    parentEventsRoot = parent._eventsRoot;
                }

                if (parentEventsRoot) {
                    for (name in parentEventsRoot._nativeEvents) {
                        self._nativeEvents[name] = true;
                    }
                }
            }

            self.bindPendingEvents();

            if (settings.style) {
                elm = self.getEl();
                if (elm) {
                    elm.setAttribute('style', settings.style);
                    elm.style.cssText = settings.style;
                }
            }

            if (!self._visible) {
                DomUtils.css(self.getEl(), 'display', 'none');
            }

            if (self.settings.border) {
                box = self.borderBox();
                DomUtils.css(self.getEl(), {
                    'border-top-width': box.top,
                    'border-right-width': box.right,
                    'border-bottom-width': box.bottom,
                    'border-left-width': box.left
                });
            }

            // Add instance to lookup
            var root = self.getRoot();
            if (!root.controlIdLookup) {
                root.controlIdLookup = {};
            }

            root.controlIdLookup[self._id] = self;

            for (var key in self._aria) {
                self.aria(key, self._aria[key]);
            }

            self.fire('postrender', {}, false);
        },

        /**
         * Scrolls the current control into view.
         *
         * @method scrollIntoView
         * @param {String} align Alignment in view top|center|bottom.
         * @return {tinymce.ui.Control} Current control instance.
         */
        scrollIntoView: function(align) {
            function getOffset(elm, rootElm) {
                var x, y, parent = elm;

                x = y = 0;
                while (parent && parent != rootElm && parent.nodeType) {
                    x += parent.offsetLeft || 0;
                    y += parent.offsetTop || 0;
                    parent = parent.offsetParent;
                }

                return {x: x, y: y};
            }

            var elm = this.getEl(), parentElm = elm.parentNode;
            var x, y, width, height, parentWidth, parentHeight;
            var pos = getOffset(elm, parentElm);

            x = pos.x;
            y = pos.y;
            width = elm.offsetWidth;
            height = elm.offsetHeight;
            parentWidth = parentElm.clientWidth;
            parentHeight = parentElm.clientHeight;

            if (align == "end") {
                x -= parentWidth - width;
                y -= parentHeight - height;
            } else if (align == "center") {
                x -= (parentWidth / 2) - (width / 2);
                y -= (parentHeight / 2) - (height / 2);
            }

            parentElm.scrollLeft = x;
            parentElm.scrollTop = y;

            return this;
        },

        /**
         * Binds pending DOM events.
         *
         * @private
         */
        bindPendingEvents: function() {
            var self = this, i, l, parents, eventRootCtrl, nativeEvents, name;

            function delegate(e) {
                var control = self.getParentCtrl(e.target);

                if (control) {
                    control.fire(e.type, e);
                }
            }

            function mouseLeaveHandler() {
                var ctrl = eventRootCtrl._lastHoverCtrl;

                if (ctrl) {
                    ctrl.fire("mouseleave", {target: ctrl.getEl()});

                    ctrl.parents().each(function(ctrl) {
                        ctrl.fire("mouseleave", {target: ctrl.getEl()});
                    });

                    eventRootCtrl._lastHoverCtrl = null;
                }
            }

            function mouseEnterHandler(e) {
                var ctrl = self.getParentCtrl(e.target), lastCtrl = eventRootCtrl._lastHoverCtrl, idx = 0, i, parents, lastParents;

                // Over on a new control
                if (ctrl !== lastCtrl) {
                    eventRootCtrl._lastHoverCtrl = ctrl;

                    parents = ctrl.parents().toArray().reverse();
                    parents.push(ctrl);

                    if (lastCtrl) {
                        lastParents = lastCtrl.parents().toArray().reverse();
                        lastParents.push(lastCtrl);

                        for (idx = 0; idx < lastParents.length; idx++) {
                            if (parents[idx] !== lastParents[idx]) {
                                break;
                            }
                        }

                        for (i = lastParents.length - 1; i >= idx; i--) {
                            lastCtrl = lastParents[i];
                            lastCtrl.fire("mouseleave", {
                                target : lastCtrl.getEl()
                            });
                        }
                    }

                    for (i = idx; i < parents.length; i++) {
                        ctrl = parents[i];
                        ctrl.fire("mouseenter", {
                            target : ctrl.getEl()
                        });
                    }
                }
            }

            function fixWheelEvent(e) {
                e.preventDefault();

                if (e.type == "mousewheel") {
                    e.deltaY = -1 / 40 * e.wheelDelta;

                    if (e.wheelDeltaX) {
                        e.deltaX = -1 / 40 * e.wheelDeltaX;
                    }
                } else {
                    e.deltaX = 0;
                    e.deltaY = e.detail;
                }

                e = self.fire("wheel", e);
            }

            self._rendered = true;

            nativeEvents = self._nativeEvents;
            if (nativeEvents) {
                // Find event root element if it exists
                parents = self.parents().toArray();
                parents.unshift(self);
                for (i = 0, l = parents.length; !eventRootCtrl && i < l; i++) {
                    eventRootCtrl = parents[i]._eventsRoot;
                }

                // Event root wasn't found the use the root control
                if (!eventRootCtrl) {
                    eventRootCtrl = parents[parents.length - 1] || self;
                }

                // Set the eventsRoot property on children that didn't have it
                self._eventsRoot = eventRootCtrl;
                for (l = i, i = 0; i < l; i++) {
                    parents[i]._eventsRoot = eventRootCtrl;
                }

                var eventRootDelegates = eventRootCtrl._delegates;
                if (!eventRootDelegates) {
                    eventRootDelegates = eventRootCtrl._delegates = {};
                }

                // Bind native event delegates
                for (name in nativeEvents) {
                    if (!nativeEvents) {
                        return false;
                    }

                    if (name === "wheel" && !hasWheelEventSupport) {
                        if (hasMouseWheelEventSupport) {
                            DomUtils.on(self.getEl(), "mousewheel", fixWheelEvent);
                        } else {
                            DomUtils.on(self.getEl(), "DOMMouseScroll", fixWheelEvent);
                        }

                        continue;
                    }

                    // Special treatment for mousenter/mouseleave since these doesn't bubble
                    if (name === "mouseenter" || name === "mouseleave") {
                        // Fake mousenter/mouseleave
                        if (!eventRootCtrl._hasMouseEnter) {
                            DomUtils.on(eventRootCtrl.getEl(), "mouseleave", mouseLeaveHandler);
                            DomUtils.on(eventRootCtrl.getEl(), "mouseover", mouseEnterHandler);
                            eventRootCtrl._hasMouseEnter = 1;
                        }
                    } else if (!eventRootDelegates[name]) {
                        DomUtils.on(eventRootCtrl.getEl(), name, delegate);
                        eventRootDelegates[name] = true;
                    }

                    // Remove the event once it's bound
                    nativeEvents[name] = false;
                }
            }
        },

        getRoot: function() {
            var ctrl = this, rootControl, parents = [];

            while (ctrl) {
                if (ctrl.rootControl) {
                    rootControl = ctrl.rootControl;
                    break;
                }

                parents.push(ctrl);
                rootControl = ctrl;
                ctrl = ctrl.parent();
            }

            if (!rootControl) {
                rootControl = this;
            }

            var i = parents.length;
            while (i--) {
                parents[i].rootControl = rootControl;
            }

            return rootControl;
        },

        /**
         * Reflows the current control and it's parents.
         * This should be used after you for example append children to the current control so
         * that the layout managers know that they need to reposition everything.
         *
         * @example
         * container.append({type: 'button', text: 'My button'}).reflow();
         *
         * @method reflow
         * @return {tinymce.ui.Control} Current control instance.
         */
        reflow: function() {
            this.repaint();

            return this;
        }

        /**
         * Sets/gets the parent container for the control.
         *
         * @method parent
         * @param {tinymce.ui.Container} parent Optional parent to set.
         * @return {tinymce.ui.Control} Parent control or the current control on a set action.
         */
        // parent: function(parent) {} -- Generated

        /**
         * Sets/gets the text for the control.
         *
         * @method text
         * @param {String} value Value to set to control.
         * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
         */
        // text: function(value) {} -- Generated

        /**
         * Sets/gets the width for the control.
         *
         * @method width
         * @param {Number} value Value to set to control.
         * @return {Number/tinymce.ui.Control} Current control on a set operation or current value on a get.
         */
        // width: function(value) {} -- Generated

        /**
         * Sets/gets the height for the control.
         *
         * @method height
         * @param {Number} value Value to set to control.
         * @return {Number/tinymce.ui.Control} Current control on a set operation or current value on a get.
         */
        // height: function(value) {} -- Generated

        /**
         * Sets/gets the disabled state on the control.
         *
         * @method disabled
         * @param {Boolean} state Value to set to control.
         * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
         */
        // disabled: function(state) {} -- Generated

        /**
         * Sets/gets the active for the control.
         *
         * @method active
         * @param {Boolean} state Value to set to control.
         * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
         */
        // active: function(state) {} -- Generated

        /**
         * Sets/gets the name for the control.
         *
         * @method name
         * @param {String} value Value to set to control.
         * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
         */
        // name: function(value) {} -- Generated

        /**
         * Sets/gets the title for the control.
         *
         * @method title
         * @param {String} value Value to set to control.
         * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
         */
        // title: function(value) {} -- Generated
    });

    return Control;
});

// Included from: js/tinymce/classes/ui/Factory.js

/**
 * Factory.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/*global tinymce:true */

/**
 * This class is a factory for control instances. This enables you
 * to create instances of controls without having to require the UI controls directly.
 *
 * It also allow you to override or add new control types.
 *
 * @class tinymce.ui.Factory
 */
define("tinymce/ui/Factory", [], function() {
    "use strict";

    var types = {}, namespaceInit;

    return {
        /**
         * Adds a new control instance type to the factory.
         *
         * @method add
         * @param {String} type Type name for example "button".
         * @param {function} typeClass Class type function.
         */
        add: function(type, typeClass) {
            types[type.toLowerCase()] = typeClass;
        },

        /**
         * Returns true/false if the specified type exists or not.
         *
         * @method has
         * @param {String} type Type to look for.
         * @return {Boolean} true/false if the control by name exists.
         */
        has: function(type) {
            return !!types[type.toLowerCase()];
        },

        /**
         * Creates a new control instance based on the settings provided. The instance created will be
         * based on the specified type property it can also create whole structures of components out of
         * the specified JSON object.
         *
         * @example
         * tinymce.ui.Factory.create({
         *     type: 'button',
         *     text: 'Hello world!'
         * });
         *
         * @method create
         * @param {Object/String} settings Name/Value object with items used to create the type.
         * @return {tinymce.ui.Control} Control instance based on the specified type.
         */
        create: function(type, settings) {
            var ControlType, name, namespace;

            // Build type lookup
            if (!namespaceInit) {
                namespace = tinymce.ui;

                for (name in namespace) {
                    types[name.toLowerCase()] = namespace[name];
                }

                namespaceInit = true;
            }

            // If string is specified then use it as the type
            if (typeof(type) == 'string') {
                settings = settings || {};
                settings.type = type;
            } else {
                settings = type;
                type = settings.type;
            }

            // Find control type
            type = type.toLowerCase();
            ControlType = types[type];

            // #if debug

            if (!ControlType) {
                throw new Error("Could not find control by type: " + type);
            }

            // #endif

            ControlType = new ControlType(settings);
            ControlType.type = type; // Set the type on the instance, this will be used by the Selector engine

            return ControlType;
        }
    };
});

// Included from: js/tinymce/classes/ui/KeyboardNavigation.js

/**
 * KeyboardNavigation.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class handles keyboard navigation of controls and elements.
 *
 * @class tinymce.ui.KeyboardNavigation
 */
define("tinymce/ui/KeyboardNavigation", [
], function() {
    "use strict";

    /**
     * This class handles all keyboard navigation for WAI-ARIA support. Each root container
     * gets an instance of this class.
     *
     * @constructor
     */
    return function(settings) {
        var root = settings.root, focusedElement, focusedControl;

        try {
            focusedElement = document.activeElement;
        } catch (ex) {
            // IE sometimes fails to return a proper element
            focusedElement = document.body;
        }

        focusedControl = root.getParentCtrl(focusedElement);

        /**
         * Returns the currently focused elements wai aria role of the currently
         * focused element or specified element.
         *
         * @private
         * @param {Element} elm Optional element to get role from.
         * @return {String} Role of specified element.
         */
        function getRole(elm) {
            elm = elm || focusedElement;

            return elm && elm.getAttribute('role');
        }

        /**
         * Returns the wai role of the parent element of the currently
         * focused element or specified element.
         *
         * @private
         * @param {Element} elm Optional element to get parent role from.
         * @return {String} Role of the first parent that has a role.
         */
        function getParentRole(elm) {
            var role, parent = elm || focusedElement;

            while ((parent = parent.parentNode)) {
                if ((role = getRole(parent))) {
                    return role;
                }
            }
        }

        /**
         * Returns a wai aria property by name for example aria-selected.
         *
         * @private
         * @param {String} name Name of the aria property to get for example "disabled".
         * @return {String} Aria property value.
         */
        function getAriaProp(name) {
            var elm = focusedElement;

            if (elm) {
                return elm.getAttribute('aria-' + name);
            }
        }

        /**
         * Is the element a text input element or not.
         *
         * @private
         * @param {Element} elm Element to check if it's an text input element or not.
         * @return {Boolean} True/false if the element is a text element or not.
         */
        function isTextInputElement(elm) {
            var tagName = elm.tagName.toUpperCase();

            // Notice: since type can be "email" etc we don't check the type
            // So all input elements gets treated as text input elements
            return tagName == "INPUT" || tagName == "TEXTAREA";
        }

        /**
         * Returns true/false if the specified element can be focused or not.
         *
         * @private
         * @param {Element} elm DOM element to check if it can be focused or not.
         * @return {Boolean} True/false if the element can have focus.
         */
        function canFocus(elm) {
            if (isTextInputElement(elm) && !elm.hidden) {
                return true;
            }

            if (/^(button|menuitem|checkbox|tab|menuitemcheckbox|option|gridcell)$/.test(getRole(elm))) {
                return true;
            }

            return false;
        }

        /**
         * Returns an array of focusable visible elements within the specified container element.
         *
         * @private
         * @param {Element} elm DOM element to find focusable elements within.
         * @return {Array} Array of focusable elements.
         */
        function getFocusElements(elm) {
            var elements = [];

            function collect(elm) {
                if (elm.nodeType != 1 || elm.style.display == 'none') {
                    return;
                }

                if (canFocus(elm)) {
                    elements.push(elm);
                }

                for (var i = 0; i < elm.childNodes.length; i++) {
                    collect(elm.childNodes[i]);
                }
            }

            collect(elm || root.getEl());

            return elements;
        }

        /**
         * Returns the navigation root control for the specified control. The navigation root
         * is the control that the keyboard navigation gets scoped to for example a menubar or toolbar group.
         * It will look for parents of the specified target control or the currenty focused control if this option is omitted.
         *
         * @private
         * @param {tinymce.ui.Control} targetControl Optional target control to find root of.
         * @return {tinymce.ui.Control} Navigation root control.
         */
        function getNavigationRoot(targetControl) {
            var navigationRoot, controls;

            targetControl = targetControl || focusedControl;
            controls = targetControl.parents().toArray();
            controls.unshift(targetControl);

            for (var i = 0; i < controls.length; i++) {
                navigationRoot = controls[i];

                if (navigationRoot.settings.ariaRoot) {
                    break;
                }
            }

            return navigationRoot;
        }

        /**
         * Focuses the first item in the specified targetControl element or the last aria index if the
         * navigation root has the ariaRemember option enabled.
         *
         * @private
         * @param {tinymce.ui.Control} targetControl Target control to focus the first item in.
         */
        function focusFirst(targetControl) {
            var navigationRoot = getNavigationRoot(targetControl);
            var focusElements = getFocusElements(navigationRoot.getEl());

            if (navigationRoot.settings.ariaRemember && "lastAriaIndex" in navigationRoot) {
                moveFocusToIndex(navigationRoot.lastAriaIndex, focusElements);
            } else {
                moveFocusToIndex(0, focusElements);
            }
        }

        /**
         * Moves the focus to the specified index within the elements list.
         * This will scope the index to the size of the element list if it changed.
         *
         * @private
         * @param {Number} idx Specified index to move to.
         * @param {Array} elements Array with dom elements to move focus within.
         * @return {Number} Input index or a changed index if it was out of range.
         */
        function moveFocusToIndex(idx, elements) {
            if (idx < 0) {
                idx = elements.length - 1;
            } else if (idx >= elements.length) {
                idx = 0;
            }

            if (elements[idx]) {
                elements[idx].focus();
            }

            return idx;
        }

        /**
         * Moves the focus forwards or backwards.
         *
         * @private
         * @param {Number} dir Direction to move in positive means forward, negative means backwards.
         * @param {Array} elements Optional array of elements to move within defaults to the current navigation roots elements.
         */
        function moveFocus(dir, elements) {
            var idx = -1, navigationRoot = getNavigationRoot();

            elements = elements || getFocusElements(navigationRoot.getEl());

            for (var i = 0; i < elements.length; i++) {
                if (elements[i] === focusedElement) {
                    idx = i;
                }
            }

            idx += dir;
            navigationRoot.lastAriaIndex = moveFocusToIndex(idx, elements);
        }

        /**
         * Moves the focus to the left this is called by the left key.
         *
         * @private
         */
        function left() {
            var parentRole = getParentRole();

            if (parentRole == "tablist") {
                moveFocus(-1, getFocusElements(focusedElement.parentNode));
            } else if (focusedControl.parent().submenu) {
                cancel();
            } else {
                moveFocus(-1);
            }
        }

        /**
         * Moves the focus to the right this is called by the right key.
         *
         * @private
         */
        function right() {
            var role = getRole(), parentRole = getParentRole();

            if (parentRole == "tablist") {
                moveFocus(1, getFocusElements(focusedElement.parentNode));
            } else if (role == "menuitem" && parentRole == "menu" && getAriaProp('haspopup')) {
                enter();
            } else {
                moveFocus(1);
            }
        }

        /**
         * Moves the focus to the up this is called by the up key.
         *
         * @private
         */
        function up() {
            moveFocus(-1);
        }

        /**
         * Moves the focus to the up this is called by the down key.
         *
         * @private
         */
        function down() {
            var role = getRole(), parentRole = getParentRole();

            if (role == "menuitem" && parentRole == "menubar") {
                enter();
            } else if (role == "button" && getAriaProp('haspopup')) {
                enter({key: 'down'});
            } else {
                moveFocus(1);
            }
        }

        /**
         * Moves the focus to the next item or previous item depending on shift key.
         *
         * @private
         * @param {DOMEvent} e DOM event object.
         */
        function tab(e) {
            var parentRole = getParentRole();

            if (parentRole == "tablist") {
                var elm = getFocusElements(focusedControl.getEl('body'))[0];

                if (elm) {
                    elm.focus();
                }
            } else {
                moveFocus(e.shiftKey ? -1 : 1);
            }
        }

        /**
         * Calls the cancel event on the currently focused control. This is normally done using the Esc key.
         *
         * @private
         */
        function cancel() {
            focusedControl.fire('cancel');
        }

        /**
         * Calls the click event on the currently focused control. This is normally done using the Enter/Space keys.
         *
         * @private
         * @param {Object} aria Optional aria data to pass along with the enter event.
         */
        function enter(aria) {
            aria = aria || {};
            focusedControl.fire('click', {target: focusedElement, aria: aria});
        }

        root.on('keydown', function(e) {
            function handleNonTabOrEscEvent(e, handler) {
                // Ignore non tab keys for text elements
                if (isTextInputElement(focusedElement)) {
                    return;
                }

                if (handler(e) !== false) {
                    e.preventDefault();
                }
            }

            if (e.isDefaultPrevented()) {
                return;
            }

            switch (e.keyCode) {
                case 37: // DOM_VK_LEFT
                    handleNonTabOrEscEvent(e, left);
                    break;

                case 39: // DOM_VK_RIGHT
                    handleNonTabOrEscEvent(e, right);
                    break;

                case 38: // DOM_VK_UP
                    handleNonTabOrEscEvent(e, up);
                    break;

                case 40: // DOM_VK_DOWN
                    handleNonTabOrEscEvent(e, down);
                    break;

                case 27: // DOM_VK_ESCAPE
                    cancel();
                    break;

                case 14: // DOM_VK_ENTER
                case 13: // DOM_VK_RETURN
                case 32: // DOM_VK_SPACE
                    handleNonTabOrEscEvent(e, enter);
                    break;

                case 9: // DOM_VK_TAB
                    if (tab(e) !== false) {
                        e.preventDefault();
                    }
                    break;
            }
        });

        root.on('focusin', function(e) {
            focusedElement = e.target;
            focusedControl = e.control;
        });

        return {
            focusFirst: focusFirst
        };
    };
});

// Included from: js/tinymce/classes/ui/Container.js

/**
 * Container.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Container control. This is extended by all controls that can have
 * children such as panels etc. You can also use this class directly as an
 * generic container instance. The container doesn't have any specific role or style.
 *
 * @-x-less Container.less
 * @class tinymce.ui.Container
 * @extends tinymce.ui.Control
 */
define("tinymce/ui/Container", [
    "tinymce/ui/Control",
    "tinymce/ui/Collection",
    "tinymce/ui/Selector",
    "tinymce/ui/Factory",
    "tinymce/ui/KeyboardNavigation",
    "tinymce/util/Tools",
    "tinymce/ui/DomUtils"
], function(Control, Collection, Selector, Factory, KeyboardNavigation, Tools, DomUtils) {
    "use strict";

    var selectorCache = {};

    return Control.extend({
        layout: '',
        innerClass: 'container-inner',

        /**
         * Constructs a new control instance with the specified settings.
         *
         * @constructor
         * @param {Object} settings Name/value object with settings.
         * @setting {Array} items Items to add to container in JSON format or control instances.
         * @setting {String} layout Layout manager by name to use.
         * @setting {Object} defaults Default settings to apply to all items.
         */
        init: function(settings) {
            var self = this;

            self._super(settings);
            settings = self.settings;
            self._fixed = settings.fixed;
            self._items = new Collection();

            if (self.isRtl()) {
                self.addClass('rtl');
            }

            self.addClass('container');
            self.addClass('container-body', 'body');

            if (settings.containerCls) {
                self.addClass(settings.containerCls);
            }

            self._layout = Factory.create((settings.layout || self.layout) + 'layout');

            if (self.settings.items) {
                self.add(self.settings.items);
            }

            // TODO: Fix this!
            self._hasBody = true;
        },

        /**
         * Returns a collection of child items that the container currently have.
         *
         * @method items
         * @return {tinymce.ui.Collection} Control collection direct child controls.
         */
        items: function() {
            return this._items;
        },

        /**
         * Find child controls by selector.
         *
         * @method find
         * @param {String} selector Selector CSS pattern to find children by.
         * @return {tinymce.ui.Collection} Control collection with child controls.
         */
        find: function(selector) {
            selector = selectorCache[selector] = selectorCache[selector] || new Selector(selector);

            return selector.find(this);
        },

        /**
         * Adds one or many items to the current container. This will create instances of
         * the object representations if needed.
         *
         * @method add
         * @param {Array/Object/tinymce.ui.Control} items Array or item that will be added to the container.
         * @return {tinymce.ui.Collection} Current collection control.
         */
        add: function(items) {
            var self = this;

            self.items().add(self.create(items)).parent(self);

            return self;
        },

        /**
         * Focuses the current container instance. This will look
         * for the first control in the container and focus that.
         *
         * @method focus
         * @param {Boolean} keyboard Optional true/false if the focus was a keyboard focus or not.
         * @return {tinymce.ui.Collection} Current instance.
         */
        focus: function(keyboard) {
            var self = this, focusCtrl, keyboardNav, items;

            if (keyboard) {
                keyboardNav = self.keyboardNav || self.parents().eq(-1)[0].keyboardNav;

                if (keyboardNav) {
                    keyboardNav.focusFirst(self);
                    return;
                }
            }

            items = self.find('*');

            // TODO: Figure out a better way to auto focus alert dialog buttons
            if (self.statusbar) {
                items.add(self.statusbar.items());
            }

            items.each(function(ctrl) {
                if (ctrl.settings.autofocus) {
                    focusCtrl = null;
                    return false;
                }

                if (ctrl.canFocus) {
                    focusCtrl = focusCtrl || ctrl;
                }
            });

            if (focusCtrl) {
                focusCtrl.focus();
            }

            return self;
        },

        /**
         * Replaces the specified child control with a new control.
         *
         * @method replace
         * @param {tinymce.ui.Control} oldItem Old item to be replaced.
         * @param {tinymce.ui.Control} newItem New item to be inserted.
         */
        replace: function(oldItem, newItem) {
            var ctrlElm, items = this.items(), i = items.length;

            // Replace the item in collection
            while (i--) {
                if (items[i] === oldItem) {
                    items[i] = newItem;
                    break;
                }
            }

            if (i >= 0) {
                // Remove new item from DOM
                ctrlElm = newItem.getEl();
                if (ctrlElm) {
                    ctrlElm.parentNode.removeChild(ctrlElm);
                }

                // Remove old item from DOM
                ctrlElm = oldItem.getEl();
                if (ctrlElm) {
                    ctrlElm.parentNode.removeChild(ctrlElm);
                }
            }

            // Adopt the item
            newItem.parent(this);
        },

        /**
         * Creates the specified items. If any of the items is plain JSON style objects
         * it will convert these into real tinymce.ui.Control instances.
         *
         * @method create
         * @param {Array} items Array of items to convert into control instances.
         * @return {Array} Array with control instances.
         */
        create: function(items) {
            var self = this, settings, ctrlItems = [];

            // Non array structure, then force it into an array
            if (!Tools.isArray(items)) {
                items = [items];
            }

            // Add default type to each child control
            Tools.each(items, function(item) {
                if (item) {
                    // Construct item if needed
                    if (!(item instanceof Control)) {
                        // Name only then convert it to an object
                        if (typeof(item) == "string") {
                            item = {type: item};
                        }

                        // Create control instance based on input settings and default settings
                        settings = Tools.extend({}, self.settings.defaults, item);
                        item.type = settings.type = settings.type || item.type || self.settings.defaultType ||
                            (settings.defaults ? settings.defaults.type : null);
                        item = Factory.create(settings);
                    }

                    ctrlItems.push(item);
                }
            });

            return ctrlItems;
        },

        /**
         * Renders new control instances.
         *
         * @private
         */
        renderNew: function() {
            var self = this;

            // Render any new items
            self.items().each(function(ctrl, index) {
                var containerElm, fragment;

                ctrl.parent(self);

                if (!ctrl._rendered) {
                    containerElm = self.getEl('body');
                    fragment = DomUtils.createFragment(ctrl.renderHtml());

                    // Insert or append the item
                    if (containerElm.hasChildNodes() && index <= containerElm.childNodes.length - 1) {
                        containerElm.insertBefore(fragment, containerElm.childNodes[index]);
                    } else {
                        containerElm.appendChild(fragment);
                    }

                    ctrl.postRender();
                }
            });

            self._layout.applyClasses(self);
            self._lastRect = null;

            return self;
        },

        /**
         * Appends new instances to the current container.
         *
         * @method append
         * @param {Array/tinymce.ui.Collection} items Array if controls to append.
         * @return {tinymce.ui.Container} Current container instance.
         */
        append: function(items) {
            return this.add(items).renderNew();
        },

        /**
         * Prepends new instances to the current container.
         *
         * @method prepend
         * @param {Array/tinymce.ui.Collection} items Array if controls to prepend.
         * @return {tinymce.ui.Container} Current container instance.
         */
        prepend: function(items) {
            var self = this;

            self.items().set(self.create(items).concat(self.items().toArray()));

            return self.renderNew();
        },

        /**
         * Inserts an control at a specific index.
         *
         * @method insert
         * @param {Array/tinymce.ui.Collection} items Array if controls to insert.
         * @param {Number} index Index to insert controls at.
         * @param {Boolean} [before=false] Inserts controls before the index.
         */
        insert: function(items, index, before) {
            var self = this, curItems, beforeItems, afterItems;

            items = self.create(items);
            curItems = self.items();

            if (!before && index < curItems.length - 1) {
                index += 1;
            }

            if (index >= 0 && index < curItems.length) {
                beforeItems = curItems.slice(0, index).toArray();
                afterItems = curItems.slice(index).toArray();
                curItems.set(beforeItems.concat(items, afterItems));
            }

            return self.renderNew();
        },

        /**
         * Populates the form fields from the specified JSON data object.
         *
         * Control items in the form that matches the data will have it's value set.
         *
         * @method fromJSON
         * @param {Object} data JSON data object to set control values by.
         * @return {tinymce.ui.Container} Current form instance.
         */
        fromJSON: function(data) {
            var self = this;

            for (var name in data) {
                self.find('#' + name).value(data[name]);
            }

            return self;
        },

        /**
         * Serializes the form into a JSON object by getting all items
         * that has a name and a value.
         *
         * @method toJSON
         * @return {Object} JSON object with form data.
         */
        toJSON: function() {
            var self = this, data = {};

            self.find('*').each(function(ctrl) {
                var name = ctrl.name(), value = ctrl.value();

                if (name && typeof(value) != "undefined") {
                    data[name] = value;
                }
            });

            return data;
        },

        preRender: function() {
        },

        /**
         * Renders the control as a HTML string.
         *
         * @method renderHtml
         * @return {String} HTML representing the control.
         */
        renderHtml: function() {
            var self = this, layout = self._layout, role = this.settings.role;

            self.preRender();
            layout.preRender(self);

            return (
                '<div id="' + self._id + '" class="' + self.classes() + '"' + (role ? ' role="' + this.settings.role + '"' : '') + '>' +
                    '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
                        (self.settings.html || '') + layout.renderHtml(self) +
                    '</div>' +
                '</div>'
            );
        },

        /**
         * Post render method. Called after the control has been rendered to the target.
         *
         * @method postRender
         * @return {tinymce.ui.Container} Current combobox instance.
         */
        postRender: function() {
            var self = this, box;

            self.items().exec('postRender');
            self._super();

            self._layout.postRender(self);
            self._rendered = true;

            if (self.settings.style) {
                DomUtils.css(self.getEl(), self.settings.style);
            }

            if (self.settings.border) {
                box = self.borderBox();
                DomUtils.css(self.getEl(), {
                    'border-top-width': box.top,
                    'border-right-width': box.right,
                    'border-bottom-width': box.bottom,
                    'border-left-width': box.left
                });
            }

            if (!self.parent()) {
                self.keyboardNav = new KeyboardNavigation({
                    root: self
                });
            }

            return self;
        },

        /**
         * Initializes the current controls layout rect.
         * This will be executed by the layout managers to determine the
         * default minWidth/minHeight etc.
         *
         * @method initLayoutRect
         * @return {Object} Layout rect instance.
         */
        initLayoutRect: function() {
            var self = this, layoutRect = self._super();

            // Recalc container size by asking layout manager
            self._layout.recalc(self);

            return layoutRect;
        },

        /**
         * Recalculates the positions of the controls in the current container.
         * This is invoked by the reflow method and shouldn't be called directly.
         *
         * @method recalc
         */
        recalc: function() {
            var self = this, rect = self._layoutRect, lastRect = self._lastRect;

            if (!lastRect || lastRect.w != rect.w || lastRect.h != rect.h) {
                self._layout.recalc(self);
                rect = self.layoutRect();
                self._lastRect = {x: rect.x, y: rect.y, w: rect.w, h: rect.h};
                return true;
            }
        },

        /**
         * Reflows the current container and it's children and possible parents.
         * This should be used after you for example append children to the current control so
         * that the layout managers know that they need to reposition everything.
         *
         * @example
         * container.append({type: 'button', text: 'My button'}).reflow();
         *
         * @method reflow
         * @return {tinymce.ui.Container} Current container instance.
         */
        reflow: function() {
            var i;

            if (this.visible()) {
                Control.repaintControls = [];
                Control.repaintControls.map = {};

                this.recalc();
                i = Control.repaintControls.length;

                while (i--) {
                    Control.repaintControls[i].repaint();
                }

                // TODO: Fix me!
                if (this.settings.layout !== "flow" && this.settings.layout !== "stack") {
                    this.repaint();
                }

                Control.repaintControls = [];
            }

            return this;
        }
    });
});

// Included from: js/tinymce/classes/ui/DragHelper.js

/**
 * DragHelper.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Drag/drop helper class.
 *
 * @example
 * var dragHelper = new tinymce.ui.DragHelper('mydiv', {
 *     start: function(evt) {
 *     },
 *
 *     drag: function(evt) {
 *     },
 *
 *     end: function(evt) {
 *     }
 * });
 *
 * @class tinymce.ui.DragHelper
 */
define("tinymce/ui/DragHelper", [
    "tinymce/ui/DomUtils"
], function(DomUtils) {
    "use strict";

    function getDocumentSize() {
        var doc = document, documentElement, body, scrollWidth, clientWidth;
        var offsetWidth, scrollHeight, clientHeight, offsetHeight, max = Math.max;

        documentElement = doc.documentElement;
        body = doc.body;

        scrollWidth = max(documentElement.scrollWidth, body.scrollWidth);
        clientWidth = max(documentElement.clientWidth, body.clientWidth);
        offsetWidth = max(documentElement.offsetWidth, body.offsetWidth);

        scrollHeight = max(documentElement.scrollHeight, body.scrollHeight);
        clientHeight = max(documentElement.clientHeight, body.clientHeight);
        offsetHeight = max(documentElement.offsetHeight, body.offsetHeight);

        return {
            width: scrollWidth < offsetWidth ? clientWidth : scrollWidth,
            height: scrollHeight < offsetHeight ? clientHeight : scrollHeight
        };
    }

    return function(id, settings) {
        var eventOverlayElm, doc = document, downButton, start, stop, drag, startX, startY;

        settings = settings || {};

        function getHandleElm() {
            return doc.getElementById(settings.handle || id);
        }

        start = function(e) {
            var docSize = getDocumentSize(), handleElm, cursor;

            e.preventDefault();
            downButton = e.button;
            handleElm = getHandleElm();
            startX = e.screenX;
            startY = e.screenY;

            // Grab cursor from handle
            if (window.getComputedStyle) {
                cursor = window.getComputedStyle(handleElm, null).getPropertyValue("cursor");
            } else {
                cursor = handleElm.runtimeStyle.cursor;
            }

            // Create event overlay and add it to document
            eventOverlayElm = doc.createElement('div');
            DomUtils.css(eventOverlayElm, {
                position: "absolute",
                top: 0, left: 0,
                width: docSize.width,
                height: docSize.height,
                zIndex: 0x7FFFFFFF,
                opacity: 0.0001,
                cursor: cursor
            });

            doc.body.appendChild(eventOverlayElm);

            // Bind mouse events
            DomUtils.on(doc, 'mousemove', drag);
            DomUtils.on(doc, 'mouseup', stop);

            // Begin drag
            settings.start(e);
        };

        drag = function(e) {
            if (e.button !== downButton) {
                return stop(e);
            }

            e.deltaX = e.screenX - startX;
            e.deltaY = e.screenY - startY;

            e.preventDefault();
            settings.drag(e);
        };

        stop = function(e) {
            DomUtils.off(doc, 'mousemove', drag);
            DomUtils.off(doc, 'mouseup', stop);

            eventOverlayElm.parentNode.removeChild(eventOverlayElm);

            if (settings.stop) {
                settings.stop(e);
            }
        };

        /**
         * Destroys the drag/drop helper instance.
         *
         * @method destroy
         */
        this.destroy = function() {
            DomUtils.off(getHandleElm());
        };

        DomUtils.on(getHandleElm(), 'mousedown', start);
    };
});

// Included from: js/tinymce/classes/ui/Scrollable.js

/**
 * Scrollable.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This mixin makes controls scrollable using custom scrollbars.
 *
 * @-x-less Scrollable.less
 * @mixin tinymce.ui.Scrollable
 */
define("tinymce/ui/Scrollable", [
    "tinymce/ui/DomUtils",
    "tinymce/ui/DragHelper"
], function(DomUtils, DragHelper) {
    "use strict";

    return {
        init: function() {
            var self = this;
            self.on('repaint', self.renderScroll);
        },

        renderScroll: function() {
            var self = this, margin = 2;

            function repaintScroll() {
                var hasScrollH, hasScrollV, bodyElm;

                function repaintAxis(axisName, posName, sizeName, contentSizeName, hasScroll, ax) {
                    var containerElm, scrollBarElm, scrollThumbElm;
                    var containerSize, scrollSize, ratio, rect;
                    var posNameLower, sizeNameLower;

                    scrollBarElm = self.getEl('scroll' + axisName);
                    if (scrollBarElm) {
                        posNameLower = posName.toLowerCase();
                        sizeNameLower = sizeName.toLowerCase();

                        if (self.getEl('absend')) {
                            DomUtils.css(self.getEl('absend'), posNameLower, self.layoutRect()[contentSizeName] - 1);
                        }

                        if (!hasScroll) {
                            DomUtils.css(scrollBarElm, 'display', 'none');
                            return;
                        }

                        DomUtils.css(scrollBarElm, 'display', 'block');
                        containerElm = self.getEl('body');
                        scrollThumbElm = self.getEl('scroll' + axisName + "t");
                        containerSize = containerElm["client" + sizeName] - (margin * 2);
                        containerSize -= hasScrollH && hasScrollV ? scrollBarElm["client" + ax] : 0;
                        scrollSize = containerElm["scroll" + sizeName];
                        ratio = containerSize / scrollSize;

                        rect = {};
                        rect[posNameLower] = containerElm["offset" + posName] + margin;
                        rect[sizeNameLower] = containerSize;
                        DomUtils.css(scrollBarElm, rect);

                        rect = {};
                        rect[posNameLower] = containerElm["scroll" + posName] * ratio;
                        rect[sizeNameLower] = containerSize * ratio;
                        DomUtils.css(scrollThumbElm, rect);
                    }
                }

                bodyElm = self.getEl('body');
                hasScrollH = bodyElm.scrollWidth > bodyElm.clientWidth;
                hasScrollV = bodyElm.scrollHeight > bodyElm.clientHeight;

                repaintAxis("h", "Left", "Width", "contentW", hasScrollH, "Height");
                repaintAxis("v", "Top", "Height", "contentH", hasScrollV, "Width");
            }

            function addScroll() {
                function addScrollAxis(axisName, posName, sizeName, deltaPosName, ax) {
                    var scrollStart, axisId = self._id + '-scroll' + axisName, prefix = self.classPrefix;

                    self.getEl().appendChild(DomUtils.createFragment(
                        '<div id="' + axisId + '" class="' + prefix + 'scrollbar ' + prefix + 'scrollbar-' + axisName + '">' +
                            '<div id="' + axisId + 't" class="' + prefix + 'scrollbar-thumb"></div>' +
                        '</div>'
                    ));

                    self.draghelper = new DragHelper(axisId + 't', {
                        start: function() {
                            scrollStart = self.getEl('body')["scroll" + posName];
                            DomUtils.addClass(DomUtils.get(axisId), prefix + 'active');
                        },

                        drag: function(e) {
                            var ratio, hasScrollH, hasScrollV, containerSize, layoutRect = self.layoutRect();

                            hasScrollH = layoutRect.contentW > layoutRect.innerW;
                            hasScrollV = layoutRect.contentH > layoutRect.innerH;
                            containerSize = self.getEl('body')["client" + sizeName] - (margin * 2);
                            containerSize -= hasScrollH && hasScrollV ? self.getEl('scroll' + axisName)["client" + ax] : 0;

                            ratio = containerSize / self.getEl('body')["scroll" + sizeName];
                            self.getEl('body')["scroll" + posName] = scrollStart + (e["delta" + deltaPosName] / ratio);
                        },

                        stop: function() {
                            DomUtils.removeClass(DomUtils.get(axisId), prefix + 'active');
                        }
                    });
/*
                    self.on('click', function(e) {
                        if (e.target.id == self._id + '-scrollv') {

                        }
                    });*/
                }

                self.addClass('scroll');

                addScrollAxis("v", "Top", "Height", "Y", "Width");
                addScrollAxis("h", "Left", "Width", "X", "Height");
            }

            if (self.settings.autoScroll) {
                if (!self._hasScroll) {
                    self._hasScroll = true;
                    addScroll();

                    self.on('wheel', function(e) {
                        var bodyEl = self.getEl('body');

                        bodyEl.scrollLeft += (e.deltaX || 0) * 10;
                        bodyEl.scrollTop += e.deltaY * 10;

                        repaintScroll();
                    });

                    DomUtils.on(self.getEl('body'), "scroll", repaintScroll);
                }

                repaintScroll();
            }
        }
    };
});

// Included from: js/tinymce/classes/ui/Panel.js

/**
 * Panel.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Creates a new panel.
 *
 * @-x-less Panel.less
 * @class tinymce.ui.Panel
 * @extends tinymce.ui.Container
 * @mixes tinymce.ui.Scrollable
 */
define("tinymce/ui/Panel", [
    "tinymce/ui/Container",
    "tinymce/ui/Scrollable"
], function(Container, Scrollable) {
    "use strict";

    return Container.extend({
        Defaults: {
            layout: 'fit',
            containerCls: 'panel'
        },

        Mixins: [Scrollable],

        /**
         * Renders the control as a HTML string.
         *
         * @method renderHtml
         * @return {String} HTML representing the control.
         */
        renderHtml: function() {
            var self = this, layout = self._layout, innerHtml = self.settings.html;

            self.preRender();
            layout.preRender(self);

            if (typeof(innerHtml) == "undefined") {
                innerHtml = (
                    '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
                        layout.renderHtml(self) +
                    '</div>'
                );
            } else {
                if (typeof(innerHtml) == 'function') {
                    innerHtml = innerHtml.call(self);
                }

                self._hasBody = false;
            }

            return (
                '<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1" role="group">' +
                    (self._preBodyHtml || '') +
                    innerHtml +
                '</div>'
            );
        }
    });
});

// Included from: js/tinymce/classes/ui/Movable.js

/**
 * Movable.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Movable mixin. Makes controls movable absolute and relative to other elements.
 *
 * @mixin tinymce.ui.Movable
 */
define("tinymce/ui/Movable", [
    "tinymce/ui/DomUtils"
], function(DomUtils) {
    "use strict";

    function calculateRelativePosition(ctrl, targetElm, rel) {
        var ctrlElm, pos, x, y, selfW, selfH, targetW, targetH, viewport, size;

        viewport = DomUtils.getViewPort();

        // Get pos of target
        pos = DomUtils.getPos(targetElm);
        x = pos.x;
        y = pos.y;

        if (ctrl._fixed) {
            x -= viewport.x;
            y -= viewport.y;
        }

        // Get size of self
        ctrlElm = ctrl.getEl();
        size = DomUtils.getSize(ctrlElm);
        selfW = size.width;
        selfH = size.height;

        // Get size of target
        size = DomUtils.getSize(targetElm);
        targetW = size.width;
        targetH = size.height;

        // Parse align string
        rel = (rel || '').split('');

        // Target corners
        if (rel[0] === 'b') {
            y += targetH;
        }

        if (rel[1] === 'r') {
            x += targetW;
        }

        if (rel[0] === 'c') {
            y += Math.round(targetH / 2);
        }

        if (rel[1] === 'c') {
            x += Math.round(targetW / 2);
        }

        // Self corners
        if (rel[3] === 'b') {
            y -= selfH;
        }

        if (rel[4] === 'r') {
            x -= selfW;
        }

        if (rel[3] === 'c') {
            y -= Math.round(selfH / 2);
        }

        if (rel[4] === 'c') {
            x -= Math.round(selfW / 2);
        }

        return {
            x: x,
            y: y,
            w: selfW,
            h: selfH
        };
    }

    return {
        /**
         * Tests various positions to get the most suitable one.
         *
         * @method testMoveRel
         * @param {DOMElement} elm Element to position against.
         * @param {Array} rels Array with relative positions.
         * @return {String} Best suitable relative position.
         */
        testMoveRel: function(elm, rels) {
            var viewPortRect = DomUtils.getViewPort();

            for (var i = 0; i < rels.length; i++) {
                var pos = calculateRelativePosition(this, elm, rels[i]);

                if (this._fixed) {
                    if (pos.x > 0 && pos.x + pos.w < viewPortRect.w && pos.y > 0 && pos.y + pos.h < viewPortRect.h) {
                        return rels[i];
                    }
                } else {
                    if (pos.x > viewPortRect.x && pos.x + pos.w < viewPortRect.w + viewPortRect.x &&
                        pos.y > viewPortRect.y && pos.y + pos.h < viewPortRect.h + viewPortRect.y) {
                        return rels[i];
                    }
                }
            }

            return rels[0];
        },

        /**
         * Move relative to the specified element.
         *
         * @method moveRel
         * @param {Element} elm Element to move relative to.
         * @param {String} rel Relative mode. For example: br-tl.
         * @return {tinymce.ui.Control} Current control instance.
         */
        moveRel: function(elm, rel) {
            if (typeof(rel) != 'string') {
                rel = this.testMoveRel(elm, rel);
            }

            var pos = calculateRelativePosition(this, elm, rel);
            return this.moveTo(pos.x, pos.y);
        },

        /**
         * Move by a relative x, y values.
         *
         * @method moveBy
         * @param {Number} dx Relative x position.
         * @param {Number} dy Relative y position.
         * @return {tinymce.ui.Control} Current control instance.
         */
        moveBy: function(dx, dy) {
            var self = this, rect = self.layoutRect();

            self.moveTo(rect.x + dx, rect.y + dy);

            return self;
        },

        /**
         * Move to absolute position.
         *
         * @method moveTo
         * @param {Number} x Absolute x position.
         * @param {Number} y Absolute y position.
         * @return {tinymce.ui.Control} Current control instance.
         */
        moveTo: function(x, y) {
            var self = this;

            // TODO: Move this to some global class
            function contrain(value, max, size) {
                if (value < 0) {
                    return 0;
                }

                if (value + size > max) {
                    value = max - size;
                    return value < 0 ? 0 : value;
                }

                return value;
            }

            if (self.settings.constrainToViewport) {
                var viewPortRect = DomUtils.getViewPort(window);
                var layoutRect = self.layoutRect();

                x = contrain(x, viewPortRect.w + viewPortRect.x, layoutRect.w);
                y = contrain(y, viewPortRect.h + viewPortRect.y, layoutRect.h);
            }

            if (self._rendered) {
                self.layoutRect({x: x, y: y}).repaint();
            } else {
                self.settings.x = x;
                self.settings.y = y;
            }

            self.fire('move', {x: x, y: y});

            return self;
        }
    };
});

// Included from: js/tinymce/classes/ui/Resizable.js

/**
 * Resizable.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Resizable mixin. Enables controls to be resized.
 *
 * @mixin tinymce.ui.Resizable
 */
define("tinymce/ui/Resizable", [
    "tinymce/ui/DomUtils"
], function(DomUtils) {
    "use strict";

    return {
        /**
         * Resizes the control to contents.
         *
         * @method resizeToContent
         */
        resizeToContent: function() {
            this._layoutRect.autoResize = true;
            this._lastRect = null;
            this.reflow();
        },

        /**
         * Resizes the control to a specific width/height.
         *
         * @method resizeTo
         * @param {Number} w Control width.
         * @param {Number} h Control height.
         * @return {tinymce.ui.Control} Current control instance.
         */
        resizeTo: function(w, h) {
            // TODO: Fix hack
            if (w <= 1 || h <= 1) {
                var rect = DomUtils.getWindowSize();

                w = w <= 1 ? w * rect.w : w;
                h = h <= 1 ? h * rect.h : h;
            }

            this._layoutRect.autoResize = false;
            return this.layoutRect({minW: w, minH: h, w: w, h: h}).reflow();
        },

        /**
         * Resizes the control to a specific relative width/height.
         *
         * @method resizeBy
         * @param {Number} dw Relative control width.
         * @param {Number} dh Relative control height.
         * @return {tinymce.ui.Control} Current control instance.
         */
        resizeBy: function(dw, dh) {
            var self = this, rect = self.layoutRect();

            return self.resizeTo(rect.w + dw, rect.h + dh);
        }
    };
});

// Included from: js/tinymce/classes/ui/FloatPanel.js

/**
 * FloatPanel.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class creates a floating panel.
 *
 * @-x-less FloatPanel.less
 * @class tinymce.ui.FloatPanel
 * @extends tinymce.ui.Panel
 * @mixes tinymce.ui.Movable
 * @mixes tinymce.ui.Resizable
 */
define("tinymce/ui/FloatPanel", [
    "tinymce/ui/Panel",
    "tinymce/ui/Movable",
    "tinymce/ui/Resizable",
    "tinymce/ui/DomUtils"
], function(Panel, Movable, Resizable, DomUtils) {
    "use strict";

    var documentClickHandler, documentScrollHandler, windowResizeHandler, visiblePanels = [];
    var zOrder = [], hasModal;

    function bindDocumentClickHandler() {
        function isChildOf(ctrl, parent) {
            while (ctrl) {
                if (ctrl == parent) {
                    return true;
                }

                ctrl = ctrl.parent();
            }
        }

        if (!documentClickHandler) {
            documentClickHandler = function(e) {
                // Gecko fires click event and in the wrong order on Mac so lets normalize
                if (e.button == 2) {
                    return;
                }

                // Hide any float panel when a click is out side that float panel and the
                // float panels direct parent for example a click on a menu button
                var i = visiblePanels.length;
                while (i--) {
                    var panel = visiblePanels[i], clickCtrl = panel.getParentCtrl(e.target);

                    if (panel.settings.autohide) {
                        if (clickCtrl) {
                            if (isChildOf(clickCtrl, panel) || panel.parent() === clickCtrl) {
                                continue;
                            }
                        }

                        e = panel.fire('autohide', {target: e.target});
                        if (!e.isDefaultPrevented()) {
                            panel.hide();
                        }
                    }
                }
            };

            DomUtils.on(document, 'click', documentClickHandler);
        }
    }

    function bindDocumentScrollHandler() {
        if (!documentScrollHandler) {
            documentScrollHandler = function() {
                var i;

                i = visiblePanels.length;
                while (i--) {
                    repositionPanel(visiblePanels[i]);
                }
            };

            DomUtils.on(window, 'scroll', documentScrollHandler);
        }
    }

    function bindWindowResizeHandler() {
        if (!windowResizeHandler) {
            windowResizeHandler = function() {
                FloatPanel.hideAll();
            };

            DomUtils.on(window, 'resize', windowResizeHandler);
        }
    }

    /**
     * Repositions the panel to the top of page if the panel is outside of the visual viewport. It will
     * also reposition all child panels of the current panel.
     */
    function repositionPanel(panel) {
        var scrollY = DomUtils.getViewPort().y;

        function toggleFixedChildPanels(fixed, deltaY) {
            var parent;

            for (var i = 0; i < visiblePanels.length; i++) {
                if (visiblePanels[i] != panel) {
                    parent = visiblePanels[i].parent();

                    while (parent && (parent = parent.parent())) {
                        if (parent == panel) {
                            visiblePanels[i].fixed(fixed).moveBy(0, deltaY).repaint();
                        }
                    }
                }
            }
        }

        if (panel.settings.autofix) {
            if (!panel._fixed) {
                panel._autoFixY = panel.layoutRect().y;

                if (panel._autoFixY < scrollY) {
                    panel.fixed(true).layoutRect({y: 0}).repaint();
                    toggleFixedChildPanels(true, scrollY - panel._autoFixY);
                }
            } else {
                if (panel._autoFixY > scrollY) {
                    panel.fixed(false).layoutRect({y: panel._autoFixY}).repaint();
                    toggleFixedChildPanels(false, panel._autoFixY - scrollY);
                }
            }
        }
    }

    var FloatPanel = Panel.extend({
        Mixins: [Movable, Resizable],

        /**
         * Constructs a new control instance with the specified settings.
         *
         * @constructor
         * @param {Object} settings Name/value object with settings.
         * @setting {Boolean} autohide Automatically hide the panel.
         */
        init: function(settings) {
            var self = this;

            function reorder() {
                var i, zIndex = FloatPanel.zIndex || 0xFFFF, topModal;

                if (zOrder.length) {
                    for (i = 0; i < zOrder.length; i++) {
                        if (zOrder[i].modal) {
                            zIndex++;
                            topModal = zOrder[i];
                        }

                        zOrder[i].getEl().style.zIndex = zIndex;
                        zOrder[i].zIndex = zIndex;
                        zIndex++;
                    }
                }

                var modalBlockEl = document.getElementById(self.classPrefix + 'modal-block');

                if (topModal) {
                    DomUtils.css(modalBlockEl, 'z-index', topModal.zIndex - 1);
                } else if (modalBlockEl) {
                    modalBlockEl.parentNode.removeChild(modalBlockEl);
                    hasModal = false;
                }

                FloatPanel.currentZIndex = zIndex;
            }

            self._super(settings);
            self._eventsRoot = self;

            self.addClass('floatpanel');

            // Hide floatpanes on click out side the root button
            if (settings.autohide) {
                bindDocumentClickHandler();
                bindWindowResizeHandler();
                visiblePanels.push(self);
            }

            if (settings.autofix) {
                bindDocumentScrollHandler();

                self.on('move', function() {
                    repositionPanel(this);
                });
            }

            self.on('postrender show', function(e) {
                if (e.control == self) {
                    var modalBlockEl, prefix = self.classPrefix;

                    if (self.modal && !hasModal) {
                        modalBlockEl = DomUtils.createFragment('<div id="' + prefix + 'modal-block" class="' +
                            prefix + 'reset ' + prefix + 'fade"></div>');
                        modalBlockEl = modalBlockEl.firstChild;

                        self.getContainerElm().appendChild(modalBlockEl);

                        setTimeout(function() {
                            DomUtils.addClass(modalBlockEl, prefix + 'in');
                            DomUtils.addClass(self.getEl(), prefix + 'in');
                        }, 0);

                        hasModal = true;
                    }

                    zOrder.push(self);
                    reorder();
                }
            });

            self.on('close hide', function(e) {
                if (e.control == self) {
                    var i = zOrder.length;

                    while (i--) {
                        if (zOrder[i] === self) {
                            zOrder.splice(i, 1);
                        }
                    }

                    reorder();
                }
            });

            self.on('show', function() {
                self.parents().each(function(ctrl) {
                    if (ctrl._fixed) {
                        self.fixed(true);
                        return false;
                    }
                });
            });

            if (settings.popover) {
                self._preBodyHtml = '<div class="' + self.classPrefix + 'arrow"></div>';
                self.addClass('popover').addClass('bottom').addClass(self.isRtl() ? 'end' : 'start');
            }
        },

        fixed: function(state) {
            var self = this;

            if (self._fixed != state) {
                if (self._rendered) {
                    var viewport = DomUtils.getViewPort();

                    if (state) {
                        self.layoutRect().y -= viewport.y;
                    } else {
                        self.layoutRect().y += viewport.y;
                    }
                }

                self.toggleClass('fixed', state);
                self._fixed = state;
            }

            return self;
        },

        /**
         * Shows the current float panel.
         *
         * @method show
         * @return {tinymce.ui.FloatPanel} Current floatpanel instance.
         */
        show: function() {
            var self = this, i, state = self._super();

            i = visiblePanels.length;
            while (i--) {
                if (visiblePanels[i] === self) {
                    break;
                }
            }

            if (i === -1) {
                visiblePanels.push(self);
            }

            return state;
        },

        /**
         * Hides the current float panel.
         *
         * @method hide
         * @return {tinymce.ui.FloatPanel} Current floatpanel instance.
         */
        hide: function() {
            removeVisiblePanel(this);
            return this._super();
        },

        /**
         * Hide all visible float panels with he autohide setting enabled. This is for
         * manually hiding floating menus or panels.
         *
         * @method hideAll
         */
        hideAll: function() {
            FloatPanel.hideAll();
        },

        /**
         * Closes the float panel. This will remove the float panel from page and fire the close event.
         *
         * @method close
         */
        close: function() {
            var self = this;

            self.fire('close');

            return self.remove();
        },

        /**
         * Removes the float panel from page.
         *
         * @method remove
         */
        remove: function() {
            removeVisiblePanel(this);
            this._super();
        },

        postRender: function() {
            var self = this;

            if (self.settings.bodyRole) {
                this.getEl('body').setAttribute('role', self.settings.bodyRole);
            }

            return self._super();
        }
    });

    /**
     * Hide all visible float panels with he autohide setting enabled. This is for
     * manually hiding floating menus or panels.
     *
     * @static
     * @method hideAll
     */
    FloatPanel.hideAll = function() {
        var i = visiblePanels.length;

        while (i--) {
            var panel = visiblePanels[i];

            if (panel && panel.settings.autohide) {
                panel.hide();
                visiblePanels.splice(i, 1);
            }
        }
    };

    function removeVisiblePanel(panel) {
        var i;

        i = visiblePanels.length;
        while (i--) {
            if (visiblePanels[i] === panel) {
                visiblePanels.splice(i, 1);
            }
        }

        i = zOrder.length;
        while (i--) {
            if (zOrder[i] === panel) {
                zOrder.splice(i, 1);
            }
        }
    }

    return FloatPanel;
});

// Included from: js/tinymce/classes/ui/Window.js

/**
 * Window.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Creates a new window.
 *
 * @-x-less Window.less
 * @class tinymce.ui.Window
 * @extends tinymce.ui.FloatPanel
 */
define("tinymce/ui/Window", [
    "tinymce/ui/FloatPanel",
    "tinymce/ui/Panel",
    "tinymce/ui/DomUtils",
    "tinymce/ui/DragHelper"
], function(FloatPanel, Panel, DomUtils, DragHelper) {
    "use strict";

    var Window = FloatPanel.extend({
        modal: true,

        Defaults: {
            border: 1,
            layout: 'flex',
            containerCls: 'panel',
            role: 'dialog',
            callbacks: {
                submit: function() {
                    this.fire('submit', {data: this.toJSON()});
                },

                close: function() {
                    this.close();
                }
            }
        },

        /**
         * Constructs a instance with the specified settings.
         *
         * @constructor
         * @param {Object} settings Name/value object with settings.
         */
        init: function(settings) {
            var self = this;

            self._super(settings);

            if (self.isRtl()) {
                self.addClass('rtl');
            }

            self.addClass('window');
            self._fixed = true;

            // Create statusbar
            if (settings.buttons) {
                self.statusbar = new Panel({
                    layout: 'flex',
                    border: '1 0 0 0',
                    spacing: 3,
                    padding: 10,
                    align: 'center',
                    pack: self.isRtl() ? 'start' : 'end',
                    defaults: {
                        type: 'button'
                    },
                    items: settings.buttons
                });

                self.statusbar.addClass('foot');
                self.statusbar.parent(self);
            }

            self.on('click', function(e) {
                if (e.target.className.indexOf(self.classPrefix + 'close') != -1) {
                    self.close();
                }
            });

            self.on('cancel', function() {
                self.close();
            });

            self.aria('describedby', self.describedBy || self._id + '-none');
            self.aria('label', settings.title);
            self._fullscreen = false;
        },

        /**
         * Recalculates the positions of the controls in the current container.
         * This is invoked by the reflow method and shouldn't be called directly.
         *
         * @method recalc
         */
        recalc: function() {
            var self = this, statusbar = self.statusbar, layoutRect, width, x, needsRecalc;

            if (self._fullscreen) {
                self.layoutRect(DomUtils.getWindowSize());
                self.layoutRect().contentH = self.layoutRect().innerH;
            }

            self._super();

            layoutRect = self.layoutRect();

            // Resize window based on title width
            if (self.settings.title && !self._fullscreen) {
                width = layoutRect.headerW;
                if (width > layoutRect.w) {
                    x = layoutRect.x - Math.max(0, width / 2);
                    self.layoutRect({w: width, x: x});
                    needsRecalc = true;
                }
            }

            // Resize window based on statusbar width
            if (statusbar) {
                statusbar.layoutRect({w: self.layoutRect().innerW}).recalc();

                width = statusbar.layoutRect().minW + layoutRect.deltaW;
                if (width > layoutRect.w) {
                    x = layoutRect.x - Math.max(0, width - layoutRect.w);
                    self.layoutRect({w: width, x: x});
                    needsRecalc = true;
                }
            }

            // Recalc body and disable auto resize
            if (needsRecalc) {
                self.recalc();
            }
        },

        /**
         * Initializes the current controls layout rect.
         * This will be executed by the layout managers to determine the
         * default minWidth/minHeight etc.
         *
         * @method initLayoutRect
         * @return {Object} Layout rect instance.
         */
        initLayoutRect: function() {
            var self = this, layoutRect = self._super(), deltaH = 0, headEl;

            // Reserve vertical space for title
            if (self.settings.title && !self._fullscreen) {
                headEl = self.getEl('head');

                var size = DomUtils.getSize(headEl);

                layoutRect.headerW = size.width;
                layoutRect.headerH = size.height;

                deltaH += layoutRect.headerH;
            }

            // Reserve vertical space for statusbar
            if (self.statusbar) {
                deltaH += self.statusbar.layoutRect().h;
            }

            layoutRect.deltaH += deltaH;
            layoutRect.minH += deltaH;
            //layoutRect.innerH -= deltaH;
            layoutRect.h += deltaH;

            var rect = DomUtils.getWindowSize();

            layoutRect.x = Math.max(0, rect.w / 2 - layoutRect.w / 2);
            layoutRect.y = Math.max(0, rect.h / 2 - layoutRect.h / 2);

            return layoutRect;
        },

        /**
         * Renders the control as a HTML string.
         *
         * @method renderHtml
         * @return {String} HTML representing the control.
         */
        renderHtml: function() {
            var self = this, layout = self._layout, id = self._id, prefix = self.classPrefix;
            var settings = self.settings, headerHtml = '', footerHtml = '', html = settings.html;

            self.preRender();
            layout.preRender(self);

            if (settings.title) {
                headerHtml = (
                    '<div id="' + id + '-head" class="' + prefix + 'window-head">' +
                        '<div id="' + id + '-title" class="' + prefix + 'title">' + self.encode(settings.title) + '</div>' +
                        '<button type="button" class="' + prefix + 'close" aria-hidden="true">\u00d7</button>' +
                        '<div id="' + id + '-dragh" class="' + prefix + 'dragh"></div>' +
                    '</div>'
                );
            }

            if (settings.url) {
                html = '<iframe src="' + settings.url + '" tabindex="-1"></iframe>';
            }

            if (typeof(html) == "undefined") {
                html = layout.renderHtml(self);
            }

            if (self.statusbar) {
                footerHtml = self.statusbar.renderHtml();
            }

            return (
                '<div id="' + id + '" class="' + self.classes() + '" hidefocus="1">' +
                    '<div class="' + self.classPrefix + 'reset" role="application">' +
                        headerHtml +
                        '<div id="' + id + '-body" class="' + self.classes('body') + '">' +
                            html +
                        '</div>' +
                        footerHtml +
                    '</div>' +
                '</div>'
            );
        },

        /**
         * Switches the window fullscreen mode.
         *
         * @method fullscreen
         * @param {Boolean} state True/false state.
         * @return {tinymce.ui.Window} Current window instance.
         */
        fullscreen: function(state) {
            var self = this, documentElement = document.documentElement, slowRendering, prefix = self.classPrefix, layoutRect;

            if (state != self._fullscreen) {
                DomUtils.on(window, 'resize', function() {
                    var time;

                    if (self._fullscreen) {
                        // Time the layout time if it's to slow use a timeout to not hog the CPU
                        if (!slowRendering) {
                            time = new Date().getTime();

                            var rect = DomUtils.getWindowSize();
                            self.moveTo(0, 0).resizeTo(rect.w, rect.h);

                            if ((new Date().getTime()) - time > 50) {
                                slowRendering = true;
                            }
                        } else {
                            if (!self._timer) {
                                self._timer = setTimeout(function() {
                                    var rect = DomUtils.getWindowSize();
                                    self.moveTo(0, 0).resizeTo(rect.w, rect.h);

                                    self._timer = 0;
                                }, 50);
                            }
                        }
                    }
                });

                layoutRect = self.layoutRect();
                self._fullscreen = state;

                if (!state) {
                    self._borderBox = self.parseBox(self.settings.border);
                    self.getEl('head').style.display = '';
                    layoutRect.deltaH += layoutRect.headerH;
                    DomUtils.removeClass(documentElement, prefix + 'fullscreen');
                    DomUtils.removeClass(document.body, prefix + 'fullscreen');
                    self.removeClass('fullscreen');
                    self.moveTo(self._initial.x, self._initial.y).resizeTo(self._initial.w, self._initial.h);
                } else {
                    self._initial = {x: layoutRect.x, y: layoutRect.y, w: layoutRect.w, h: layoutRect.h};

                    self._borderBox = self.parseBox('0');
                    self.getEl('head').style.display = 'none';
                    layoutRect.deltaH -= layoutRect.headerH + 2;
                    DomUtils.addClass(documentElement, prefix + 'fullscreen');
                    DomUtils.addClass(document.body, prefix + 'fullscreen');
                    self.addClass('fullscreen');

                    var rect = DomUtils.getWindowSize();
                    self.moveTo(0, 0).resizeTo(rect.w, rect.h);
                }
            }

            return self.reflow();
        },

        /**
         * Called after the control has been rendered.
         *
         * @method postRender
         */
        postRender: function() {
            var self = this, startPos;

            setTimeout(function() {
                self.addClass('in');
            }, 0);

            self._super();

            if (self.statusbar) {
                self.statusbar.postRender();
            }

            self.focus();

            this.dragHelper = new DragHelper(self._id + '-dragh', {
                start: function() {
                    startPos = {
                        x: self.layoutRect().x,
                        y: self.layoutRect().y
                    };
                },

                drag: function(e) {
                    self.moveTo(startPos.x + e.deltaX, startPos.y + e.deltaY);
                }
            });

            self.on('submit', function(e) {
                if (!e.isDefaultPrevented()) {
                    self.close();
                }
            });
        },

        /**
         * Fires a submit event with the serialized form.
         *
         * @method submit
         * @return {Object} Event arguments object.
         */
        submit: function() {
            return this.fire('submit', {data: this.toJSON()});
        },

        /**
         * Removes the current control from DOM and from UI collections.
         *
         * @method remove
         * @return {tinymce.ui.Control} Current control instance.
         */
        remove: function() {
            var self = this, prefix = self.classPrefix;

            self.dragHelper.destroy();
            self._super();

            if (self.statusbar) {
                this.statusbar.remove();
            }

            if (self._fullscreen) {
                DomUtils.removeClass(document.documentElement, prefix + 'fullscreen');
                DomUtils.removeClass(document.body, prefix + 'fullscreen');
            }
        },

        /**
         * Returns the contentWindow object of the iframe if it exists.
         *
         * @method getContentWindow
         * @return {Window} window object or null.
         */
        getContentWindow: function() {
            var ifr = this.getEl().getElementsByTagName('iframe')[0];
            return ifr ? ifr.contentWindow : null;
        }
    });

    return Window;
});

// Included from: js/tinymce/classes/ui/MessageBox.js

/**
 * MessageBox.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class is used to create MessageBoxes like alerts/confirms etc.
 *
 * @class tinymce.ui.MessageBox
 * @extends tinymce.ui.Window
 */
define("tinymce/ui/MessageBox", [
    "tinymce/ui/Window"
], function(Window) {
    "use strict";

    var MessageBox = Window.extend({
        /**
         * Constructs a instance with the specified settings.
         *
         * @constructor
         * @param {Object} settings Name/value object with settings.
         */
        init: function(settings) {
            settings = {
                border: 1,
                padding: 20,
                layout: 'flex',
                pack: "center",
                align: "center",
                containerCls: 'panel',
                autoScroll: true,
                buttons: {type: "button", text: "Ok", action: "ok"},
                items: {
                    type: "label",
                    multiline: true,
                    maxWidth: 500,
                    maxHeight: 200
                }
            };

            this._super(settings);
        },

        Statics: {
            /**
             * Ok buttons constant.
             *
             * @static
             * @final
             * @field {Number} OK
             */
            OK: 1,

            /**
             * Ok/cancel buttons constant.
             *
             * @static
             * @final
             * @field {Number} OK_CANCEL
             */
            OK_CANCEL: 2,

            /**
             * yes/no buttons constant.
             *
             * @static
             * @final
             * @field {Number} YES_NO
             */
            YES_NO: 3,

            /**
             * yes/no/cancel buttons constant.
             *
             * @static
             * @final
             * @field {Number} YES_NO_CANCEL
             */
            YES_NO_CANCEL: 4,

            /**
             * Constructs a new message box and renders it to the body element.
             *
             * @static
             * @method msgBox
             * @param {Object} settings Name/value object with settings.
             */
            msgBox: function(settings) {
                var buttons, callback = settings.callback || function() {};

                function createButton(text, status, primary) {
                    return {
                        type: "button",
                        text: text,
                        subtype: primary ? 'primary' : '',
                        onClick: function(e) {
                            e.control.parents()[1].close();
                            callback(status);
                        }
                    };
                }

                switch (settings.buttons) {
                    case MessageBox.OK_CANCEL:
                        buttons = [
                            createButton('Ok', true, true),
                            createButton('Cancel', false)
                        ];
                        break;

                    case MessageBox.YES_NO:
                    case MessageBox.YES_NO_CANCEL:
                        buttons = [
                            createButton('Yes', 1, true),
                            createButton('No', 0)
                        ];

                        if (settings.buttons == MessageBox.YES_NO_CANCEL) {
                            buttons.push(createButton('Cancel', -1));
                        }
                        break;

                    default:
                        buttons = [
                            createButton('Ok', true, true)
                        ];
                        break;
                }

                return new Window({
                    padding: 20,
                    x: settings.x,
                    y: settings.y,
                    minWidth: 300,
                    minHeight: 100,
                    layout: "flex",
                    pack: "center",
                    align: "center",
                    buttons: buttons,
                    title: settings.title,
                    role: 'alertdialog',
                    items: {
                        type: "label",
                        multiline: true,
                        maxWidth: 500,
                        maxHeight: 200,
                        text: settings.text
                    },
                    onPostRender: function() {
                        this.aria('describedby', this.items()[0]._id);
                    },
                    onClose: settings.onClose,
                    onCancel: function() {
                        callback(false);
                    }
                }).renderTo(document.body).reflow();
            },

            /**
             * Creates a new alert dialog.
             *
             * @method alert
             * @param {Object} settings Settings for the alert dialog.
             * @param {function} [callback] Callback to execute when the user makes a choice.
             */
            alert: function(settings, callback) {
                if (typeof(settings) == "string") {
                    settings = {text: settings};
                }

                settings.callback = callback;
                return MessageBox.msgBox(settings);
            },

            /**
             * Creates a new confirm dialog.
             *
             * @method confirm
             * @param {Object} settings Settings for the confirm dialog.
             * @param {function} [callback] Callback to execute when the user makes a choice.
             */
            confirm: function(settings, callback) {
                if (typeof(settings) == "string") {
                    settings = {text: settings};
                }

                settings.callback = callback;
                settings.buttons = MessageBox.OK_CANCEL;

                return MessageBox.msgBox(settings);
            }
        }
    });

    return MessageBox;
});

// Included from: js/tinymce/classes/WindowManager.js

/**
 * WindowManager.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class handles the creation of native windows and dialogs. This class can be extended to provide for example inline dialogs.
 *
 * @class tinymce.WindowManager
 * @example
 * // Opens a new dialog with the file.htm file and the size 320x240
 * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog.
 * tinymce.activeEditor.windowManager.open({
 *    url: 'file.htm',
 *    width: 320,
 *    height: 240
 * }, {
 *    custom_param: 1
 * });
 *
 * // Displays an alert box using the active editors window manager instance
 * tinymce.activeEditor.windowManager.alert('Hello world!');
 *
 * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm
 * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) {
 *    if (s)
 *       tinymce.activeEditor.windowManager.alert("Ok");
 *    else
 *       tinymce.activeEditor.windowManager.alert("Cancel");
 * });
 */
define("tinymce/WindowManager", [
    "tinymce/ui/Window",
    "tinymce/ui/MessageBox"
], function(Window, MessageBox) {
    return function(editor) {
        var self = this, windows = [];

        function getTopMostWindow() {
            if (windows.length) {
                return windows[windows.length - 1];
            }
        }

        self.windows = windows;

        /**
         * Opens a new window.
         *
         * @method open
         * @param {Object} args Optional name/value settings collection contains things like width/height/url etc.
         * @option {String} title Window title.
         * @option {String} file URL of the file to open in the window.
         * @option {Number} width Width in pixels.
         * @option {Number} height Height in pixels.
         * @option {Boolean} resizable Specifies whether the popup window is resizable or not.
         * @option {Boolean} maximizable Specifies whether the popup window has a "maximize" button and can get maximized or not.
         * @option {String/Boolean} scrollbars Specifies whether the popup window can have scrollbars if required (i.e. content
         * larger than the popup size specified).
         */
        self.open = function(args, params) {
            var win;

            editor.editorManager.activeEditor = editor;

            args.title = args.title || ' ';

            // Handle URL
            args.url = args.url || args.file; // Legacy
            if (args.url) {
                args.width = parseInt(args.width || 320, 10);
                args.height = parseInt(args.height || 240, 10);
            }

            // Handle body
            if (args.body) {
                args.items = {
                    defaults: args.defaults,
                    type: args.bodyType || 'form',
                    items: args.body
                };
            }

            if (!args.url && !args.buttons) {
                args.buttons = [
                    {text: 'Ok', subtype: 'primary', onclick: function() {
                        win.find('form')[0].submit();
                    }},

                    {text: 'Cancel', onclick: function() {
                        win.close();
                    }}
                ];
            }

            win = new Window(args);
            windows.push(win);

            win.on('close', function() {
                var i = windows.length;

                while (i--) {
                    if (windows[i] === win) {
                        windows.splice(i, 1);
                    }
                }

                editor.focus();
            });

            // Handle data
            if (args.data) {
                win.on('postRender', function() {
                    this.find('*').each(function(ctrl) {
                        var name = ctrl.name();

                        if (name in args.data) {
                            ctrl.value(args.data[name]);
                        }
                    });
                });
            }

            // store args and parameters
            win.features = args || {};
            win.params = params || {};

            // Takes a snapshot in the FocusManager of the selection before focus is lost to dialog
            editor.nodeChanged();

            return win.renderTo().reflow();
        };

        /**
         * Creates a alert dialog. Please don't use the blocking behavior of this
         * native version use the callback method instead then it can be extended.
         *
         * @method alert
         * @param {String} message Text to display in the new alert dialog.
         * @param {function} callback Callback function to be executed after the user has selected ok.
         * @param {Object} scope Optional scope to execute the callback in.
         * @example
         * // Displays an alert box using the active editors window manager instance
         * tinymce.activeEditor.windowManager.alert('Hello world!');
         */
        self.alert = function(message, callback, scope) {
            MessageBox.alert(message, function() {
                if (callback) {
                    callback.call(scope || this);
                } else {
                    editor.focus();
                }
            });
        };

        /**
         * Creates a confirm dialog. Please don't use the blocking behavior of this
         * native version use the callback method instead then it can be extended.
         *
         * @method confirm
         * @param {String} messageText to display in the new confirm dialog.
         * @param {function} callback Callback function to be executed after the user has selected ok or cancel.
         * @param {Object} scope Optional scope to execute the callback in.
         * @example
         * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm
         * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) {
         *    if (s)
         *       tinymce.activeEditor.windowManager.alert("Ok");
         *    else
         *       tinymce.activeEditor.windowManager.alert("Cancel");
         * });
         */
        self.confirm = function(message, callback, scope) {
            MessageBox.confirm(message, function(state) {
                callback.call(scope || this, state);
            });
        };

        /**
         * Closes the top most window.
         *
         * @method close
         */
        self.close = function() {
            if (getTopMostWindow()) {
                getTopMostWindow().close();
            }
        };

        /**
         * Returns the params of the last window open call. This can be used in iframe based
         * dialog to get params passed from the tinymce plugin.
         *
         * @example
         * var dialogArguments = top.tinymce.activeEditor.windowManager.getParams();
         *
         * @method getParams
         * @return {Object} Name/value object with parameters passed from windowManager.open call.
         */
        self.getParams = function() {
            return getTopMostWindow() ? getTopMostWindow().params : null;
        };

        /**
         * Sets the params of the last opened window.
         *
         * @method setParams
         * @param {Object} params Params object to set for the last opened window.
         */
        self.setParams = function(params) {
            if (getTopMostWindow()) {
                getTopMostWindow().params = params;
            }
        };

        /**
         * Returns the currently opened window objects.
         *
         * @method getWindows
         * @return {Array} Array of the currently opened windows.
         */
        self.getWindows = function() {
            return windows;
        };
    };
});

// Included from: js/tinymce/classes/util/Quirks.js

/**
 * Quirks.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 *
 * @ignore-file
 */

/**
 * This file includes fixes for various browser quirks it's made to make it easy to add/remove browser specific fixes.
 *
 * @class tinymce.util.Quirks
 */
define("tinymce/util/Quirks", [
    "tinymce/util/VK",
    "tinymce/dom/RangeUtils",
    "tinymce/html/Node",
    "tinymce/html/Entities",
    "tinymce/Env",
    "tinymce/util/Tools"
], function(VK, RangeUtils, Node, Entities, Env, Tools) {
    return function(editor) {
        var each = Tools.each;
        var BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection,
            settings = editor.settings, parser = editor.parser, serializer = editor.serializer;
        var isGecko = Env.gecko, isIE = Env.ie, isWebKit = Env.webkit;

        /**
         * Executes a command with a specific state this can be to enable/disable browser editing features.
         */
        function setEditorCommandState(cmd, state) {
            try {
                editor.getDoc().execCommand(cmd, false, state);
            } catch (ex) {
                // Ignore
            }
        }

        /**
         * Returns current IE document mode.
         */
        function getDocumentMode() {
            var documentMode = editor.getDoc().documentMode;

            return documentMode ? documentMode : 6;
        }

        /**
         * Returns true/false if the event is prevented or not.
         *
         * @private
         * @param {Event} e Event object.
         * @return {Boolean} true/false if the event is prevented or not.
         */
        function isDefaultPrevented(e) {
            return e.isDefaultPrevented();
        }

        /**
         * Fixes a WebKit bug when deleting contents using backspace or delete key.
         * WebKit will produce a span element if you delete across two block elements.
         *
         * Example:
         * <h1>a</h1><p>|b</p>
         *
         * Will produce this on backspace:
         * <h1>a<span style="<all runtime styles>">b</span></p>
         *
         * This fixes the backspace to produce:
         * <h1>a|b</p>
         *
         * See bug: https://bugs.webkit.org/show_bug.cgi?id=45784
         *
         * This fixes the following delete scenarios:
         *  1. Delete by pressing backspace key.
         *  2. Delete by pressing delete key.
         *  3. Delete by pressing backspace key with ctrl/cmd (Word delete).
         *  4. Delete by pressing delete key with ctrl/cmd (Word delete).
         *  5. Delete by drag/dropping contents inside the editor.
         *  6. Delete by using Cut Ctrl+X/Cmd+X.
         *  7. Delete by selecting contents and writing a character.'
         *
         * This code is a ugly hack since writing full custom delete logic for just this bug
         * fix seemed like a huge task. I hope we can remove this before the year 2030.
         */
        function cleanupStylesWhenDeleting() {
            var doc = editor.getDoc(), urlPrefix = 'data:text/mce-internal,';
            var MutationObserver = window.MutationObserver, olderWebKit, dragStartRng;

            // Add mini polyfill for older WebKits
            // TODO: Remove this when old Safari versions gets updated
            if (!MutationObserver) {
                olderWebKit = true;

                MutationObserver = function() {
                    var records = [], target;

                    function nodeInsert(e) {
                        var target = e.relatedNode || e.target;
                        records.push({target: target, addedNodes: [target]});
                    }

                    function attrModified(e) {
                        var target = e.relatedNode || e.target;
                        records.push({target: target, attributeName: e.attrName});
                    }

                    this.observe = function(node) {
                        target = node;
                        target.addEventListener('DOMSubtreeModified', nodeInsert, false);
                        target.addEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false);
                        target.addEventListener('DOMNodeInserted', nodeInsert, false);
                        target.addEventListener('DOMAttrModified', attrModified, false);
                    };

                    this.disconnect = function() {
                        target.removeEventListener('DOMSubtreeModified', nodeInsert, false);
                        target.removeEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false);
                        target.removeEventListener('DOMNodeInserted', nodeInsert, false);
                        target.removeEventListener('DOMAttrModified', attrModified, false);
                    };

                    this.takeRecords = function() {
                        return records;
                    };
                };
            }

            function customDelete(isForward) {
                var mutationObserver = new MutationObserver(function() {});

                Tools.each(editor.getBody().getElementsByTagName('*'), function(elm) {
                    // Mark existing spans
                    if (elm.tagName == 'SPAN') {
                        elm.setAttribute('mce-data-marked', 1);
                    }

                    // Make sure all elements has a data-mce-style attribute
                    if (!elm.hasAttribute('data-mce-style') && elm.hasAttribute('style')) {
                        editor.dom.setAttrib(elm, 'style', elm.getAttribute('style'));
                    }
                });

                // Observe added nodes and style attribute changes
                mutationObserver.observe(editor.getDoc(), {
                    childList: true,
                    attributes: true,
                    subtree: true,
                    attributeFilter: ['style']
                });

                editor.getDoc().execCommand(isForward ? 'ForwardDelete' : 'Delete', false, null);

                var rng = editor.selection.getRng();
                var caretElement = rng.startContainer.parentNode;

                Tools.each(mutationObserver.takeRecords(), function(record) {
                    if (!dom.isChildOf(record.target, editor.getBody())) {
                        return;
                    }

                    // Restore style attribute to previous value
                    if (record.attributeName == "style") {
                        var oldValue = record.target.getAttribute('data-mce-style');

                        if (oldValue) {
                            record.target.setAttribute("style", oldValue);
                        } else {
                            record.target.removeAttribute("style");
                        }
                    }

                    // Remove all spans that isn't maked and retain selection
                    Tools.each(record.addedNodes, function(node) {
                        if (node.nodeName == "SPAN" && !node.getAttribute('mce-data-marked')) {
                            var offset, container;

                            if (node == caretElement) {
                                offset = rng.startOffset;
                                container = node.firstChild;
                            }

                            dom.remove(node, true);

                            if (container) {
                                rng.setStart(container, offset);
                                rng.setEnd(container, offset);
                                editor.selection.setRng(rng);
                            }
                        }
                    });
                });

                mutationObserver.disconnect();

                // Remove any left over marks
                Tools.each(editor.dom.select('span[mce-data-marked]'), function(span) {
                    span.removeAttribute('mce-data-marked');
                });
            }

            editor.on('keydown', function(e) {
                var isForward = e.keyCode == DELETE, isMeta = VK.metaKeyPressed(e);

                if (!isDefaultPrevented(e) && (isForward || e.keyCode == BACKSPACE)) {
                    var rng = editor.selection.getRng(), container = rng.startContainer, offset = rng.startOffset;

                    // Ignore non meta delete in the where there is text before/after the caret
                    if (!isMeta && rng.collapsed && container.nodeType == 3) {
                        if (isForward ? offset < container.data.length : offset > 0) {
                            return;
                        }
                    }

                    e.preventDefault();

                    if (isMeta) {
                        editor.selection.getSel().modify("extend", isForward ? "forward" : "backward", "word");
                    }

                    customDelete(isForward);
                }
            });

            editor.on('keypress', function(e) {
                if (!isDefaultPrevented(e) && !selection.isCollapsed() && e.charCode && !VK.metaKeyPressed(e)) {
                    e.preventDefault();
                    customDelete(true);
                    editor.selection.setContent(String.fromCharCode(e.charCode));
                }
            });

            editor.addCommand('Delete', function() {
                customDelete();
            });

            editor.addCommand('ForwardDelete', function() {
                customDelete(true);
            });

            // Older WebKits doesn't properly handle the clipboard so we can't add the rest
            if (olderWebKit) {
                return;
            }

            editor.on('dragstart', function(e) {
                var selectionHtml;

                if (editor.selection.isCollapsed() && e.target.tagName == 'IMG') {
                    selection.select(e.target);
                }

                dragStartRng = selection.getRng();
                selectionHtml = editor.selection.getContent();

                // Safari doesn't support custom dataTransfer items so we can only use URL and Text
                if (selectionHtml.length > 0) {
                    e.dataTransfer.setData('URL', 'data:text/mce-internal,' + escape(selectionHtml));
                }
            });

            editor.on('drop', function(e) {
                if (!isDefaultPrevented(e)) {
                    var internalContent = e.dataTransfer.getData('URL');

                    if (!internalContent || internalContent.indexOf(urlPrefix) == -1 || !doc.caretRangeFromPoint) {
                        return;
                    }

                    internalContent = unescape(internalContent.substr(urlPrefix.length));
                    if (doc.caretRangeFromPoint) {
                        e.preventDefault();

                        // Safari has a weird issue where drag/dropping images sometimes
                        // produces a green plus icon. When this happens the caretRangeFromPoint
                        // will return "null" even though the x, y coordinate is correct.
                        // But if we detach the insert from the drop event we will get a proper range
                        window.setTimeout(function() {
                            var pointRng = doc.caretRangeFromPoint(e.x, e.y);

                            if (dragStartRng) {
                                selection.setRng(dragStartRng);
                                dragStartRng = null;
                            }

                            customDelete();

                            selection.setRng(pointRng);
                            editor.insertContent(internalContent);
                        }, 0);
                    }

                }
            });

            editor.on('cut', function(e) {
                if (!isDefaultPrevented(e) && e.clipboardData) {
                    e.preventDefault();
                    e.clipboardData.clearData();
                    e.clipboardData.setData('text/html', editor.selection.getContent());
                    e.clipboardData.setData('text/plain', editor.selection.getContent({format: 'text'}));
                    customDelete(true);
                }
            });
        }

        /**
         * Makes sure that the editor body becomes empty when backspace or delete is pressed in empty editors.
         *
         * For example:
         * <p><b>|</b></p>
         *
         * Or:
         * <h1>|</h1>
         *
         * Or:
         * [<h1></h1>]
         */
        function emptyEditorWhenDeleting() {
            function serializeRng(rng) {
                var body = dom.create("body");
                var contents = rng.cloneContents();
                body.appendChild(contents);
                return selection.serializer.serialize(body, {format: 'html'});
            }

            function allContentsSelected(rng) {
                if (!rng.setStart) {
                    if (rng.item) {
                        return false;
                    }

                    var bodyRng = rng.duplicate();
                    bodyRng.moveToElementText(editor.getBody());
                    return RangeUtils.compareRanges(rng, bodyRng);
                }

                var selection = serializeRng(rng);

                var allRng = dom.createRng();
                allRng.selectNode(editor.getBody());

                var allSelection = serializeRng(allRng);
                return selection === allSelection;
            }

            editor.on('keydown', function(e) {
                var keyCode = e.keyCode, isCollapsed, body;

                // Empty the editor if it's needed for example backspace at <p><b>|</b></p>
                if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) {
                    isCollapsed = editor.selection.isCollapsed();
                    body = editor.getBody();

                    // Selection is collapsed but the editor isn't empty
                    if (isCollapsed && !dom.isEmpty(body)) {
                        return;
                    }

                    // Selection isn't collapsed but not all the contents is selected
                    if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
                        return;
                    }

                    // Manually empty the editor
                    e.preventDefault();
                    editor.setContent('');

                    if (body.firstChild && dom.isBlock(body.firstChild)) {
                        editor.selection.setCursorLocation(body.firstChild, 0);
                    } else {
                        editor.selection.setCursorLocation(body, 0);
                    }

                    editor.nodeChanged();
                }
            });
        }

        /**
         * WebKit doesn't select all the nodes in the body when you press Ctrl+A.
         * IE selects more than the contents <body>[<p>a</p>]</body> instead of <body><p>[a]</p]</body> see bug #6438
         * This selects the whole body so that backspace/delete logic will delete everything
         */
        function selectAll() {
            editor.on('keydown', function(e) {
                if (!isDefaultPrevented(e) && e.keyCode == 65 && VK.metaKeyPressed(e)) {
                    e.preventDefault();
                    editor.execCommand('SelectAll');
                }
            });
        }

        /**
         * WebKit has a weird issue where it some times fails to properly convert keypresses to input method keystrokes.
         * The IME on Mac doesn't initialize when it doesn't fire a proper focus event.
         *
         * This seems to happen when the user manages to click the documentElement element then the window doesn't get proper focus until
         * you enter a character into the editor.
         *
         * It also happens when the first focus in made to the body.
         *
         * See: https://bugs.webkit.org/show_bug.cgi?id=83566
         */
        function inputMethodFocus() {
            if (!editor.settings.content_editable) {
                // Case 1 IME doesn't initialize if you focus the document
                dom.bind(editor.getDoc(), 'focusin', function() {
                    selection.setRng(selection.getRng());
                });

                // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
                dom.bind(editor.getDoc(), 'mousedown', function(e) {
                    if (e.target == editor.getDoc().documentElement) {
                        editor.getBody().focus();
                        selection.setRng(selection.getRng());
                    }
                });
            }
        }

        /**
         * Backspacing in FireFox/IE from a paragraph into a horizontal rule results in a floating text node because the
         * browser just deletes the paragraph - the browser fails to merge the text node with a horizontal rule so it is
         * left there. TinyMCE sees a floating text node and wraps it in a paragraph on the key up event (ForceBlocks.js
         * addRootBlocks), meaning the action does nothing. With this code, FireFox/IE matche the behaviour of other
         * browsers.
         *
         * It also fixes a bug on Firefox where it's impossible to delete HR elements.
         */
        function removeHrOnBackspace() {
            editor.on('keydown', function(e) {
                if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
                    // Check if there is any HR elements this is faster since getRng on IE 7 & 8 is slow
                    if (!editor.getBody().getElementsByTagName('hr').length) {
                        return;
                    }

                    if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
                        var node = selection.getNode();
                        var previousSibling = node.previousSibling;

                        if (node.nodeName == 'HR') {
                            dom.remove(node);
                            e.preventDefault();
                            return;
                        }

                        if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
                            dom.remove(previousSibling);
                            e.preventDefault();
                        }
                    }
                }
            });
        }

        /**
         * Firefox 3.x has an issue where the body element won't get proper focus if you click out
         * side it's rectangle.
         */
        function focusBody() {
            // Fix for a focus bug in FF 3.x where the body element
            // wouldn't get proper focus if the user clicked on the HTML element
            if (!window.Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
                editor.on('mousedown', function(e) {
                    if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") {
                        var body = editor.getBody();

                        // Blur the body it's focused but not correctly focused
                        body.blur();

                        // Refocus the body after a little while
                        setTimeout(function() {
                            body.focus();
                        }, 0);
                    }
                });
            }
        }

        /**
         * WebKit has a bug where it isn't possible to select image, hr or anchor elements
         * by clicking on them so we need to fake that.
         */
        function selectControlElements() {
            editor.on('click', function(e) {
                e = e.target;

                // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
                // WebKit can't even do simple things like selecting an image
                // Needs tobe the setBaseAndExtend or it will fail to select floated images
                if (/^(IMG|HR)$/.test(e.nodeName)) {
                    selection.getSel().setBaseAndExtent(e, 0, e, 1);
                }

                if (e.nodeName == 'A' && dom.hasClass(e, 'mce-item-anchor')) {
                    selection.select(e);
                }
            });
        }

        /**
         * Fixes a Gecko bug where the style attribute gets added to the wrong element when deleting between two block elements.
         *
         * Fixes do backspace/delete on this:
         * <p>bla[ck</p><p style="color:red">r]ed</p>
         *
         * Would become:
         * <p>bla|ed</p>
         *
         * Instead of:
         * <p style="color:red">bla|ed</p>
         */
        function removeStylesWhenDeletingAcrossBlockElements() {
            function getAttributeApplyFunction() {
                var template = dom.getAttribs(selection.getStart().cloneNode(false));

                return function() {
                    var target = selection.getStart();

                    if (target !== editor.getBody()) {
                        dom.setAttrib(target, "style", null);

                        each(template, function(attr) {
                            target.setAttributeNode(attr.cloneNode(true));
                        });
                    }
                };
            }

            function isSelectionAcrossElements() {
                return !selection.isCollapsed() &&
                    dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock);
            }

            editor.on('keypress', function(e) {
                var applyAttributes;

                if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
                    applyAttributes = getAttributeApplyFunction();
                    editor.getDoc().execCommand('delete', false, null);
                    applyAttributes();
                    e.preventDefault();
                    return false;
                }
            });

            dom.bind(editor.getDoc(), 'cut', function(e) {
                var applyAttributes;

                if (!isDefaultPrevented(e) && isSelectionAcrossElements()) {
                    applyAttributes = getAttributeApplyFunction();

                    setTimeout(function() {
                        applyAttributes();
                    }, 0);
                }
            });
        }

        /**
         * Fire a nodeChanged when the selection is changed on WebKit this fixes selection issues on iOS5. It only fires the nodeChange
         * event every 50ms since it would other wise update the UI when you type and it hogs the CPU.
         */
        function selectionChangeNodeChanged() {
            var lastRng, selectionTimer;

            editor.on('selectionchange', function() {
                if (selectionTimer) {
                    clearTimeout(selectionTimer);
                    selectionTimer = 0;
                }

                selectionTimer = window.setTimeout(function() {
                    if (editor.removed) {
                        return;
                    }

                    var rng = selection.getRng();

                    // Compare the ranges to see if it was a real change or not
                    if (!lastRng || !RangeUtils.compareRanges(rng, lastRng)) {
                        editor.nodeChanged();
                        lastRng = rng;
                    }
                }, 50);
            });
        }

        /**
         * Screen readers on IE needs to have the role application set on the body.
         */
        function ensureBodyHasRoleApplication() {
            document.body.setAttribute("role", "application");
        }

        /**
         * Backspacing into a table behaves differently depending upon browser type.
         * Therefore, disable Backspace when cursor immediately follows a table.
         */
        function disableBackspaceIntoATable() {
            editor.on('keydown', function(e) {
                if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
                    if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
                        var previousSibling = selection.getNode().previousSibling;
                        if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
                            e.preventDefault();
                            return false;
                        }
                    }
                }
            });
        }

        /**
         * Old IE versions can't properly render BR elements in PRE tags white in contentEditable mode. So this
         * logic adds a \n before the BR so that it will get rendered.
         */
        function addNewLinesBeforeBrInPre() {
            // IE8+ rendering mode does the right thing with BR in PRE
            if (getDocumentMode() > 7) {
                return;
            }

            // Enable display: none in area and add a specific class that hides all BR elements in PRE to
            // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
            setEditorCommandState('RespectVisibilityInDesign', true);
            editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
            dom.addClass(editor.getBody(), 'mceHideBrInPre');

            // Adds a \n before all BR elements in PRE to get them visual
            parser.addNodeFilter('pre', function(nodes) {
                var i = nodes.length, brNodes, j, brElm, sibling;

                while (i--) {
                    brNodes = nodes[i].getAll('br');
                    j = brNodes.length;
                    while (j--) {
                        brElm = brNodes[j];

                        // Add \n before BR in PRE elements on older IE:s so the new lines get rendered
                        sibling = brElm.prev;
                        if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
                            sibling.value += '\n';
                        } else {
                            brElm.parent.insert(new Node('#text', 3), brElm, true).value = '\n';
                        }
                    }
                }
            });

            // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
            serializer.addNodeFilter('pre', function(nodes) {
                var i = nodes.length, brNodes, j, brElm, sibling;

                while (i--) {
                    brNodes = nodes[i].getAll('br');
                    j = brNodes.length;
                    while (j--) {
                        brElm = brNodes[j];
                        sibling = brElm.prev;
                        if (sibling && sibling.type == 3) {
                            sibling.value = sibling.value.replace(/\r?\n$/, '');
                        }
                    }
                }
            });
        }

        /**
         * Moves style width/height to attribute width/height when the user resizes an image on IE.
         */
        function removePreSerializedStylesWhenSelectingControls() {
            dom.bind(editor.getBody(), 'mouseup', function() {
                var value, node = selection.getNode();

                // Moved styles to attributes on IMG eements
                if (node.nodeName == 'IMG') {
                    // Convert style width to width attribute
                    if ((value = dom.getStyle(node, 'width'))) {
                        dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));
                        dom.setStyle(node, 'width', '');
                    }

                    // Convert style height to height attribute
                    if ((value = dom.getStyle(node, 'height'))) {
                        dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));
                        dom.setStyle(node, 'height', '');
                    }
                }
            });
        }

        /**
         * Removes a blockquote when backspace is pressed at the beginning of it.
         *
         * For example:
         * <blockquote><p>|x</p></blockquote>
         *
         * Becomes:
         * <p>|x</p>
         */
        function removeBlockQuoteOnBackSpace() {
            // Add block quote deletion handler
            editor.on('keydown', function(e) {
                var rng, container, offset, root, parent;

                if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) {
                    return;
                }

                rng = selection.getRng();
                container = rng.startContainer;
                offset = rng.startOffset;
                root = dom.getRoot();
                parent = container;

                if (!rng.collapsed || offset !== 0) {
                    return;
                }

                while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
                    parent = parent.parentNode;
                }

                // Is the cursor at the beginning of a blockquote?
                if (parent.tagName === 'BLOCKQUOTE') {
                    // Remove the blockquote
                    editor.formatter.toggle('blockquote', null, parent);

                    // Move the caret to the beginning of container
                    rng = dom.createRng();
                    rng.setStart(container, 0);
                    rng.setEnd(container, 0);
                    selection.setRng(rng);
                }
            });
        }

        /**
         * Sets various Gecko editing options on mouse down and before a execCommand to disable inline table editing that is broken etc.
         */
        function setGeckoEditingOptions() {
            function setOpts() {
                editor._refreshContentEditable();

                setEditorCommandState("StyleWithCSS", false);
                setEditorCommandState("enableInlineTableEditing", false);

                if (!settings.object_resizing) {
                    setEditorCommandState("enableObjectResizing", false);
                }
            }

            if (!settings.readonly) {
                editor.on('BeforeExecCommand MouseDown', setOpts);
            }
        }

        /**
         * Fixes a gecko link bug, when a link is placed at the end of block elements there is
         * no way to move the caret behind the link. This fix adds a bogus br element after the link.
         *
         * For example this:
         * <p><b><a href="#">x</a></b></p>
         *
         * Becomes this:
         * <p><b><a href="#">x</a></b><br></p>
         */
        function addBrAfterLastLinks() {
            function fixLinks() {
                each(dom.select('a'), function(node) {
                    var parentNode = node.parentNode, root = dom.getRoot();

                    if (parentNode.lastChild === node) {
                        while (parentNode && !dom.isBlock(parentNode)) {
                            if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
                                return;
                            }

                            parentNode = parentNode.parentNode;
                        }

                        dom.add(parentNode, 'br', {'data-mce-bogus': 1});
                    }
                });
            }

            editor.on('SetContent ExecCommand', function(e) {
                if (e.type == "setcontent" || e.command === 'mceInsertLink') {
                    fixLinks();
                }
            });
        }

        /**
         * WebKit will produce DIV elements here and there by default. But since TinyMCE uses paragraphs by
         * default we want to change that behavior.
         */
        function setDefaultBlockType() {
            if (settings.forced_root_block) {
                editor.on('init', function() {
                    setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
                });
            }
        }

        /**
         * Removes ghost selections from images/tables on Gecko.
         */
        function removeGhostSelection() {
            editor.on('Undo Redo SetContent', function(e) {
                if (!e.initial) {
                    editor.execCommand('mceRepaint');
                }
            });
        }

        /**
         * Deletes the selected image on IE instead of navigating to previous page.
         */
        function deleteControlItemOnBackSpace() {
            editor.on('keydown', function(e) {
                var rng;

                if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) {
                    rng = editor.getDoc().selection.createRange();
                    if (rng && rng.item) {
                        e.preventDefault();
                        editor.undoManager.beforeChange();
                        dom.remove(rng.item(0));
                        editor.undoManager.add();
                    }
                }
            });
        }

        /**
         * IE10 doesn't properly render block elements with the right height until you add contents to them.
         * This fixes that by adding a padding-right to all empty text block elements.
         * See: https://connect.microsoft.com/IE/feedback/details/743881
         */
        function renderEmptyBlocksFix() {
            var emptyBlocksCSS;

            // IE10+
            if (getDocumentMode() >= 10) {
                emptyBlocksCSS = '';
                each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
                    emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
                });

                editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
            }
        }

        /**
         * Old IE versions can't retain contents within noscript elements so this logic will store the contents
         * as a attribute and the insert that value as it's raw text when the DOM is serialized.
         */
        function keepNoScriptContents() {
            if (getDocumentMode() < 9) {
                parser.addNodeFilter('noscript', function(nodes) {
                    var i = nodes.length, node, textNode;

                    while (i--) {
                        node = nodes[i];
                        textNode = node.firstChild;

                        if (textNode) {
                            node.attr('data-mce-innertext', textNode.value);
                        }
                    }
                });

                serializer.addNodeFilter('noscript', function(nodes) {
                    var i = nodes.length, node, textNode, value;

                    while (i--) {
                        node = nodes[i];
                        textNode = nodes[i].firstChild;

                        if (textNode) {
                            textNode.value = Entities.decode(textNode.value);
                        } else {
                            // Old IE can't retain noscript value so an attribute is used to store it
                            value = node.attributes.map['data-mce-innertext'];
                            if (value) {
                                node.attr('data-mce-innertext', null);
                                textNode = new Node('#text', 3);
                                textNode.value = value;
                                textNode.raw = true;
                                node.append(textNode);
                            }
                        }
                    }
                });
            }
        }

        /**
         * IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode.
         */
        function fixCaretSelectionOfDocumentElementOnIe() {
            var doc = dom.doc, body = doc.body, started, startRng, htmlElm;

            // Return range from point or null if it failed
            function rngFromPoint(x, y) {
                var rng = body.createTextRange();

                try {
                    rng.moveToPoint(x, y);
                } catch (ex) {
                    // IE sometimes throws and exception, so lets just ignore it
                    rng = null;
                }

                return rng;
            }

            // Fires while the selection is changing
            function selectionChange(e) {
                var pointRng;

                // Check if the button is down or not
                if (e.button) {
                    // Create range from mouse position
                    pointRng = rngFromPoint(e.x, e.y);

                    if (pointRng) {
                        // Check if pointRange is before/after selection then change the endPoint
                        if (pointRng.compareEndPoints('StartToStart', startRng) > 0) {
                            pointRng.setEndPoint('StartToStart', startRng);
                        } else {
                            pointRng.setEndPoint('EndToEnd', startRng);
                        }

                        pointRng.select();
                    }
                } else {
                    endSelection();
                }
            }

            // Removes listeners
            function endSelection() {
                var rng = doc.selection.createRange();

                // If the range is collapsed then use the last start range
                if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) {
                    startRng.select();
                }

                dom.unbind(doc, 'mouseup', endSelection);
                dom.unbind(doc, 'mousemove', selectionChange);
                startRng = started = 0;
            }

            // Make HTML element unselectable since we are going to handle selection by hand
            doc.documentElement.unselectable = true;

            // Detect when user selects outside BODY
            dom.bind(doc, 'mousedown contextmenu', function(e) {
                if (e.target.nodeName === 'HTML') {
                    if (started) {
                        endSelection();
                    }

                    // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
                    htmlElm = doc.documentElement;
                    if (htmlElm.scrollHeight > htmlElm.clientHeight) {
                        return;
                    }

                    started = 1;
                    // Setup start position
                    startRng = rngFromPoint(e.x, e.y);
                    if (startRng) {
                        // Listen for selection change events
                        dom.bind(doc, 'mouseup', endSelection);
                        dom.bind(doc, 'mousemove', selectionChange);

                        dom.getRoot().focus();
                        startRng.select();
                    }
                }
            });
        }

        /**
         * Fixes selection issues where the caret can be placed between two inline elements like <b>a</b>|<b>b</b>
         * this fix will lean the caret right into the closest inline element.
         */
        function normalizeSelection() {
            // Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything
            editor.on('keyup focusin mouseup', function(e) {
                if (e.keyCode != 65 || !VK.metaKeyPressed(e)) {
                    selection.normalize();
                }
            }, true);
        }

        /**
         * Forces Gecko to render a broken image icon if it fails to load an image.
         */
        function showBrokenImageIcon() {
            editor.contentStyles.push(
                'img:-moz-broken {' +
                    '-moz-force-broken-image-icon:1;' +
                    'min-width:24px;' +
                    'min-height:24px' +
                '}'
            );
        }

        /**
         * iOS has a bug where it's impossible to type if the document has a touchstart event
         * bound and the user touches the document while having the on screen keyboard visible.
         *
         * The touch event moves the focus to the parent document while having the caret inside the iframe
         * this fix moves the focus back into the iframe document.
         */
        function restoreFocusOnKeyDown() {
            if (!editor.inline) {
                editor.on('keydown', function() {
                    if (document.activeElement == document.body) {
                        editor.getWin().focus();
                    }
                });
            }
        }

        /**
         * IE 11 has an annoying issue where you can't move focus into the editor
         * by clicking on the white area HTML element. We used to be able to to fix this with
         * the fixCaretSelectionOfDocumentElementOnIe fix. But since M$ removed the selection
         * object it's not possible anymore. So we need to hack in a ungly CSS to force the
         * body to be at least 150px. If the user clicks the HTML element out side this 150px region
         * we simply move the focus into the first paragraph. Not ideal since you loose the
         * positioning of the caret but goot enough for most cases.
         */
        function bodyHeight() {
            if (!editor.inline) {
                editor.contentStyles.push('body {min-height: 150px}');
                editor.on('click', function(e) {
                    if (e.target.nodeName == 'HTML') {
                        editor.getBody().focus();
                        editor.selection.normalize();
                        editor.nodeChanged();
                    }
                });
            }
        }

        /**
         * Firefox on Mac OS will move the browser back to the previous page if you press CMD+Left arrow.
         * You might then loose all your work so we need to block that behavior and replace it with our own.
         */
        function blockCmdArrowNavigation() {
            if (Env.mac) {
                editor.on('keydown', function(e) {
                    if (VK.metaKeyPressed(e) && (e.keyCode == 37 || e.keyCode == 39)) {
                        e.preventDefault();
                        editor.selection.getSel().modify('move', e.keyCode == 37 ? 'backward' : 'forward', 'word');
                    }
                });
            }
        }

        /**
         * Disables the autolinking in IE 9+ this is then re-enabled by the autolink plugin.
         */
        function disableAutoUrlDetect() {
            setEditorCommandState("AutoUrlDetect", false);
        }

        /**
         * IE 11 has a fantastic bug where it will produce two trailing BR elements to iframe bodies when
         * the iframe is hidden by display: none on a parent container. The DOM is actually out of sync
         * with innerHTML in this case. It's like IE adds shadow DOM BR elements that appears on innerHTML
         * but not as the lastChild of the body. However is we add a BR element to the body then remove it
         * it doesn't seem to add these BR elements makes sence right?!
         *
         * Example of what happens: <body>text</body> becomes <body>text<br><br></body>
         */
        function doubleTrailingBrElements() {
            if (!editor.inline) {
                editor.on('focus blur beforegetcontent', function() {
                    var br = editor.dom.create('br');
                    editor.getBody().appendChild(br);
                    br.parentNode.removeChild(br);
                }, true);
            }
        }

        /**
         * iOS 7.1 introduced two new bugs:
         * 1) It's possible to open links within a contentEditable area by clicking on them.
         * 2) If you hold down the finger it will display the link/image touch callout menu.
         */
        function tapLinksAndImages() {
            editor.on('click', function(e) {
                var elm = e.target;

                do {
                    if (elm.tagName === 'A') {
                        e.preventDefault();
                        return;
                    }
                } while ((elm = elm.parentNode));
            });

            editor.contentStyles.push('.mce-content-body {-webkit-touch-callout: none}');
        }

        /**
         * WebKit has a bug where it will allow forms to be submitted if they are inside a contentEditable element.
         * For example this: <form><button></form>
         */
        function blockFormSubmitInsideEditor() {
            editor.on('init', function() {
                editor.dom.bind(editor.getBody(), 'submit', function(e) {
                    e.preventDefault();
                });
            });
        }

        /**
         * Sometimes WebKit/Blink generates BR elements with the Apple-interchange-newline class.
         *
         * Scenario:
         *  1) Create a table 2x2.
         *  2) Select and copy cells A2-B2.
         *  3) Paste and it will add BR element to table cell.
         */
        function removeAppleInterchangeBrs() {
            parser.addNodeFilter('br', function(nodes) {
                var i = nodes.length;

                while (i--) {
                    if (nodes[i].attr('class') == 'Apple-interchange-newline') {
                        nodes[i].remove();
                    }
                }
            });
        }

        // All browsers
        removeBlockQuoteOnBackSpace();
        emptyEditorWhenDeleting();
        normalizeSelection();

        // WebKit
        if (isWebKit) {
            cleanupStylesWhenDeleting();
            inputMethodFocus();
            selectControlElements();
            setDefaultBlockType();
            blockFormSubmitInsideEditor();
            disableBackspaceIntoATable();
            removeAppleInterchangeBrs();

            // iOS
            if (Env.iOS) {
                selectionChangeNodeChanged();
                restoreFocusOnKeyDown();
                bodyHeight();
                tapLinksAndImages();
            } else {
                selectAll();
            }
        }

        // IE
        if (isIE && Env.ie < 11) {
            removeHrOnBackspace();
            ensureBodyHasRoleApplication();
            addNewLinesBeforeBrInPre();
            removePreSerializedStylesWhenSelectingControls();
            deleteControlItemOnBackSpace();
            renderEmptyBlocksFix();
            keepNoScriptContents();
            fixCaretSelectionOfDocumentElementOnIe();
        }

        if (Env.ie >= 11) {
            bodyHeight();
            doubleTrailingBrElements();
            disableBackspaceIntoATable();
        }

        if (Env.ie) {
            selectAll();
            disableAutoUrlDetect();
        }

        // Gecko
        if (isGecko) {
            removeHrOnBackspace();
            focusBody();
            removeStylesWhenDeletingAcrossBlockElements();
            setGeckoEditingOptions();
            addBrAfterLastLinks();
            removeGhostSelection();
            showBrokenImageIcon();
            blockCmdArrowNavigation();
            disableBackspaceIntoATable();
        }
    };
});

// Included from: js/tinymce/classes/util/Observable.js

/**
 * Observable.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This mixin will add event binding logic to classes.
 *
 * @mixin tinymce.util.Observable
 */
define("tinymce/util/Observable", [
    "tinymce/util/EventDispatcher"
], function(EventDispatcher) {
    function getEventDispatcher(obj) {
        if (!obj._eventDispatcher) {
            obj._eventDispatcher = new EventDispatcher({
                scope: obj,
                toggleEvent: function(name, state) {
                    if (EventDispatcher.isNative(name) && obj.toggleNativeEvent) {
                        obj.toggleNativeEvent(name, state);
                    }
                }
            });
        }

        return obj._eventDispatcher;
    }

    return {
        /**
         * Fires the specified event by name.
         *
         * @method fire
         * @param {String} name Name of the event to fire.
         * @param {Object?} args Event arguments.
         * @param {Boolean?} bubble True/false if the event is to be bubbled.
         * @return {Object} Event args instance passed in.
         * @example
         * instance.fire('event', {...});
         */
        fire: function(name, args, bubble) {
            var self = this;

            // Prevent all events except the remove event after the instance has been removed
            if (self.removed && name !== "remove") {
                return args;
            }

            args = getEventDispatcher(self).fire(name, args, bubble);

            // Bubble event up to parents
            if (bubble !== false && self.parent) {
                var parent = self.parent();
                while (parent && !args.isPropagationStopped()) {
                    parent.fire(name, args, false);
                    parent = parent.parent();
                }
            }

            return args;
        },

        /**
         * Binds an event listener to a specific event by name.
         *
         * @method on
         * @param {String} name Event name or space separated list of events to bind.
         * @param {callback} callback Callback to be executed when the event occurs.
         * @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
         * @return {Object} Current class instance.
         * @example
         * instance.on('event', function(e) {
         *     // Callback logic
         * });
         */
        on: function(name, callback, prepend) {
            return getEventDispatcher(this).on(name, callback, prepend);
        },

        /**
         * Unbinds an event listener to a specific event by name.
         *
         * @method off
         * @param {String?} name Name of the event to unbind.
         * @param {callback?} callback Callback to unbind.
         * @return {Object} Current class instance.
         * @example
         * // Unbind specific callback
         * instance.off('event', handler);
         *
         * // Unbind all listeners by name
         * instance.off('event');
         *
         * // Unbind all events
         * instance.off();
         */
        off: function(name, callback) {
            return getEventDispatcher(this).off(name, callback);
        },

        /**
         * Bind the event callback and once it fires the callback is removed.
         *
         * @method once
         * @param {String} name Name of the event to bind.
         * @param {callback} callback Callback to bind only once.
         * @return {Object} Current class instance.
         */
        once: function(name, callback) {
            return getEventDispatcher(this).once(name, callback);
        },

        /**
         * Returns true/false if the object has a event of the specified name.
         *
         * @method hasEventListeners
         * @param {String} name Name of the event to check for.
         * @return {Boolean} true/false if the event exists or not.
         */
        hasEventListeners: function(name) {
            return getEventDispatcher(this).has(name);
        }
    };
});

// Included from: js/tinymce/classes/EditorObservable.js

/**
 * EditorObservable.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This mixin contains the event logic for the tinymce.Editor class.
 *
 * @mixin tinymce.EditorObservable
 * @extends tinymce.util.Observable
 */
define("tinymce/EditorObservable", [
    "tinymce/util/Observable",
    "tinymce/dom/DOMUtils",
    "tinymce/util/Tools"
], function(Observable, DOMUtils, Tools) {
    var DOM = DOMUtils.DOM;

    function getEventTarget(editor, eventName) {
        if (eventName == 'selectionchange') {
            return editor.getDoc();
        }

        // Need to bind mousedown/mouseup etc to document not body in iframe mode
        // Since the user might click on the HTML element not the BODY
        if (!editor.inline && /^mouse|click|contextmenu|drop|dragover|dragend/.test(eventName)) {
            return editor.getDoc();
        }

        return editor.getBody();
    }

    function bindEventDelegate(editor, name) {
        var eventRootSelector = editor.settings.event_root, editorManager = editor.editorManager;
        var eventRootElm = editorManager.eventRootElm || getEventTarget(editor, name);

        if (eventRootSelector) {
            if (!editorManager.rootEvents) {
                editorManager.rootEvents = {};

                editorManager.on('RemoveEditor', function() {
                    if (!editorManager.activeEditor) {
                        DOM.unbind(eventRootElm);
                        delete editorManager.rootEvents;
                    }
                });
            }

            if (editorManager.rootEvents[name]) {
                return;
            }

            if (eventRootElm == editor.getBody()) {
                eventRootElm = DOM.select(eventRootSelector)[0];
                editorManager.eventRootElm = eventRootElm;
            }

            editorManager.rootEvents[name] = true;

            DOM.bind(eventRootElm, name, function(e) {
                var target = e.target, editors = editorManager.editors, i = editors.length;

                while (i--) {
                    var body = editors[i].getBody();

                    if (body === target || DOM.isChildOf(target, body)) {
                        if (!editors[i].hidden) {
                            editors[i].fire(name, e);
                        }
                    }
                }
            });
        } else {
            editor.dom.bind(eventRootElm, name, function(e) {
                if (!editor.hidden) {
                    editor.fire(name, e);
                }
            });
        }
    }

    var EditorObservable = {
        bindPendingEventDelegates: function() {
            var self = this;

            Tools.each(self._pendingNativeEvents, function(name) {
                bindEventDelegate(self, name);
            });
        },

        toggleNativeEvent: function(name, state) {
            var self = this;

            if (self.settings.readonly) {
                return;
            }

            // Never bind focus/blur since the FocusManager fakes those
            if (name == "focus" || name == "blur") {
                return;
            }

            if (state) {
                if (self.initialized) {
                    bindEventDelegate(self, name);
                } else {
                    if (!self._pendingNativeEvents) {
                        self._pendingNativeEvents = [name];
                    } else {
                        self._pendingNativeEvents.push(name);
                    }
                }
            } else if (self.initialized) {
                self.dom.unbind(getEventTarget(self, name), name);
            }
        }
    };

    EditorObservable = Tools.extend({}, Observable, EditorObservable);

    return EditorObservable;
});

// Included from: js/tinymce/classes/Shortcuts.js

/**
 * Shortcuts.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Contains all logic for handling of keyboard shortcuts.
 */
define("tinymce/Shortcuts", [
    "tinymce/util/Tools",
    "tinymce/Env"
], function(Tools, Env) {
    var each = Tools.each, explode = Tools.explode;

    var keyCodeLookup = {
        "f9": 120,
        "f10": 121,
        "f11": 122
    };

    return function(editor) {
        var self = this, shortcuts = {};

        editor.on('keyup keypress keydown', function(e) {
            if (e.altKey || e.ctrlKey || e.metaKey) {
                each(shortcuts, function(shortcut) {
                    var ctrlKey = Env.mac ? e.metaKey : e.ctrlKey;

                    if (shortcut.ctrl != ctrlKey || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) {
                        return;
                    }

                    if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
                        e.preventDefault();

                        if (e.type == "keydown") {
                            shortcut.func.call(shortcut.scope);
                        }

                        return true;
                    }
                });
            }
        });

        /**
         * Adds a keyboard shortcut for some command or function.
         *
         * @method addShortcut
         * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
         * @param {String} desc Text description for the command.
         * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
         * @param {Object} sc Optional scope to execute the function in.
         * @return {Boolean} true/false state if the shortcut was added or not.
         */
        self.add = function(pattern, desc, cmdFunc, scope) {
            var cmd;

            cmd = cmdFunc;

            if (typeof(cmdFunc) === 'string') {
                cmdFunc = function() {
                    editor.execCommand(cmd, false, null);
                };
            } else if (Tools.isArray(cmd)) {
                cmdFunc = function() {
                    editor.execCommand(cmd[0], cmd[1], cmd[2]);
                };
            }

            each(explode(pattern.toLowerCase()), function(pattern) {
                var shortcut = {
                    func: cmdFunc,
                    scope: scope || editor,
                    desc: editor.translate(desc),
                    alt: false,
                    ctrl: false,
                    shift: false
                };

                each(explode(pattern, '+'), function(value) {
                    switch (value) {
                        case 'alt':
                        case 'ctrl':
                        case 'shift':
                            shortcut[value] = true;
                            break;

                        default:
                            // Allow numeric keycodes like ctrl+219 for ctrl+[
                            if (/^[0-9]{2,}$/.test(value)) {
                                shortcut.keyCode = parseInt(value, 10);
                            } else {
                                shortcut.charCode = value.charCodeAt(0);
                                shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0);
                            }
                    }
                });

                shortcuts[
                    (shortcut.ctrl ? 'ctrl' : '') + ',' +
                    (shortcut.alt ? 'alt' : '') + ',' +
                    (shortcut.shift ? 'shift' : '') + ',' +
                    shortcut.keyCode
                ] = shortcut;
            });

            return true;
        };
    };
});

// Included from: js/tinymce/classes/Editor.js

/**
 * Editor.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/*jshint scripturl:true */

/**
 * Include the base event class documentation.
 *
 * @include ../../../tools/docs/tinymce.Event.js
 */

/**
 * This class contains the core logic for a TinyMCE editor.
 *
 * @class tinymce.Editor
 * @mixes tinymce.util.Observable
 * @example
 * // Add a class to all paragraphs in the editor.
 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
 *
 * // Gets the current editors selection as text
 * tinymce.activeEditor.selection.getContent({format: 'text'});
 *
 * // Creates a new editor instance
 * var ed = new tinymce.Editor('textareaid', {
 *     some_setting: 1
 * }, tinymce.EditorManager);
 *
 * // Select each item the user clicks on
 * ed.on('click', function(e) {
 *     ed.selection.select(e.target);
 * });
 *
 * ed.render();
 */
define("tinymce/Editor", [
    "tinymce/dom/DOMUtils",
    "tinymce/dom/DomQuery",
    "tinymce/AddOnManager",
    "tinymce/NodeChange",
    "tinymce/html/Node",
    "tinymce/dom/Serializer",
    "tinymce/html/Serializer",
    "tinymce/dom/Selection",
    "tinymce/Formatter",
    "tinymce/UndoManager",
    "tinymce/EnterKey",
    "tinymce/ForceBlocks",
    "tinymce/EditorCommands",
    "tinymce/util/URI",
    "tinymce/dom/ScriptLoader",
    "tinymce/dom/EventUtils",
    "tinymce/WindowManager",
    "tinymce/html/Schema",
    "tinymce/html/DomParser",
    "tinymce/util/Quirks",
    "tinymce/Env",
    "tinymce/util/Tools",
    "tinymce/EditorObservable",
    "tinymce/Shortcuts"
], function(
    DOMUtils, DomQuery, AddOnManager, NodeChange, Node, DomSerializer, Serializer,
    Selection, Formatter, UndoManager, EnterKey, ForceBlocks, EditorCommands,
    URI, ScriptLoader, EventUtils, WindowManager,
    Schema, DomParser, Quirks, Env, Tools, EditorObservable, Shortcuts
) {
    // Shorten these names
    var DOM = DOMUtils.DOM, ThemeManager = AddOnManager.ThemeManager, PluginManager = AddOnManager.PluginManager;
    var extend = Tools.extend, each = Tools.each, explode = Tools.explode;
    var inArray = Tools.inArray, trim = Tools.trim, resolve = Tools.resolve;
    var Event = EventUtils.Event;
    var isGecko = Env.gecko, ie = Env.ie;

    /**
     * Include documentation for all the events.
     *
     * @include ../../../tools/docs/tinymce.Editor.js
     */

    /**
     * Constructs a editor instance by id.
     *
     * @constructor
     * @method Editor
     * @param {String} id Unique id for the editor.
     * @param {Object} settings Settings for the editor.
     * @param {tinymce.EditorManager} editorManager EditorManager instance.
     * @author Moxiecode
     */
    function Editor(id, settings, editorManager) {
        var self = this, documentBaseUrl, baseUri;

        documentBaseUrl = self.documentBaseUrl = editorManager.documentBaseURL;
        baseUri = editorManager.baseURI;

        /**
         * Name/value collection with editor settings.
         *
         * @property settings
         * @type Object
         * @example
         * // Get the value of the theme setting
         * tinymce.activeEditor.windowManager.alert("You are using the " + tinymce.activeEditor.settings.theme + " theme");
         */
        self.settings = settings = extend({
            id: id,
            theme: 'modern',
            delta_width: 0,
            delta_height: 0,
            popup_css: '',
            plugins: '',
            document_base_url: documentBaseUrl,
            add_form_submit_trigger: true,
            submit_patch: true,
            add_unload_trigger: true,
            convert_urls: true,
            relative_urls: true,
            remove_script_host: true,
            object_resizing: true,
            doctype: '<!DOCTYPE html>',
            visual: true,
            font_size_style_values: 'xx-small,x-small,small,medium,large,x-large,xx-large',

            // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
            font_size_legacy_values: 'xx-small,small,medium,large,x-large,xx-large,300%',
            forced_root_block: 'p',
            hidden_input: true,
            padd_empty_editor: true,
            render_ui: true,
            indentation: '30px',
            inline_styles: true,
            convert_fonts_to_spans: true,
            indent: 'simple',
            indent_before: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,ol,li,dl,dt,dd,area,table,thead,' +
                'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
            indent_after: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,ol,li,dl,dt,dd,area,table,thead,' +
                'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
            validate: true,
            entity_encoding: 'named',
            url_converter: self.convertURL,
            url_converter_scope: self,
            ie7_compat: true
        }, settings);

        AddOnManager.language = settings.language || 'en';
        AddOnManager.languageLoad = settings.language_load;

        AddOnManager.baseURL = editorManager.baseURL;

        /**
         * Editor instance id, normally the same as the div/textarea that was replaced.
         *
         * @property id
         * @type String
         */
        self.id = settings.id = id;

        /**
         * State to force the editor to return false on a isDirty call.
         *
         * @property isNotDirty
         * @type Boolean
         * @example
         * function ajaxSave() {
         *     var ed = tinymce.get('elm1');
         *
         *     // Save contents using some XHR call
         *     alert(ed.getContent());
         *
         *     ed.isNotDirty = true; // Force not dirty state
         * }
         */
        self.isNotDirty = true;

        /**
         * Name/Value object containting plugin instances.
         *
         * @property plugins
         * @type Object
         * @example
         * // Execute a method inside a plugin directly
         * tinymce.activeEditor.plugins.someplugin.someMethod();
         */
        self.plugins = {};

        /**
         * URI object to document configured for the TinyMCE instance.
         *
         * @property documentBaseURI
         * @type tinymce.util.URI
         * @example
         * // Get relative URL from the location of document_base_url
         * tinymce.activeEditor.documentBaseURI.toRelative('/somedir/somefile.htm');
         *
         * // Get absolute URL from the location of document_base_url
         * tinymce.activeEditor.documentBaseURI.toAbsolute('somefile.htm');
         */
        self.documentBaseURI = new URI(settings.document_base_url || documentBaseUrl, {
            base_uri: baseUri
        });

        /**
         * URI object to current document that holds the TinyMCE editor instance.
         *
         * @property baseURI
         * @type tinymce.util.URI
         * @example
         * // Get relative URL from the location of the API
         * tinymce.activeEditor.baseURI.toRelative('/somedir/somefile.htm');
         *
         * // Get absolute URL from the location of the API
         * tinymce.activeEditor.baseURI.toAbsolute('somefile.htm');
         */
        self.baseURI = baseUri;

        /**
         * Array with CSS files to load into the iframe.
         *
         * @property contentCSS
         * @type Array
         */
        self.contentCSS = [];

        /**
         * Array of CSS styles to add to head of document when the editor loads.
         *
         * @property contentStyles
         * @type Array
         */
        self.contentStyles = [];

        // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
        self.shortcuts = new Shortcuts(self);

        // Internal command handler objects
        self.execCommands = {};
        self.queryStateCommands = {};
        self.queryValueCommands = {};
        self.loadedCSS = {};

        if (settings.target) {
            self.targetElm = settings.target;
        }

        self.suffix = editorManager.suffix;
        self.editorManager = editorManager;
        self.inline = settings.inline;

        // Call setup
        editorManager.fire('SetupEditor', self);
        self.execCallback('setup', self);

        /**
         * Dom query instance with default scope to the editor document and default element is the body of the editor.
         *
         * @property $
         * @type tinymce.dom.DomQuery
         * @example
         * tinymce.activeEditor.$('p').css('color', 'red');
         * tinymce.activeEditor.$().append('<p>new</p>');
         */
        self.$ = DomQuery.overrideDefaults(function() {
            return {
                context: self.inline ? self.getBody() : self.getDoc(),
                element: self.getBody()
            };
        });
    }

    Editor.prototype = {
        /**
         * Renderes the editor/adds it to the page.
         *
         * @method render
         */
        render: function() {
            var self = this, settings = self.settings, id = self.id, suffix = self.suffix;

            function readyHandler() {
                DOM.unbind(window, 'ready', readyHandler);
                self.render();
            }

            // Page is not loaded yet, wait for it
            if (!Event.domLoaded) {
                DOM.bind(window, 'ready', readyHandler);
                return;
            }

            // Element not found, then skip initialization
            if (!self.getElement()) {
                return;
            }

            // No editable support old iOS versions etc
            if (!Env.contentEditable) {
                return;
            }

            // Hide target element early to prevent content flashing
            if (!settings.inline) {
                self.orgVisibility = self.getElement().style.visibility;
                self.getElement().style.visibility = 'hidden';
            } else {
                self.inline = true;
            }

            var form = self.getElement().form || DOM.getParent(id, 'form');
            if (form) {
                self.formElement = form;

                // Add hidden input for non input elements inside form elements
                if (settings.hidden_input && !/TEXTAREA|INPUT/i.test(self.getElement().nodeName)) {
                    DOM.insertAfter(DOM.create('input', {type: 'hidden', name: id}), id);
                    self.hasHiddenInput = true;
                }

                // Pass submit/reset from form to editor instance
                self.formEventDelegate = function(e) {
                    self.fire(e.type, e);
                };

                DOM.bind(form, 'submit reset', self.formEventDelegate);

                // Reset contents in editor when the form is reset
                self.on('reset', function() {
                    self.setContent(self.startContent, {format: 'raw'});
                });

                // Check page uses id="submit" or name="submit" for it's submit button
                if (settings.submit_patch && !form.submit.nodeType && !form.submit.length && !form._mceOldSubmit) {
                    form._mceOldSubmit = form.submit;
                    form.submit = function() {
                        self.editorManager.triggerSave();
                        self.isNotDirty = true;

                        return form._mceOldSubmit(form);
                    };
                }
            }

            /**
             * Window manager reference, use this to open new windows and dialogs.
             *
             * @property windowManager
             * @type tinymce.WindowManager
             * @example
             * // Shows an alert message
             * tinymce.activeEditor.windowManager.alert('Hello world!');
             *
             * // Opens a new dialog with the file.htm file and the size 320x240
             * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog.
             * tinymce.activeEditor.windowManager.open({
             *    url: 'file.htm',
             *    width: 320,
             *    height: 240
             * }, {
             *    custom_param: 1
             * });
             */
            self.windowManager = new WindowManager(self);

            if (settings.encoding == 'xml') {
                self.on('GetContent', function(e) {
                    if (e.save) {
                        e.content = DOM.encode(e.content);
                    }
                });
            }

            if (settings.add_form_submit_trigger) {
                self.on('submit', function() {
                    if (self.initialized) {
                        self.save();
                    }
                });
            }

            if (settings.add_unload_trigger) {
                self._beforeUnload = function() {
                    if (self.initialized && !self.destroyed && !self.isHidden()) {
                        self.save({format: 'raw', no_events: true, set_dirty: false});
                    }
                };

                self.editorManager.on('BeforeUnload', self._beforeUnload);
            }

            // Load scripts
            function loadScripts() {
                var scriptLoader = ScriptLoader.ScriptLoader;

                if (settings.language && settings.language != 'en' && !settings.language_url) {
                    settings.language_url = self.editorManager.baseURL + '/langs/' + settings.language + '.js';
                }

                if (settings.language_url) {
                    scriptLoader.add(settings.language_url);
                }

                if (settings.theme && typeof settings.theme != "function" &&
                    settings.theme.charAt(0) != '-' && !ThemeManager.urls[settings.theme]) {
                    var themeUrl = settings.theme_url;

                    if (themeUrl) {
                        themeUrl = self.documentBaseURI.toAbsolute(themeUrl);
                    } else {
                        themeUrl = 'themes/' + settings.theme + '/theme' + suffix + '.js';
                    }

                    ThemeManager.load(settings.theme, themeUrl);
                }

                if (Tools.isArray(settings.plugins)) {
                    settings.plugins = settings.plugins.join(' ');
                }

                each(settings.external_plugins, function(url, name) {
                    PluginManager.load(name, url);
                    settings.plugins += ' ' + name;
                });

                each(settings.plugins.split(/[ ,]/), function(plugin) {
                    plugin = trim(plugin);

                    if (plugin && !PluginManager.urls[plugin]) {
                        if (plugin.charAt(0) == '-') {
                            plugin = plugin.substr(1, plugin.length);

                            var dependencies = PluginManager.dependencies(plugin);

                            each(dependencies, function(dep) {
                                var defaultSettings = {
                                    prefix: 'plugins/',
                                    resource: dep,
                                    suffix: '/plugin' + suffix + '.js'
                                };

                                dep = PluginManager.createUrl(defaultSettings, dep);
                                PluginManager.load(dep.resource, dep);
                            });
                        } else {
                            PluginManager.load(plugin, {
                                prefix: 'plugins/',
                                resource: plugin,
                                suffix: '/plugin' + suffix + '.js'
                            });
                        }
                    }
                });

                scriptLoader.loadQueue(function() {
                    if (!self.removed) {
                        self.init();
                    }
                });
            }

            loadScripts();
        },

        /**
         * Initializes the editor this will be called automatically when
         * all plugins/themes and language packs are loaded by the rendered method.
         * This method will setup the iframe and create the theme and plugin instances.
         *
         * @method init
         */
        init: function() {
            var self = this, settings = self.settings, elm = self.getElement();
            var w, h, minHeight, n, o, Theme, url, bodyId, bodyClass, re, i, initializedPlugins = [];

            self.rtl = this.editorManager.i18n.rtl;
            self.editorManager.add(self);

            settings.aria_label = settings.aria_label || DOM.getAttrib(elm, 'aria-label', self.getLang('aria.rich_text_area'));

            /**
             * Reference to the theme instance that was used to generate the UI.
             *
             * @property theme
             * @type tinymce.Theme
             * @example
             * // Executes a method on the theme directly
             * tinymce.activeEditor.theme.someMethod();
             */
            if (settings.theme) {
                if (typeof settings.theme != "function") {
                    settings.theme = settings.theme.replace(/-/, '');
                    Theme = ThemeManager.get(settings.theme);
                    self.theme = new Theme(self, ThemeManager.urls[settings.theme]);

                    if (self.theme.init) {
                        self.theme.init(self, ThemeManager.urls[settings.theme] || self.documentBaseUrl.replace(/\/$/, ''), self.$);
                    }
                } else {
                    self.theme = settings.theme;
                }
            }

            function initPlugin(plugin) {
                var Plugin = PluginManager.get(plugin), pluginUrl, pluginInstance;

                pluginUrl = PluginManager.urls[plugin] || self.documentBaseUrl.replace(/\/$/, '');
                plugin = trim(plugin);
                if (Plugin && inArray(initializedPlugins, plugin) === -1) {
                    each(PluginManager.dependencies(plugin), function(dep) {
                        initPlugin(dep);
                    });

                    pluginInstance = new Plugin(self, pluginUrl, self.$);

                    self.plugins[plugin] = pluginInstance;

                    if (pluginInstance.init) {
                        pluginInstance.init(self, pluginUrl);
                        initializedPlugins.push(plugin);
                    }
                }
            }

            // Create all plugins
            each(settings.plugins.replace(/\-/g, '').split(/[ ,]/), initPlugin);

            // Measure box
            if (settings.render_ui && self.theme) {
                self.orgDisplay = elm.style.display;

                if (typeof settings.theme != "function") {
                    w = settings.width || elm.style.width || elm.offsetWidth;
                    h = settings.height || elm.style.height || elm.offsetHeight;
                    minHeight = settings.min_height || 100;
                    re = /^[0-9\.]+(|px)$/i;

                    if (re.test('' + w)) {
                        w = Math.max(parseInt(w, 10), 100);
                    }

                    if (re.test('' + h)) {
                        h = Math.max(parseInt(h, 10), minHeight);
                    }

                    // Render UI
                    o = self.theme.renderUI({
                        targetNode: elm,
                        width: w,
                        height: h,
                        deltaWidth: settings.delta_width,
                        deltaHeight: settings.delta_height
                    });

                    // Resize editor
                    if (!settings.content_editable) {
                        h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
                        if (h < minHeight) {
                            h = minHeight;
                        }
                    }
                } else {
                    o = settings.theme(self, elm);

                    // Convert element type to id:s
                    if (o.editorContainer.nodeType) {
                        o.editorContainer = o.editorContainer.id = o.editorContainer.id || self.id + "_parent";
                    }

                    // Convert element type to id:s
                    if (o.iframeContainer.nodeType) {
                        o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || self.id + "_iframecontainer";
                    }

                    // Use specified iframe height or the targets offsetHeight
                    h = o.iframeHeight || elm.offsetHeight;
                }

                self.editorContainer = o.editorContainer;
            }

            // Load specified content CSS last
            if (settings.content_css) {
                each(explode(settings.content_css), function(u) {
                    self.contentCSS.push(self.documentBaseURI.toAbsolute(u));
                });
            }

            // Load specified content CSS last
            if (settings.content_style) {
                self.contentStyles.push(settings.content_style);
            }

            // Content editable mode ends here
            if (settings.content_editable) {
                elm = n = o = null; // Fix IE leak
                return self.initContentBody();
            }

            self.iframeHTML = settings.doctype + '<html><head>';

            // We only need to override paths if we have to
            // IE has a bug where it remove site absolute urls to relative ones if this is specified
            if (settings.document_base_url != self.documentBaseUrl) {
                self.iframeHTML += '<base href="' + self.documentBaseURI.getURI() + '" />';
            }

            // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
            if (!Env.caretAfter && settings.ie7_compat) {
                self.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
            }

            self.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';

            // Load the CSS by injecting them into the HTML this will reduce "flicker"
            for (i = 0; i < self.contentCSS.length; i++) {
                var cssUrl = self.contentCSS[i];
                self.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + cssUrl + '" />';
                self.loadedCSS[cssUrl] = true;
            }

            bodyId = settings.body_id || 'tinymce';
            if (bodyId.indexOf('=') != -1) {
                bodyId = self.getParam('body_id', '', 'hash');
                bodyId = bodyId[self.id] || bodyId;
            }

            bodyClass = settings.body_class || '';
            if (bodyClass.indexOf('=') != -1) {
                bodyClass = self.getParam('body_class', '', 'hash');
                bodyClass = bodyClass[self.id] || '';
            }

            self.iframeHTML += '</head><body id="' + bodyId +
                '" class="mce-content-body ' + bodyClass +
                '" data-id="' + self.id + '"><br></body></html>';

            /*eslint no-script-url:0 */
            var domainRelaxUrl = 'javascript:(function(){' +
                'document.open();document.domain="' + document.domain + '";' +
                'var ed = window.parent.tinymce.get("' + self.id + '");document.write(ed.iframeHTML);' +
                'document.close();ed.initContentBody(true);})()';

            // Domain relaxing is required since the user has messed around with document.domain
            if (document.domain != location.hostname) {
                url = domainRelaxUrl;
            }

            // Create iframe
            // TODO: ACC add the appropriate description on this.
            var ifr = DOM.create('iframe', {
                id: self.id + "_ifr",
                //src: url || 'javascript:""', // Workaround for HTTPS warning in IE6/7
                frameBorder: '0',
                allowTransparency: "true",
                title: self.editorManager.translate(
                        "Rich Text Area. Press ALT-F9 for menu. " +
                        "Press ALT-F10 for toolbar. Press ALT-0 for help"
                ),
                style: {
                    width: '100%',
                    height: h,
                    display: 'block' // Important for Gecko to render the iframe correctly
                }
            });

            ifr.onload = function() {
                ifr.onload = null;
                self.fire("load");
            };

            DOM.setAttrib("src", url || 'javascript:""');

            n = DOM.add(o.iframeContainer, ifr);

            // Try accessing the document this will fail on IE when document.domain is set to the same as location.hostname
            // Then we have to force domain relaxing using the domainRelaxUrl approach very ugly!!
            if (ie) {
                try {
                    self.getDoc();
                } catch (e) {
                    n.src = url = domainRelaxUrl;
                }
            }

            self.contentAreaContainer = o.iframeContainer;
            self.iframeElement = ifr;

            if (o.editorContainer) {
                DOM.get(o.editorContainer).style.display = self.orgDisplay;
                self.hidden = DOM.isHidden(o.editorContainer);
            }

            self.getElement().style.display = 'none';
            DOM.setAttrib(self.id, 'aria-hidden', true);

            if (!url) {
                self.initContentBody();
            }

            elm = n = o = null; // Cleanup
        },

        /**
         * This method get called by the init method ones the iframe is loaded.
         * It will fill the iframe with contents, setups DOM and selection objects for the iframe.
         *
         * @method initContentBody
         * @private
         */
        initContentBody: function(skipWrite) {
            var self = this, settings = self.settings, targetElm = self.getElement(), doc = self.getDoc(), body, contentCssText;

            // Restore visibility on target element
            if (!settings.inline) {
                self.getElement().style.visibility = self.orgVisibility;
            }

            // Setup iframe body
            if (!skipWrite && !settings.content_editable) {
                doc.open();
                doc.write(self.iframeHTML);
                doc.close();
            }

            if (settings.content_editable) {
                self.on('remove', function() {
                    var bodyEl = this.getBody();

                    DOM.removeClass(bodyEl, 'mce-content-body');
                    DOM.removeClass(bodyEl, 'mce-edit-focus');
                    DOM.setAttrib(bodyEl, 'contentEditable', null);
                });

                DOM.addClass(targetElm, 'mce-content-body');
                self.contentDocument = doc = settings.content_document || document;
                self.contentWindow = settings.content_window || window;
                self.bodyElement = targetElm;

                // Prevent leak in IE
                settings.content_document = settings.content_window = null;

                // TODO: Fix this
                settings.root_name = targetElm.nodeName.toLowerCase();
            }

            // It will not steal focus while setting contentEditable
            body = self.getBody();
            body.disabled = true;

            if (!settings.readonly) {
                if (self.inline && DOM.getStyle(body, 'position', true) == 'static') {
                    body.style.position = 'relative';
                }

                body.contentEditable = self.getParam('content_editable_state', true);
            }

            body.disabled = false;

            /**
             * Schema instance, enables you to validate elements and it's children.
             *
             * @property schema
             * @type tinymce.html.Schema
             */
            self.schema = new Schema(settings);

            /**
             * DOM instance for the editor.
             *
             * @property dom
             * @type tinymce.dom.DOMUtils
             * @example
             * // Adds a class to all paragraphs within the editor
             * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
             */
            self.dom = new DOMUtils(doc, {
                keep_values: true,
                url_converter: self.convertURL,
                url_converter_scope: self,
                hex_colors: settings.force_hex_style_colors,
                class_filter: settings.class_filter,
                update_styles: true,
                root_element: self.inline ? self.getBody() : null,
                collect: settings.content_editable,
                schema: self.schema,
                onSetAttrib: function(e) {
                    self.fire('SetAttrib', e);
                }
            });

            /**
             * HTML parser will be used when contents is inserted into the editor.
             *
             * @property parser
             * @type tinymce.html.DomParser
             */
            self.parser = new DomParser(settings, self.schema);

            // Convert src and href into data-mce-src, data-mce-href and data-mce-style
            self.parser.addAttributeFilter('src,href,style,tabindex', function(nodes, name) {
                var i = nodes.length, node, dom = self.dom, value, internalName;

                while (i--) {
                    node = nodes[i];
                    value = node.attr(name);
                    internalName = 'data-mce-' + name;

                    // Add internal attribute if we need to we don't on a refresh of the document
                    if (!node.attributes.map[internalName]) {
                        if (name === "style") {
                            value = dom.serializeStyle(dom.parseStyle(value), node.name);

                            if (!value.length) {
                                value = null;
                            }

                            node.attr(internalName, value);
                            node.attr(name, value);
                        } else if (name === "tabindex") {
                            node.attr(internalName, value);
                            node.attr(name, null);
                        } else {
                            node.attr(internalName, self.convertURL(value, name, node.name));
                        }
                    }
                }
            });

            // Keep scripts from executing
            self.parser.addNodeFilter('script', function(nodes) {
                var i = nodes.length, node;

                while (i--) {
                    node = nodes[i];
                    node.attr('type', 'mce-' + (node.attr('type') || 'no/type'));
                }
            });

            self.parser.addNodeFilter('#cdata', function(nodes) {
                var i = nodes.length, node;

                while (i--) {
                    node = nodes[i];
                    node.type = 8;
                    node.name = '#comment';
                    node.value = '[CDATA[' + node.value + ']]';
                }
            });

            self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes) {
                var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();

                while (i--) {
                    node = nodes[i];

                    if (node.isEmpty(nonEmptyElements)) {
                        node.append(new Node('br', 1)).shortEnded = true;
                    }
                }
            });

            /**
             * DOM serializer for the editor. Will be used when contents is extracted from the editor.
             *
             * @property serializer
             * @type tinymce.dom.Serializer
             * @example
             * // Serializes the first paragraph in the editor into a string
             * tinymce.activeEditor.serializer.serialize(tinymce.activeEditor.dom.select('p')[0]);
             */
            self.serializer = new DomSerializer(settings, self);

            /**
             * Selection instance for the editor.
             *
             * @property selection
             * @type tinymce.dom.Selection
             * @example
             * // Sets some contents to the current selection in the editor
             * tinymce.activeEditor.selection.setContent('Some contents');
             *
             * // Gets the current selection
             * alert(tinymce.activeEditor.selection.getContent());
             *
             * // Selects the first paragraph found
             * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]);
             */
            self.selection = new Selection(self.dom, self.getWin(), self.serializer, self);

            /**
             * Formatter instance.
             *
             * @property formatter
             * @type tinymce.Formatter
             */
            self.formatter = new Formatter(self);

            /**
             * Undo manager instance, responsible for handling undo levels.
             *
             * @property undoManager
             * @type tinymce.UndoManager
             * @example
             * // Undoes the last modification to the editor
             * tinymce.activeEditor.undoManager.undo();
             */
            self.undoManager = new UndoManager(self);

            self.forceBlocks = new ForceBlocks(self);
            self.enterKey = new EnterKey(self);
            self.editorCommands = new EditorCommands(self);
            self._nodeChangeDispatcher = new NodeChange(self);

            self.fire('PreInit');

            if (!settings.browser_spellcheck && !settings.gecko_spellcheck) {
                doc.body.spellcheck = false; // Gecko
                DOM.setAttrib(body, "spellcheck", "false");
            }

            self.fire('PostRender');

            self.quirks = new Quirks(self);

            if (settings.directionality) {
                body.dir = settings.directionality;
            }

            if (settings.nowrap) {
                body.style.whiteSpace = "nowrap";
            }

            if (settings.protect) {
                self.on('BeforeSetContent', function(e) {
                    each(settings.protect, function(pattern) {
                        e.content = e.content.replace(pattern, function(str) {
                            return '<!--mce:protected ' + escape(str) + '-->';
                        });
                    });
                });
            }

            self.on('SetContent', function() {
                self.addVisual(self.getBody());
            });

            // Remove empty contents
            if (settings.padd_empty_editor) {
                self.on('PostProcess', function(e) {
                    e.content = e.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
                });
            }

            self.load({initial: true, format: 'html'});
            self.startContent = self.getContent({format: 'raw'});

            /**
             * Is set to true after the editor instance has been initialized
             *
             * @property initialized
             * @type Boolean
             * @example
             * function isEditorInitialized(editor) {
             *     return editor && editor.initialized;
             * }
             */
            self.initialized = true;
            self.bindPendingEventDelegates();

            self.fire('init');
            self.focus(true);
            self.nodeChanged({initial: true});
            self.execCallback('init_instance_callback', self);

            // Add editor specific CSS styles
            if (self.contentStyles.length > 0) {
                contentCssText = '';

                each(self.contentStyles, function(style) {
                    contentCssText += style + "\r\n";
                });

                self.dom.addStyle(contentCssText);
            }

            // Load specified content CSS last
            each(self.contentCSS, function(cssUrl) {
                if (!self.loadedCSS[cssUrl]) {
                    self.dom.loadCSS(cssUrl);
                    self.loadedCSS[cssUrl] = true;
                }
            });

            // Handle auto focus
            if (settings.auto_focus) {
                setTimeout(function() {
                    var ed = self.editorManager.get(settings.auto_focus);

                    ed.selection.select(ed.getBody(), 1);
                    ed.selection.collapse(1);
                    ed.getBody().focus();
                    ed.getWin().focus();
                }, 100);
            }

            // Clean up references for IE
            targetElm = doc = body = null;
        },

        /**
         * Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection
         * it will also place DOM focus inside the editor.
         *
         * @method focus
         * @param {Boolean} skipFocus Skip DOM focus. Just set is as the active editor.
         */
        focus: function(skipFocus) {
            var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, rng;
            var controlElm, doc = self.getDoc(), body;

            if (!skipFocus) {
                // Get selected control element
                rng = selection.getRng();
                if (rng.item) {
                    controlElm = rng.item(0);
                }

                self._refreshContentEditable();

                // Focus the window iframe
                if (!contentEditable) {
                    // WebKit needs this call to fire focusin event properly see #5948
                    // But Opera pre Blink engine will produce an empty selection so skip Opera
                    if (!Env.opera) {
                        self.getBody().focus();
                    }

                    self.getWin().focus();
                }

                // Focus the body as well since it's contentEditable
                if (isGecko || contentEditable) {
                    body = self.getBody();

                    // Check for setActive since it doesn't scroll to the element
                    if (body.setActive) {
                        // IE 11 sometimes throws "Invalid function" then fallback to focus
                        try {
                            body.setActive();
                        } catch (ex) {
                            body.focus();
                        }
                    } else {
                        body.focus();
                    }

                    if (contentEditable) {
                        selection.normalize();
                    }
                }

                // Restore selected control element
                // This is needed when for example an image is selected within a
                // layer a call to focus will then remove the control selection
                if (controlElm && controlElm.ownerDocument == doc) {
                    rng = doc.body.createControlRange();
                    rng.addElement(controlElm);
                    rng.select();
                }
            }

            if (self.editorManager.activeEditor != self) {
                if ((oed = self.editorManager.activeEditor)) {
                    oed.fire('deactivate', {relatedTarget: self});
                }

                self.fire('activate', {relatedTarget: oed});
            }

            self.editorManager.activeEditor = self;
        },

        /**
         * Executes a legacy callback. This method is useful to call old 2.x option callbacks.
         * There new event model is a better way to add callback so this method might be removed in the future.
         *
         * @method execCallback
         * @param {String} name Name of the callback to execute.
         * @return {Object} Return value passed from callback function.
         */
        execCallback: function(name) {
            var self = this, callback = self.settings[name], scope;

            if (!callback) {
                return;
            }

            // Look through lookup
            if (self.callbackLookup && (scope = self.callbackLookup[name])) {
                callback = scope.func;
                scope = scope.scope;
            }

            if (typeof(callback) === 'string') {
                scope = callback.replace(/\.\w+$/, '');
                scope = scope ? resolve(scope) : 0;
                callback = resolve(callback);
                self.callbackLookup = self.callbackLookup || {};
                self.callbackLookup[name] = {func: callback, scope: scope};
            }

            return callback.apply(scope || self, Array.prototype.slice.call(arguments, 1));
        },

        /**
         * Translates the specified string by replacing variables with language pack items it will also check if there is
         * a key mathcin the input.
         *
         * @method translate
         * @param {String} text String to translate by the language pack data.
         * @return {String} Translated string.
         */
        translate: function(text) {
            var lang = this.settings.language || 'en', i18n = this.editorManager.i18n;

            if (!text) {
                return '';
            }

            return i18n.data[lang + '.' + text] || text.replace(/\{\#([^\}]+)\}/g, function(a, b) {
                return i18n.data[lang + '.' + b] || '{#' + b + '}';
            });
        },

        /**
         * Returns a language pack item by name/key.
         *
         * @method getLang
         * @param {String} name Name/key to get from the language pack.
         * @param {String} defaultVal Optional default value to retrive.
         */
        getLang: function(name, defaultVal) {
            return (
                this.editorManager.i18n.data[(this.settings.language || 'en') + '.' + name] ||
                (defaultVal !== undefined ? defaultVal : '{#' + name + '}')
            );
        },

        /**
         * Returns a configuration parameter by name.
         *
         * @method getParam
         * @param {String} name Configruation parameter to retrive.
         * @param {String} defaultVal Optional default value to return.
         * @param {String} type Optional type parameter.
         * @return {String} Configuration parameter value or default value.
         * @example
         * // Returns a specific config value from the currently active editor
         * var someval = tinymce.activeEditor.getParam('myvalue');
         *
         * // Returns a specific config value from a specific editor instance by id
         * var someval2 = tinymce.get('my_editor').getParam('myvalue');
         */
        getParam: function(name, defaultVal, type) {
            var value = name in this.settings ? this.settings[name] : defaultVal, output;

            if (type === 'hash') {
                output = {};

                if (typeof(value) === 'string') {
                    each(value.indexOf('=') > 0 ? value.split(/[;,](?![^=;,]*(?:[;,]|$))/) : value.split(','), function(value) {
                        value = value.split('=');

                        if (value.length > 1) {
                            output[trim(value[0])] = trim(value[1]);
                        } else {
                            output[trim(value[0])] = trim(value);
                        }
                    });
                } else {
                    output = value;
                }

                return output;
            }

            return value;
        },

        /**
         * Distpaches out a onNodeChange event to all observers. This method should be called when you
         * need to update the UI states or element path etc.
         *
         * @method nodeChanged
         * @param {Object} args Optional args to pass to NodeChange event handlers.
         */
        nodeChanged: function(args) {
            this._nodeChangeDispatcher.nodeChanged(args);
        },

        /**
         * Adds a button that later gets created by the theme in the editors toolbars.
         *
         * @method addButton
         * @param {String} name Button name to add.
         * @param {Object} settings Settings object with title, cmd etc.
         * @example
         * // Adds a custom button to the editor that inserts contents when clicked
         * tinymce.init({
         *    ...
         *
         *    toolbar: 'example'
         *
         *    setup: function(ed) {
         *       ed.addButton('example', {
         *          title: 'My title',
         *          image: '../js/tinymce/plugins/example/img/example.gif',
         *          onclick: function() {
         *             ed.insertContent('Hello world!!');
         *          }
         *       });
         *    }
         * });
         */
        addButton: function(name, settings) {
            var self = this;

            if (settings.cmd) {
                settings.onclick = function() {
                    self.execCommand(settings.cmd);
                };
            }

            if (!settings.text && !settings.icon) {
                settings.icon = name;
            }

            self.buttons = self.buttons || {};
            settings.tooltip = settings.tooltip || settings.title;
            self.buttons[name] = settings;
        },

        /**
         * Adds a menu item to be used in the menus of the theme. There might be multiple instances
         * of this menu item for example it might be used in the main menus of the theme but also in
         * the context menu so make sure that it's self contained and supports multiple instances.
         *
         * @method addMenuItem
         * @param {String} name Menu item name to add.
         * @param {Object} settings Settings object with title, cmd etc.
         * @example
         * // Adds a custom menu item to the editor that inserts contents when clicked
         * // The context option allows you to add the menu item to an existing default menu
         * tinymce.init({
         *    ...
         *
         *    setup: function(ed) {
         *       ed.addMenuItem('example', {
         *          text: 'My menu item',
         *          context: 'tools',
         *          onclick: function() {
         *             ed.insertContent('Hello world!!');
         *          }
         *       });
         *    }
         * });
         */
        addMenuItem: function(name, settings) {
            var self = this;

            if (settings.cmd) {
                settings.onclick = function() {
                    self.execCommand(settings.cmd);
                };
            }

            self.menuItems = self.menuItems || {};
            self.menuItems[name] = settings;
        },

        /**
         * Adds a custom command to the editor, you can also override existing commands with this method.
         * The command that you add can be executed with execCommand.
         *
         * @method addCommand
         * @param {String} name Command name to add/override.
         * @param {addCommandCallback} callback Function to execute when the command occurs.
         * @param {Object} scope Optional scope to execute the function in.
         * @example
         * // Adds a custom command that later can be executed using execCommand
         * tinymce.init({
         *    ...
         *
         *    setup: function(ed) {
         *       // Register example command
         *       ed.addCommand('mycommand', function(ui, v) {
         *          ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({format: 'text'}));
         *       });
         *    }
         * });
         */
        addCommand: function(name, callback, scope) {
            /**
             * Callback function that gets called when a command is executed.
             *
             * @callback addCommandCallback
             * @param {Boolean} ui Display UI state true/false.
             * @param {Object} value Optional value for command.
             * @return {Boolean} True/false state if the command was handled or not.
             */
            this.execCommands[name] = {func: callback, scope: scope || this};
        },

        /**
         * Adds a custom query state command to the editor, you can also override existing commands with this method.
         * The command that you add can be executed with queryCommandState function.
         *
         * @method addQueryStateHandler
         * @param {String} name Command name to add/override.
         * @param {addQueryStateHandlerCallback} callback Function to execute when the command state retrival occurs.
         * @param {Object} scope Optional scope to execute the function in.
         */
        addQueryStateHandler: function(name, callback, scope) {
            /**
             * Callback function that gets called when a queryCommandState is executed.
             *
             * @callback addQueryStateHandlerCallback
             * @return {Boolean} True/false state if the command is enabled or not like is it bold.
             */
            this.queryStateCommands[name] = {func: callback, scope: scope || this};
        },

        /**
         * Adds a custom query value command to the editor, you can also override existing commands with this method.
         * The command that you add can be executed with queryCommandValue function.
         *
         * @method addQueryValueHandler
         * @param {String} name Command name to add/override.
         * @param {addQueryValueHandlerCallback} callback Function to execute when the command value retrival occurs.
         * @param {Object} scope Optional scope to execute the function in.
         */
        addQueryValueHandler: function(name, callback, scope) {
            /**
             * Callback function that gets called when a queryCommandValue is executed.
             *
             * @callback addQueryValueHandlerCallback
             * @return {Object} Value of the command or undefined.
             */
            this.queryValueCommands[name] = {func: callback, scope: scope || this};
        },

        /**
         * Adds a keyboard shortcut for some command or function.
         *
         * @method addShortcut
         * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
         * @param {String} desc Text description for the command.
         * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
         * @param {Object} sc Optional scope to execute the function in.
         * @return {Boolean} true/false state if the shortcut was added or not.
         */
        addShortcut: function(pattern, desc, cmdFunc, scope) {
            this.shortcuts.add(pattern, desc, cmdFunc, scope);
        },

        /**
         * Executes a command on the current instance. These commands can be TinyMCE internal commands prefixed with "mce" or
         * they can be build in browser commands such as "Bold". A compleate list of browser commands is available on MSDN or Mozilla.org.
         * This function will dispatch the execCommand function on each plugin, theme or the execcommand_callback option if none of these
         * return true it will handle the command as a internal browser command.
         *
         * @method execCommand
         * @param {String} cmd Command name to execute, for example mceLink or Bold.
         * @param {Boolean} ui True/false state if a UI (dialog) should be presented or not.
         * @param {mixed} value Optional command value, this can be anything.
         * @param {Object} a Optional arguments object.
         */
        execCommand: function(cmd, ui, value, args) {
            var self = this, state = 0, cmdItem;

            if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(cmd) && (!args || !args.skip_focus)) {
                self.focus();
            }

            args = extend({}, args);
            args = self.fire('BeforeExecCommand', {command: cmd, ui: ui, value: value});
            if (args.isDefaultPrevented()) {
                return false;
            }

            // Registred commands
            if ((cmdItem = self.execCommands[cmd])) {
                // Fall through on true
                if (cmdItem.func.call(cmdItem.scope, ui, value) !== true) {
                    self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
                    return true;
                }
            }

            // Plugin commands
            each(self.plugins, function(p) {
                if (p.execCommand && p.execCommand(cmd, ui, value)) {
                    self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
                    state = true;
                    return false;
                }
            });

            if (state) {
                return state;
            }

            // Theme commands
            if (self.theme && self.theme.execCommand && self.theme.execCommand(cmd, ui, value)) {
                self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
                return true;
            }

            // Editor commands
            if (self.editorCommands.execCommand(cmd, ui, value)) {
                self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
                return true;
            }

            // Browser commands
            try {
                state = self.getDoc().execCommand(cmd, ui, value);
            } catch (ex) {
                // Ignore old IE errors
            }

            if (state) {
                self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
                return true;
            }

            return false;
        },

        /**
         * Returns a command specific state, for example if bold is enabled or not.
         *
         * @method queryCommandState
         * @param {string} cmd Command to query state from.
         * @return {Boolean} Command specific state, for example if bold is enabled or not.
         */
        queryCommandState: function(cmd) {
            var self = this, queryItem, returnVal;

            // Is hidden then return undefined
            if (self._isHidden()) {
                return;
            }

            // Registred commands
            if ((queryItem = self.queryStateCommands[cmd])) {
                returnVal = queryItem.func.call(queryItem.scope);

                // Fall though on non boolean returns
                if (returnVal === true || returnVal === false) {
                    return returnVal;
                }
            }

            // Editor commands
            returnVal = self.editorCommands.queryCommandState(cmd);
            if (returnVal !== -1) {
                return returnVal;
            }

            // Browser commands
            try {
                return self.getDoc().queryCommandState(cmd);
            } catch (ex) {
                // Fails sometimes see bug: 1896577
            }
        },

        /**
         * Returns a command specific value, for example the current font size.
         *
         * @method queryCommandValue
         * @param {string} cmd Command to query value from.
         * @return {Object} Command specific value, for example the current font size.
         */
        queryCommandValue: function(cmd) {
            var self = this, queryItem, returnVal;

            // Is hidden then return undefined
            if (self._isHidden()) {
                return;
            }

            // Registred commands
            if ((queryItem = self.queryValueCommands[cmd])) {
                returnVal = queryItem.func.call(queryItem.scope);

                // Fall though on true
                if (returnVal !== true) {
                    return returnVal;
                }
            }

            // Editor commands
            returnVal = self.editorCommands.queryCommandValue(cmd);
            if (returnVal !== undefined) {
                return returnVal;
            }

            // Browser commands
            try {
                return self.getDoc().queryCommandValue(cmd);
            } catch (ex) {
                // Fails sometimes see bug: 1896577
            }
        },

        /**
         * Shows the editor and hides any textarea/div that the editor is supposed to replace.
         *
         * @method show
         */
        show: function() {
            var self = this;

            if (self.hidden) {
                self.hidden = false;

                if (self.inline) {
                    self.getBody().contentEditable = true;
                } else {
                    DOM.show(self.getContainer());
                    DOM.hide(self.id);
                }

                self.load();
                self.fire('show');
            }
        },

        /**
         * Hides the editor and shows any textarea/div that the editor is supposed to replace.
         *
         * @method hide
         */
        hide: function() {
            var self = this, doc = self.getDoc();

            if (!self.hidden) {
                // Fixed bug where IE has a blinking cursor left from the editor
                if (ie && doc && !self.inline) {
                    doc.execCommand('SelectAll');
                }

                // We must save before we hide so Safari doesn't crash
                self.save();

                if (self.inline) {
                    self.getBody().contentEditable = false;

                    // Make sure the editor gets blurred
                    if (self == self.editorManager.focusedEditor) {
                        self.editorManager.focusedEditor = null;
                    }
                } else {
                    DOM.hide(self.getContainer());
                    DOM.setStyle(self.id, 'display', self.orgDisplay);
                }

                self.hidden = true;
                self.fire('hide');
            }
        },

        /**
         * Returns true/false if the editor is hidden or not.
         *
         * @method isHidden
         * @return {Boolean} True/false if the editor is hidden or not.
         */
        isHidden: function() {
            return !!this.hidden;
        },

        /**
         * Sets the progress state, this will display a throbber/progess for the editor.
         * This is ideal for asycronous operations like an AJAX save call.
         *
         * @method setProgressState
         * @param {Boolean} state Boolean state if the progress should be shown or hidden.
         * @param {Number} time Optional time to wait before the progress gets shown.
         * @return {Boolean} Same as the input state.
         * @example
         * // Show progress for the active editor
         * tinymce.activeEditor.setProgressState(true);
         *
         * // Hide progress for the active editor
         * tinymce.activeEditor.setProgressState(false);
         *
         * // Show progress after 3 seconds
         * tinymce.activeEditor.setProgressState(true, 3000);
         */
        setProgressState: function(state, time) {
            this.fire('ProgressState', {state: state, time: time});
        },

        /**
         * Loads contents from the textarea or div element that got converted into an editor instance.
         * This method will move the contents from that textarea or div into the editor by using setContent
         * so all events etc that method has will get dispatched as well.
         *
         * @method load
         * @param {Object} args Optional content object, this gets passed around through the whole load process.
         * @return {String} HTML string that got set into the editor.
         */
        load: function(args) {
            var self = this, elm = self.getElement(), html;

            if (elm) {
                args = args || {};
                args.load = true;

                html = self.setContent(elm.value !== undefined ? elm.value : elm.innerHTML, args);
                args.element = elm;

                if (!args.no_events) {
                    self.fire('LoadContent', args);
                }

                args.element = elm = null;

                return html;
            }
        },

        /**
         * Saves the contents from a editor out to the textarea or div element that got converted into an editor instance.
         * This method will move the HTML contents from the editor into that textarea or div by getContent
         * so all events etc that method has will get dispatched as well.
         *
         * @method save
         * @param {Object} args Optional content object, this gets passed around through the whole save process.
         * @return {String} HTML string that got set into the textarea/div.
         */
        save: function(args) {
            var self = this, elm = self.getElement(), html, form;

            if (!elm || !self.initialized) {
                return;
            }

            args = args || {};
            args.save = true;

            args.element = elm;
            html = args.content = self.getContent(args);

            if (!args.no_events) {
                self.fire('SaveContent', args);
            }

            html = args.content;

            if (!/TEXTAREA|INPUT/i.test(elm.nodeName)) {
                // Update DIV element when not in inline mode
                if (!self.inline) {
                    elm.innerHTML = html;
                }

                // Update hidden form element
                if ((form = DOM.getParent(self.id, 'form'))) {
                    each(form.elements, function(elm) {
                        if (elm.name == self.id) {
                            elm.value = html;
                            return false;
                        }
                    });
                }
            } else {
                elm.value = html;
            }

            args.element = elm = null;

            if (args.set_dirty !== false) {
                self.isNotDirty = true;
            }

            return html;
        },

        /**
         * Sets the specified content to the editor instance, this will cleanup the content before it gets set using
         * the different cleanup rules options.
         *
         * @method setContent
         * @param {String} content Content to set to editor, normally HTML contents but can be other formats as well.
         * @param {Object} args Optional content object, this gets passed around through the whole set process.
         * @return {String} HTML string that got set into the editor.
         * @example
         * // Sets the HTML contents of the activeEditor editor
         * tinymce.activeEditor.setContent('<span>some</span> html');
         *
         * // Sets the raw contents of the activeEditor editor
         * tinymce.activeEditor.setContent('<span>some</span> html', {format: 'raw'});
         *
         * // Sets the content of a specific editor (my_editor in this example)
         * tinymce.get('my_editor').setContent(data);
         *
         * // Sets the bbcode contents of the activeEditor editor if the bbcode plugin was added
         * tinymce.activeEditor.setContent('[b]some[/b] html', {format: 'bbcode'});
         */
        setContent: function(content, args) {
            var self = this, body = self.getBody(), forcedRootBlockName;

            // Setup args object
            args = args || {};
            args.format = args.format || 'html';
            args.set = true;
            args.content = content;

            // Do preprocessing
            if (!args.no_events) {
                self.fire('BeforeSetContent', args);
            }

            content = args.content;

            // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
            // It will also be impossible to place the caret in the editor unless there is a BR element present
            if (content.length === 0 || /^\s+$/.test(content)) {
                forcedRootBlockName = self.settings.forced_root_block;

                // Check if forcedRootBlock is configured and that the block is a valid child of the body
                if (forcedRootBlockName && self.schema.isValidChild(body.nodeName.toLowerCase(), forcedRootBlockName.toLowerCase())) {
                    // Padd with bogus BR elements on modern browsers and IE 7 and 8 since they don't render empty P tags properly
                    content = ie && ie < 11 ? '' : '<br data-mce-bogus="1">';
                    content = self.dom.createHTML(forcedRootBlockName, self.settings.forced_root_block_attrs, content);
                } else if (!ie) {
                    // We need to add a BR when forced_root_block is disabled on non IE browsers to place the caret
                    content = '<br data-mce-bogus="1">';
                }

                self.dom.setHTML(body, content);

                self.fire('SetContent', args);
            } else {
                // Parse and serialize the html
                if (args.format !== 'raw') {
                    content = new Serializer({}, self.schema).serialize(
                        self.parser.parse(content, {isRootContent: true})
                    );
                }

                // Set the new cleaned contents to the editor
                args.content = trim(content);
                self.dom.setHTML(body, args.content);

                // Do post processing
                if (!args.no_events) {
                    self.fire('SetContent', args);
                }

                // Don't normalize selection if the focused element isn't the body in
                // content editable mode since it will steal focus otherwise
                /*if (!self.settings.content_editable || document.activeElement === self.getBody()) {
                    self.selection.normalize();
                }*/
            }

            return args.content;
        },

        /**
         * Gets the content from the editor instance, this will cleanup the content before it gets returned using
         * the different cleanup rules options.
         *
         * @method getContent
         * @param {Object} args Optional content object, this gets passed around through the whole get process.
         * @return {String} Cleaned content string, normally HTML contents.
         * @example
         * // Get the HTML contents of the currently active editor
         * console.debug(tinymce.activeEditor.getContent());
         *
         * // Get the raw contents of the currently active editor
         * tinymce.activeEditor.getContent({format: 'raw'});
         *
         * // Get content of a specific editor:
         * tinymce.get('content id').getContent()
         */
        getContent: function(args) {
            var self = this, content, body = self.getBody();

            // Setup args object
            args = args || {};
            args.format = args.format || 'html';
            args.get = true;
            args.getInner = true;

            // Do preprocessing
            if (!args.no_events) {
                self.fire('BeforeGetContent', args);
            }

            // Get raw contents or by default the cleaned contents
            if (args.format == 'raw') {
                content = body.innerHTML;
            } else if (args.format == 'text') {
                content = body.innerText || body.textContent;
            } else {
                content = self.serializer.serialize(body, args);
            }

            // Trim whitespace in beginning/end of HTML
            if (args.format != 'text') {
                args.content = trim(content);
            } else {
                args.content = content;
            }

            // Do post processing
            if (!args.no_events) {
                self.fire('GetContent', args);
            }

            return args.content;
        },

        /**
         * Inserts content at caret position.
         *
         * @method insertContent
         * @param {String} content Content to insert.
         * @param {Object} args Optional args to pass to insert call.
         */
        insertContent: function(content, args) {
            if (args) {
                content = extend({content: content}, args);
            }

            this.execCommand('mceInsertContent', false, content);
        },

        /**
         * Returns true/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
         *
         * @method isDirty
         * @return {Boolean} True/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
         * @example
         * if (tinymce.activeEditor.isDirty())
         *     alert("You must save your contents.");
         */
        isDirty: function() {
            return !this.isNotDirty;
        },

        /**
         * Returns the editors container element. The container element wrappes in
         * all the elements added to the page for the editor. Such as UI, iframe etc.
         *
         * @method getContainer
         * @return {Element} HTML DOM element for the editor container.
         */
        getContainer: function() {
            var self = this;

            if (!self.container) {
                self.container = DOM.get(self.editorContainer || self.id + '_parent');
            }

            return self.container;
        },

        /**
         * Returns the editors content area container element. The this element is the one who
         * holds the iframe or the editable element.
         *
         * @method getContentAreaContainer
         * @return {Element} HTML DOM element for the editor area container.
         */
        getContentAreaContainer: function() {
            return this.contentAreaContainer;
        },

        /**
         * Returns the target element/textarea that got replaced with a TinyMCE editor instance.
         *
         * @method getElement
         * @return {Element} HTML DOM element for the replaced element.
         */
        getElement: function() {
            if (!this.targetElm) {
                this.targetElm = DOM.get(this.id);
            }

            return this.targetElm;
        },

        /**
         * Returns the iframes window object.
         *
         * @method getWin
         * @return {Window} Iframe DOM window object.
         */
        getWin: function() {
            var self = this, elm;

            if (!self.contentWindow) {
                elm = self.iframeElement;

                if (elm) {
                    self.contentWindow = elm.contentWindow;
                }
            }

            return self.contentWindow;
        },

        /**
         * Returns the iframes document object.
         *
         * @method getDoc
         * @return {Document} Iframe DOM document object.
         */
        getDoc: function() {
            var self = this, win;

            if (!self.contentDocument) {
                win = self.getWin();

                if (win) {
                    self.contentDocument = win.document;
                }
            }

            return self.contentDocument;
        },

        /**
         * Returns the iframes body element.
         *
         * @method getBody
         * @return {Element} Iframe body element.
         */
        getBody: function() {
            return this.bodyElement || this.getDoc().body;
        },

        /**
         * URL converter function this gets executed each time a user adds an img, a or
         * any other element that has a URL in it. This will be called both by the DOM and HTML
         * manipulation functions.
         *
         * @method convertURL
         * @param {string} url URL to convert.
         * @param {string} name Attribute name src, href etc.
         * @param {string/HTMLElement} elm Tag name or HTML DOM element depending on HTML or DOM insert.
         * @return {string} Converted URL string.
         */
        convertURL: function(url, name, elm) {
            var self = this, settings = self.settings;

            // Use callback instead
            if (settings.urlconverter_callback) {
                return self.execCallback('urlconverter_callback', url, elm, true, name);
            }

            // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
            if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0 || url.length === 0) {
                return url;
            }

            // Convert to relative
            if (settings.relative_urls) {
                return self.documentBaseURI.toRelative(url);
            }

            // Convert to absolute
            url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);

            return url;
        },

        /**
         * Adds visual aid for tables, anchors etc so they can be more easily edited inside the editor.
         *
         * @method addVisual
         * @param {Element} elm Optional root element to loop though to find tables etc that needs the visual aid.
         */
        addVisual: function(elm) {
            var self = this, settings = self.settings, dom = self.dom, cls;

            elm = elm || self.getBody();

            if (self.hasVisual === undefined) {
                self.hasVisual = settings.visual;
            }

            each(dom.select('table,a', elm), function(elm) {
                var value;

                switch (elm.nodeName) {
                    case 'TABLE':
                        cls = settings.visual_table_class || 'mce-item-table';
                        value = dom.getAttrib(elm, 'border');

                        if ((!value || value == '0') && self.hasVisual) {
                            dom.addClass(elm, cls);
                        } else {
                            dom.removeClass(elm, cls);
                        }

                        return;

                    case 'A':
                        if (!dom.getAttrib(elm, 'href', false)) {
                            value = dom.getAttrib(elm, 'name') || elm.id;
                            cls = settings.visual_anchor_class || 'mce-item-anchor';

                            if (value && self.hasVisual) {
                                dom.addClass(elm, cls);
                            } else {
                                dom.removeClass(elm, cls);
                            }
                        }

                        return;
                }
            });

            self.fire('VisualAid', {element: elm, hasVisual: self.hasVisual});
        },

        /**
         * Removes the editor from the dom and tinymce collection.
         *
         * @method remove
         */
        remove: function() {
            var self = this;

            if (!self.removed) {
                self.save();
                self.removed = 1;

                // Remove any hidden input
                if (self.hasHiddenInput) {
                    DOM.remove(self.getElement().nextSibling);
                }

                if (!self.inline) {
                    // IE 9 has a bug where the selection stops working if you place the
                    // caret inside the editor then remove the iframe
                    if (ie && ie < 10) {
                        self.getDoc().execCommand('SelectAll', false, null);
                    }

                    DOM.setStyle(self.id, 'display', self.orgDisplay);
                    self.getBody().onload = null; // Prevent #6816

                    // Don't clear the window or document if content editable
                    // is enabled since other instances might still be present
                    Event.unbind(self.getWin());
                    Event.unbind(self.getDoc());
                }

                var elm = self.getContainer();
                Event.unbind(self.getBody());
                Event.unbind(elm);

                self.fire('remove');

                self.editorManager.remove(self);
                DOM.remove(elm);
                self.destroy();
            }
        },

        /**
         * Destroys the editor instance by removing all events, element references or other resources
         * that could leak memory. This method will be called automatically when the page is unloaded
         * but you can also call it directly if you know what you are doing.
         *
         * @method destroy
         * @param {Boolean} automatic Optional state if the destroy is an automatic destroy or user called one.
         */
        destroy: function(automatic) {
            var self = this, form;

            // One time is enough
            if (self.destroyed) {
                return;
            }

            // If user manually calls destroy and not remove
            // Users seems to have logic that calls destroy instead of remove
            if (!automatic && !self.removed) {
                self.remove();
                return;
            }

            // We must unbind on Gecko since it would otherwise produce the pesky "attempt
            // to run compile-and-go script on a cleared scope" message
            if (automatic && isGecko) {
                Event.unbind(self.getDoc());
                Event.unbind(self.getWin());
                Event.unbind(self.getBody());
            }

            if (!automatic) {
                self.editorManager.off('beforeunload', self._beforeUnload);

                // Manual destroy
                if (self.theme && self.theme.destroy) {
                    self.theme.destroy();
                }

                // Destroy controls, selection and dom
                self.selection.destroy();
                self.dom.destroy();
            }

            form = self.formElement;
            if (form) {
                if (form._mceOldSubmit) {
                    form.submit = form._mceOldSubmit;
                    form._mceOldSubmit = null;
                }

                DOM.unbind(form, 'submit reset', self.formEventDelegate);
            }

            self.contentAreaContainer = self.formElement = self.container = self.editorContainer = null;
            self.bodyElement = self.contentDocument = self.contentWindow = null;
            self.iframeElement = self.targetElm = null;

            if (self.selection) {
                self.selection = self.selection.win = self.selection.dom = self.selection.dom.doc = null;
            }

            self.destroyed = 1;
        },

        // Internal functions

        _refreshContentEditable: function() {
            var self = this, body, parent;

            // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
            if (self._isHidden()) {
                body = self.getBody();
                parent = body.parentNode;

                parent.removeChild(body);
                parent.appendChild(body);

                body.focus();
            }
        },

        _isHidden: function() {
            var sel;

            if (!isGecko) {
                return 0;
            }

            // Weird, wheres that cursor selection?
            sel = this.selection.getSel();
            return (!sel || !sel.rangeCount || sel.rangeCount === 0);
        }
    };

    extend(Editor.prototype, EditorObservable);

    return Editor;
});

// Included from: js/tinymce/classes/util/I18n.js

/**
 * I18n.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * I18n class that handles translation of TinyMCE UI.
 * Uses po style with csharp style parameters.
 *
 * @class tinymce.util.I18n
 */
define("tinymce/util/I18n", [], function() {
    "use strict";

    var data = {};

    return {
        /**
         * Property gets set to true if a RTL language pack was loaded.
         *
         * @property rtl
         * @type Boolean
         */
        rtl: false,

        /**
         * Adds translations for a specific language code.
         *
         * @method add
         * @param {String} code Language code like sv_SE.
         * @param {Array} items Name/value array with English en_US to sv_SE.
         */
        add: function(code, items) {
            for (var name in items) {
                data[name] = items[name];
            }

            this.rtl = this.rtl || data._dir === 'rtl';
        },

        /**
         * Translates the specified text.
         *
         * It has a few formats:
         * I18n.translate("Text");
         * I18n.translate(["Text {0}/{1}", 0, 1]);
         * I18n.translate({raw: "Raw string"});
         *
         * @method translate
         * @param {String/Object/Array} text Text to translate.
         * @return {String} String that got translated.
         */
        translate: function(text) {
            if (typeof(text) == "undefined") {
                return text;
            }

            if (typeof(text) != "string" && text.raw) {
                return text.raw;
            }

            if (text.push) {
                var values = text.slice(1);

                text = (data[text[0]] || text[0]).replace(/\{([^\}]+)\}/g, function(match1, match2) {
                    return values[match2];
                });
            }

            return data[text] || text;
        },

        data: data
    };
});

// Included from: js/tinymce/classes/FocusManager.js

/**
 * FocusManager.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class manages the focus/blur state of the editor. This class is needed since some
 * browsers fire false focus/blur states when the selection is moved to a UI dialog or similar.
 *
 * This class will fire two events focus and blur on the editor instances that got affected.
 * It will also handle the restore of selection when the focus is lost and returned.
 *
 * @class tinymce.FocusManager
 */
define("tinymce/FocusManager", [
    "tinymce/dom/DOMUtils",
    "tinymce/Env"
], function(DOMUtils, Env) {
    var selectionChangeHandler, documentFocusInHandler, documentMouseUpHandler, DOM = DOMUtils.DOM;

    /**
     * Constructs a new focus manager instance.
     *
     * @constructor FocusManager
     * @param {tinymce.EditorManager} editorManager Editor manager instance to handle focus for.
     */
    function FocusManager(editorManager) {
        function getActiveElement() {
            try {
                return document.activeElement;
            } catch (ex) {
                // IE sometimes fails to get the activeElement when resizing table
                // TODO: Investigate this
                return document.body;
            }
        }

        // We can't store a real range on IE 11 since it gets mutated so we need to use a bookmark object
        // TODO: Move this to a separate range utils class since it's it's logic is present in Selection as well.
        function createBookmark(dom, rng) {
            if (rng && rng.startContainer) {
                // Verify that the range is within the root of the editor
                if (!dom.isChildOf(rng.startContainer, dom.getRoot()) || !dom.isChildOf(rng.endContainer, dom.getRoot())) {
                    return;
                }

                return {
                    startContainer: rng.startContainer,
                    startOffset: rng.startOffset,
                    endContainer: rng.endContainer,
                    endOffset: rng.endOffset
                };
            }

            return rng;
        }

        function bookmarkToRng(editor, bookmark) {
            var rng;

            if (bookmark.startContainer) {
                rng = editor.getDoc().createRange();
                rng.setStart(bookmark.startContainer, bookmark.startOffset);
                rng.setEnd(bookmark.endContainer, bookmark.endOffset);
            } else {
                rng = bookmark;
            }

            return rng;
        }

        function isUIElement(elm) {
            return !!DOM.getParent(elm, FocusManager.isEditorUIElement);
        }

        function registerEvents(e) {
            var editor = e.editor;

            editor.on('init', function() {
                // Gecko/WebKit has ghost selections in iframes and IE only has one selection per browser tab
                if (editor.inline || Env.ie) {
                    // Use the onbeforedeactivate event when available since it works better see #7023
                    if ("onbeforedeactivate" in document && Env.ie < 9) {
                        editor.dom.bind(editor.getBody(), 'beforedeactivate', function() {
                            try {
                                editor.lastRng = editor.selection.getRng();
                            } catch (ex) {
                                // IE throws "Unexcpected call to method or property access" some times so lets ignore it
                            }
                        });
                    } else {
                        // On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes
                        editor.on('nodechange mouseup keyup', function(e) {
                            var node = getActiveElement();

                            // Only act on manual nodechanges
                            if (e.type == 'nodechange' && e.selectionChange) {
                                return;
                            }

                            // IE 11 reports active element as iframe not body of iframe
                            if (node && node.id == editor.id + '_ifr') {
                                node = editor.getBody();
                            }

                            if (editor.dom.isChildOf(node, editor.getBody())) {
                                editor.lastRng = editor.selection.getRng();
                            }
                        });
                    }

                    // Handles the issue with WebKit not retaining selection within inline document
                    // If the user releases the mouse out side the body since a mouse up event wont occur on the body
                    if (Env.webkit && !selectionChangeHandler) {
                        selectionChangeHandler = function() {
                            var activeEditor = editorManager.activeEditor;

                            if (activeEditor && activeEditor.selection) {
                                var rng = activeEditor.selection.getRng();

                                // Store when it's non collapsed
                                if (rng && !rng.collapsed) {
                                    editor.lastRng = rng;
                                }
                            }
                        };

                        DOM.bind(document, 'selectionchange', selectionChangeHandler);
                    }
                }
            });

            editor.on('setcontent', function() {
                editor.lastRng = null;
            });

            // Remove last selection bookmark on mousedown see #6305
            editor.on('mousedown', function() {
                editor.selection.lastFocusBookmark = null;
            });

            editor.on('focusin', function() {
                var focusedEditor = editorManager.focusedEditor;

                if (editor.selection.lastFocusBookmark) {
                    editor.selection.setRng(bookmarkToRng(editor, editor.selection.lastFocusBookmark));
                    editor.selection.lastFocusBookmark = null;
                }

                if (focusedEditor != editor) {
                    if (focusedEditor) {
                        focusedEditor.fire('blur', {focusedEditor: editor});
                    }

                    editorManager.activeEditor = editor;
                    editorManager.focusedEditor = editor;
                    editor.fire('focus', {blurredEditor: focusedEditor});
                    editor.focus(true);
                }

                editor.lastRng = null;
            });

            editor.on('focusout', function() {
                window.setTimeout(function() {
                    var focusedEditor = editorManager.focusedEditor;

                    // Still the same editor the the blur was outside any editor UI
                    if (!isUIElement(getActiveElement()) && focusedEditor == editor) {
                        editor.fire('blur', {focusedEditor: null});
                        editorManager.focusedEditor = null;

                        // Make sure selection is valid could be invalid if the editor is blured and removed before the timeout occurs
                        if (editor.selection) {
                            editor.selection.lastFocusBookmark = null;
                        }
                    }
                }, 0);
            });

            // Check if focus is moved to an element outside the active editor by checking if the target node
            // isn't within the body of the activeEditor nor a UI element such as a dialog child control
            if (!documentFocusInHandler) {
                documentFocusInHandler = function(e) {
                    var activeEditor = editorManager.activeEditor;

                    if (activeEditor && e.target.ownerDocument == document) {
                        // Check to make sure we have a valid selection don't update the bookmark if it's
                        // a focusin to the body of the editor see #7025
                        if (activeEditor.selection && e.target != activeEditor.getBody()) {
                            activeEditor.selection.lastFocusBookmark = createBookmark(activeEditor.dom, activeEditor.lastRng);
                        }

                        // Fire a blur event if the element isn't a UI element
                        if (!isUIElement(e.target) && editorManager.focusedEditor == activeEditor) {
                            activeEditor.fire('blur', {focusedEditor: null});
                            editorManager.focusedEditor = null;
                        }
                    }
                };

                DOM.bind(document, 'focusin', documentFocusInHandler);
            }

            // Handle edge case when user starts the selection inside the editor and releases
            // the mouse outside the editor producing a new selection. This weird workaround is needed since
            // Gecko doesn't have the "selectionchange" event we need to do this. Fixes: #6843
            if (editor.inline && !documentMouseUpHandler) {
                documentMouseUpHandler = function(e) {
                    var activeEditor = editorManager.activeEditor;

                    if (activeEditor.inline && !activeEditor.dom.isChildOf(e.target, activeEditor.getBody())) {
                        var rng = activeEditor.selection.getRng();

                        if (!rng.collapsed) {
                            activeEditor.lastRng = rng;
                        }
                    }
                };

                DOM.bind(document, 'mouseup', documentMouseUpHandler);
            }
        }

        function unregisterDocumentEvents(e) {
            if (editorManager.focusedEditor == e.editor) {
                editorManager.focusedEditor = null;
            }

            if (!editorManager.activeEditor) {
                DOM.unbind(document, 'selectionchange', selectionChangeHandler);
                DOM.unbind(document, 'focusin', documentFocusInHandler);
                DOM.unbind(document, 'mouseup', documentMouseUpHandler);
                selectionChangeHandler = documentFocusInHandler = documentMouseUpHandler = null;
            }
        }

        editorManager.on('AddEditor', registerEvents);
        editorManager.on('RemoveEditor', unregisterDocumentEvents);
    }

    /**
     * Returns true if the specified element is part of the UI for example an button or text input.
     *
     * @method isEditorUIElement
     * @param  {Element} elm Element to check if it's part of the UI or not.
     * @return {Boolean} True/false state if the element is part of the UI or not.
     */
    FocusManager.isEditorUIElement = function(elm) {
        // Needs to be converted to string since svg can have focus: #6776
        return elm.className.toString().indexOf('mce-') !== -1;
    };

    return FocusManager;
});

// Included from: js/tinymce/classes/EditorManager.js

/**
 * EditorManager.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class used as a factory for manager for tinymce.Editor instances.
 *
 * @example
 * tinymce.EditorManager.init({});
 *
 * @class tinymce.EditorManager
 * @mixes tinymce.util.Observable
 * @static
 */
define("tinymce/EditorManager", [
    "tinymce/Editor",
    "tinymce/dom/DomQuery",
    "tinymce/dom/DOMUtils",
    "tinymce/util/URI",
    "tinymce/Env",
    "tinymce/util/Tools",
    "tinymce/util/Observable",
    "tinymce/util/I18n",
    "tinymce/FocusManager"
], function(Editor, DomQuery, DOMUtils, URI, Env, Tools, Observable, I18n, FocusManager) {
    var DOM = DOMUtils.DOM;
    var explode = Tools.explode, each = Tools.each, extend = Tools.extend;
    var instanceCounter = 0, beforeUnloadDelegate, EditorManager;

    function removeEditorFromList(editor) {
        var editors = EditorManager.editors, removedFromList;

        delete editors[editor.id];

        for (var i = 0; i < editors.length; i++) {
            if (editors[i] == editor) {
                editors.splice(i, 1);
                removedFromList = true;
                break;
            }
        }

        // Select another editor since the active one was removed
        if (EditorManager.activeEditor == editor) {
            EditorManager.activeEditor = editors[0];
        }

        // Clear focusedEditor if necessary, so that we don't try to blur the destroyed editor
        if (EditorManager.focusedEditor == editor) {
            EditorManager.focusedEditor = null;
        }

        return removedFromList;
    }

    function purgeDestroyedEditor(editor) {
        // User has manually destroyed the editor lets clean up the mess
        if (editor && !(editor.getContainer() || editor.getBody()).parentNode) {
            removeEditorFromList(editor);
            editor.destroy(true);
            editor = null;
        }

        return editor;
    }

    EditorManager = {
        /**
         * Dom query instance.
         *
         * @property $
         * @type tinymce.dom.DomQuery
         */
        $: DomQuery,

        /**
         * Major version of TinyMCE build.
         *
         * @property majorVersion
         * @type String
         */
        majorVersion: '4',

        /**
         * Minor version of TinyMCE build.
         *
         * @property minorVersion
         * @type String
         */
        minorVersion: '1.2',

        /**
         * Release date of TinyMCE build.
         *
         * @property releaseDate
         * @type String
         */
        releaseDate: '2014-07-15',

        /**
         * Collection of editor instances.
         *
         * @property editors
         * @type Object
         * @example
         * for (edId in tinymce.editors)
         *     tinymce.editors[edId].save();
         */
        editors: [],

        /**
         * Collection of language pack data.
         *
         * @property i18n
         * @type Object
         */
        i18n: I18n,

        /**
         * Currently active editor instance.
         *
         * @property activeEditor
         * @type tinymce.Editor
         * @example
         * tinyMCE.activeEditor.selection.getContent();
         * tinymce.EditorManager.activeEditor.selection.getContent();
         */
        activeEditor: null,

        setup: function() {
            var self = this, baseURL, documentBaseURL, suffix = "", preInit, src;

            // Get base URL for the current document
            documentBaseURL = document.location.href;

            // Check if the URL is a document based format like: http://site/dir/file and file:///
            // leave other formats like applewebdata://... intact
            if (/^[^:]+:\/\/\/?[^\/]+\//.test(documentBaseURL)) {
                documentBaseURL = documentBaseURL.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');

                if (!/[\/\\]$/.test(documentBaseURL)) {
                    documentBaseURL += '/';
                }
            }

            // If tinymce is defined and has a base use that or use the old tinyMCEPreInit
            preInit = window.tinymce || window.tinyMCEPreInit;
            if (preInit) {
                baseURL = preInit.base || preInit.baseURL;
                suffix = preInit.suffix;
            } else {
                // Get base where the tinymce script is located
                var scripts = document.getElementsByTagName('script');
                for (var i = 0; i < scripts.length; i++) {
                    src = scripts[i].src;

                    // Script types supported:
                    // tinymce.js tinymce.min.js tinymce.dev.js
                    // tinymce.jquery.js tinymce.jquery.min.js tinymce.jquery.dev.js
                    // tinymce.full.js tinymce.full.min.js tinymce.full.dev.js
                    if (/tinymce(\.full|\.jquery|)(\.min|\.dev|)\.js/.test(src)) {
                        if (src.indexOf('.min') != -1) {
                            suffix = '.min';
                        }

                        baseURL = src.substring(0, src.lastIndexOf('/'));
                        break;
                    }
                }

                // We didn't find any baseURL by looking at the script elements
                // Try to use the document.currentScript as a fallback
                if (!baseURL && document.currentScript) {
                    src = document.currentScript.src;

                    if (src.indexOf('.min') != -1) {
                        suffix = '.min';
                    }

                    baseURL = src.substring(0, src.lastIndexOf('/'));
                }
            }

            /**
             * Base URL where the root directory if TinyMCE is located.
             *
             * @property baseURL
             * @type String
             */
            self.baseURL = new URI(documentBaseURL).toAbsolute(baseURL);

            /**
             * Document base URL where the current document is located.
             *
             * @property documentBaseURL
             * @type String
             */
            self.documentBaseURL = documentBaseURL;

            /**
             * Absolute baseURI for the installation path of TinyMCE.
             *
             * @property baseURI
             * @type tinymce.util.URI
             */
            self.baseURI = new URI(self.baseURL);

            /**
             * Current suffix to add to each plugin/theme that gets loaded for example ".min".
             *
             * @property suffix
             * @type String
             */
            self.suffix = suffix;

            self.focusManager = new FocusManager(self);
        },

        /**
         * Initializes a set of editors. This method will create editors based on various settings.
         *
         * @method init
         * @param {Object} settings Settings object to be passed to each editor instance.
         * @example
         * // Initializes a editor using the longer method
         * tinymce.EditorManager.init({
         *    some_settings : 'some value'
         * });
         *
         * // Initializes a editor instance using the shorter version
         * tinyMCE.init({
         *    some_settings : 'some value'
         * });
         */
        init: function(settings) {
            var self = this, editors = [], editor;

            function createId(elm) {
                var id = elm.id;

                // Use element id, or unique name or generate a unique id
                if (!id) {
                    id = elm.name;

                    if (id && !DOM.get(id)) {
                        id = elm.name;
                    } else {
                        // Generate unique name
                        id = DOM.uniqueId();
                    }

                    elm.setAttribute('id', id);
                }

                return id;
            }

            function createEditor(id, settings, targetElm) {
                if (!purgeDestroyedEditor(self.get(id))) {
                    var editor = new Editor(id, settings, self);
                    editor.targetElm = editor.targetElm || targetElm;
                    editors.push(editor);
                    editor.render();
                }
            }

            function execCallback(name) {
                var callback = settings[name];

                if (!callback) {
                    return;
                }

                return callback.apply(self, Array.prototype.slice.call(arguments, 2));
            }

            function hasClass(elm, className) {
                return className.constructor === RegExp ? className.test(elm.className) : DOM.hasClass(elm, className);
            }

            function readyHandler() {
                var l, co;

                DOM.unbind(window, 'ready', readyHandler);

                execCallback('onpageload');

                if (settings.types) {
                    // Process type specific selector
                    each(settings.types, function(type) {
                        each(DOM.select(type.selector), function(elm) {
                            createEditor(createId(elm), extend({}, settings, type), elm);
                        });
                    });

                    return;
                } else if (settings.selector) {
                    // Process global selector
                    each(DOM.select(settings.selector), function(elm) {
                        createEditor(createId(elm), settings, elm);
                    });

                    return;
                } else if (settings.target) {
                    createEditor(createId(settings.target), settings);
                }

                // Fallback to old setting
                switch (settings.mode) {
                    case "exact":
                        l = settings.elements || '';

                        if (l.length > 0) {
                            each(explode(l), function(v) {
                                if (DOM.get(v)) {
                                    editor = new Editor(v, settings, self);
                                    editors.push(editor);
                                    editor.render();
                                } else {
                                    each(document.forms, function(f) {
                                        each(f.elements, function(e) {
                                            if (e.name === v) {
                                                v = 'mce_editor_' + instanceCounter++;
                                                DOM.setAttrib(e, 'id', v);
                                                createEditor(v, settings, e);
                                            }
                                        });
                                    });
                                }
                            });
                        }
                        break;

                    case "textareas":
                    case "specific_textareas":
                        each(DOM.select('textarea'), function(elm) {
                            if (settings.editor_deselector && hasClass(elm, settings.editor_deselector)) {
                                return;
                            }

                            if (!settings.editor_selector || hasClass(elm, settings.editor_selector)) {
                                createEditor(createId(elm), settings, elm);
                            }
                        });
                        break;
                }

                // Call onInit when all editors are initialized
                if (settings.oninit) {
                    l = co = 0;

                    each(editors, function(ed) {
                        co++;

                        if (!ed.initialized) {
                            // Wait for it
                            ed.on('init', function() {
                                l++;

                                // All done
                                if (l == co) {
                                    execCallback('oninit');
                                }
                            });
                        } else {
                            l++;
                        }

                        // All done
                        if (l == co) {
                            execCallback('oninit');
                        }
                    });
                }
            }

            self.settings = settings;

            DOM.bind(window, 'ready', readyHandler);
        },

        /**
         * Returns a editor instance by id.
         *
         * @method get
         * @param {String/Number} id Editor instance id or index to return.
         * @return {tinymce.Editor} Editor instance to return.
         * @example
         * // Adds an onclick event to an editor by id (shorter version)
         * tinymce.get('mytextbox').on('click', function(e) {
         *    ed.windowManager.alert('Hello world!');
         * });
         *
         * // Adds an onclick event to an editor by id (longer version)
         * tinymce.EditorManager.get('mytextbox').on('click', function(e) {
         *    ed.windowManager.alert('Hello world!');
         * });
         */
        get: function(id) {
            if (!arguments.length) {
                return this.editors;
            }

            return id in this.editors ? this.editors[id] : null;
        },

        /**
         * Adds an editor instance to the editor collection. This will also set it as the active editor.
         *
         * @method add
         * @param {tinymce.Editor} editor Editor instance to add to the collection.
         * @return {tinymce.Editor} The same instance that got passed in.
         */
        add: function(editor) {
            var self = this, editors = self.editors;

            // Add named and index editor instance
            editors[editor.id] = editor;
            editors.push(editor);

            self.activeEditor = editor;

            /**
             * Fires when an editor is added to the EditorManager collection.
             *
             * @event AddEditor
             * @param {Object} e Event arguments.
             */
            self.fire('AddEditor', {editor: editor});

            if (!beforeUnloadDelegate) {
                beforeUnloadDelegate = function() {
                    self.fire('BeforeUnload');
                };

                DOM.bind(window, 'beforeunload', beforeUnloadDelegate);
            }

            return editor;
        },

        /**
         * Creates an editor instance and adds it to the EditorManager collection.
         *
         * @method createEditor
         * @param {String} id Instance id to use for editor.
         * @param {Object} settings Editor instance settings.
         * @return {tinymce.Editor} Editor instance that got created.
         */
        createEditor: function(id, settings) {
            return this.add(new Editor(id, settings, this));
        },

        /**
         * Removes a editor or editors form page.
         *
         * @example
         * // Remove all editors bound to divs
         * tinymce.remove('div');
         *
         * // Remove all editors bound to textareas
         * tinymce.remove('textarea');
         *
         * // Remove all editors
         * tinymce.remove();
         *
         * // Remove specific instance by id
         * tinymce.remove('#id');
         *
         * @method remove
         * @param {tinymce.Editor/String/Object} [selector] CSS selector or editor instance to remove.
         * @return {tinymce.Editor} The editor that got passed in will be return if it was found otherwise null.
         */
        remove: function(selector) {
            var self = this, i, editors = self.editors, editor;

            // Remove all editors
            if (!selector) {
                for (i = editors.length - 1; i >= 0; i--) {
                    self.remove(editors[i]);
                }

                return;
            }

            // Remove editors by selector
            if (typeof(selector) == "string") {
                selector = selector.selector || selector;

                each(DOM.select(selector), function(elm) {
                    editor = editors[elm.id];

                    if (editor) {
                        self.remove(editor);
                    }
                });

                return;
            }

            // Remove specific editor
            editor = selector;

            // Not in the collection
            if (!editors[editor.id]) {
                return null;
            }

            /**
             * Fires when an editor is removed from EditorManager collection.
             *
             * @event RemoveEditor
             * @param {Object} e Event arguments.
             */
            if (removeEditorFromList(editor)) {
                self.fire('RemoveEditor', {editor: editor});
            }

            if (!editors.length) {
                DOM.unbind(window, 'beforeunload', beforeUnloadDelegate);
            }

            editor.remove();

            return editor;
        },

        /**
         * Executes a specific command on the currently active editor.
         *
         * @method execCommand
         * @param {String} c Command to perform for example Bold.
         * @param {Boolean} u Optional boolean state if a UI should be presented for the command or not.
         * @param {String} v Optional value parameter like for example an URL to a link.
         * @return {Boolean} true/false if the command was executed or not.
         */
        execCommand: function(cmd, ui, value) {
            var self = this, editor = self.get(value);

            // Manager commands
            switch (cmd) {
                case "mceAddEditor":
                    if (!self.get(value)) {
                        new Editor(value, self.settings, self).render();
                    }

                    return true;

                case "mceRemoveEditor":
                    if (editor) {
                        editor.remove();
                    }

                    return true;

                case 'mceToggleEditor':
                    if (!editor) {
                        self.execCommand('mceAddEditor', 0, value);
                        return true;
                    }

                    if (editor.isHidden()) {
                        editor.show();
                    } else {
                        editor.hide();
                    }

                    return true;
            }

            // Run command on active editor
            if (self.activeEditor) {
                return self.activeEditor.execCommand(cmd, ui, value);
            }

            return false;
        },

        /**
         * Calls the save method on all editor instances in the collection. This can be useful when a form is to be submitted.
         *
         * @method triggerSave
         * @example
         * // Saves all contents
         * tinyMCE.triggerSave();
         */
        triggerSave: function() {
            each(this.editors, function(editor) {
                editor.save();
            });
        },

        /**
         * Adds a language pack, this gets called by the loaded language files like en.js.
         *
         * @method addI18n
         * @param {String} code Optional language code.
         * @param {Object} items Name/value object with translations.
         */
        addI18n: function(code, items) {
            I18n.add(code, items);
        },

        /**
         * Translates the specified string using the language pack items.
         *
         * @method translate
         * @param {String/Array/Object} text String to translate
         * @return {String} Translated string.
         */
        translate: function(text) {
            return I18n.translate(text);
        }
    };

    extend(EditorManager, Observable);

    EditorManager.setup();

    // Export EditorManager as tinymce/tinymce in global namespace
    window.tinymce = window.tinyMCE = EditorManager;

    return EditorManager;
});

// Included from: js/tinymce/classes/LegacyInput.js

/**
 * LegacyInput.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

define("tinymce/LegacyInput", [
    "tinymce/EditorManager",
    "tinymce/util/Tools"
], function(EditorManager, Tools) {
    var each = Tools.each, explode = Tools.explode;

    EditorManager.on('AddEditor', function(e) {
        var editor = e.editor;

        editor.on('preInit', function() {
            var filters, fontSizes, dom, settings = editor.settings;

            function replaceWithSpan(node, styles) {
                each(styles, function(value, name) {
                    if (value) {
                        dom.setStyle(node, name, value);
                    }
                });

                dom.rename(node, 'span');
            }

            function convert(e) {
                dom = editor.dom;

                if (settings.convert_fonts_to_spans) {
                    each(dom.select('font,u,strike', e.node), function(node) {
                        filters[node.nodeName.toLowerCase()](dom, node);
                    });
                }
            }

            if (settings.inline_styles) {
                fontSizes = explode(settings.font_size_legacy_values);

                filters = {
                    font: function(dom, node) {
                        replaceWithSpan(node, {
                            backgroundColor: node.style.backgroundColor,
                            color: node.color,
                            fontFamily: node.face,
                            fontSize: fontSizes[parseInt(node.size, 10) - 1]
                        });
                    },

                    u: function(dom, node) {
                        replaceWithSpan(node, {
                            textDecoration: 'underline'
                        });
                    },

                    strike: function(dom, node) {
                        replaceWithSpan(node, {
                            textDecoration: 'line-through'
                        });
                    }
                };

                editor.on('PreProcess SetContent', convert);
            }
        });
    });
});

// Included from: js/tinymce/classes/util/XHR.js

/**
 * XHR.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class enables you to send XMLHTTPRequests cross browser.
 * @class tinymce.util.XHR
 * @static
 * @example
 * // Sends a low level Ajax request
 * tinymce.util.XHR.send({
 *    url: 'someurl',
 *    success: function(text) {
 *       console.debug(text);
 *    }
 * });
 */
define("tinymce/util/XHR", [], function() {
    return {
        /**
         * Sends a XMLHTTPRequest.
         * Consult the Wiki for details on what settings this method takes.
         *
         * @method send
         * @param {Object} settings Object will target URL, callbacks and other info needed to make the request.
         */
        send: function(settings) {
            var xhr, count = 0;

            function ready() {
                if (!settings.async || xhr.readyState == 4 || count++ > 10000) {
                    if (settings.success && count < 10000 && xhr.status == 200) {
                        settings.success.call(settings.success_scope, '' + xhr.responseText, xhr, settings);
                    } else if (settings.error) {
                        settings.error.call(settings.error_scope, count > 10000 ? 'TIMED_OUT' : 'GENERAL', xhr, settings);
                    }

                    xhr = null;
                } else {
                    setTimeout(ready, 10);
                }
            }

            // Default settings
            settings.scope = settings.scope || this;
            settings.success_scope = settings.success_scope || settings.scope;
            settings.error_scope = settings.error_scope || settings.scope;
            settings.async = settings.async === false ? false : true;
            settings.data = settings.data || '';

            xhr = new XMLHttpRequest();

            if (xhr) {
                if (xhr.overrideMimeType) {
                    xhr.overrideMimeType(settings.content_type);
                }

                xhr.open(settings.type || (settings.data ? 'POST' : 'GET'), settings.url, settings.async);

                if (settings.crossDomain) {
                    xhr.withCredentials = true;
                }
                if (settings.content_type) {
                    xhr.setRequestHeader('Content-Type', settings.content_type);
                }

                xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');

                xhr.send(settings.data);

                // Syncronous request
                if (!settings.async) {
                    return ready();
                }

                // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
                setTimeout(ready, 10);
            }
        }
    };
});

// Included from: js/tinymce/classes/util/JSON.js

/**
 * JSON.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * JSON parser and serializer class.
 *
 * @class tinymce.util.JSON
 * @static
 * @example
 * // JSON parse a string into an object
 * var obj = tinymce.util.JSON.parse(somestring);
 *
 * // JSON serialize a object into an string
 * var str = tinymce.util.JSON.serialize(obj);
 */
define("tinymce/util/JSON", [], function() {
    function serialize(o, quote) {
        var i, v, t, name;

        quote = quote || '"';

        if (o === null) {
            return 'null';
        }

        t = typeof o;

        if (t == 'string') {
            v = '\bb\tt\nn\ff\rr\""\'\'\\\\';

            return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
                // Make sure single quotes never get encoded inside double quotes for JSON compatibility
                if (quote === '"' && a === "'") {
                    return a;
                }

                i = v.indexOf(b);

                if (i + 1) {
                    return '\\' + v.charAt(i + 1);
                }

                a = b.charCodeAt().toString(16);

                return '\\u' + '0000'.substring(a.length) + a;
            }) + quote;
        }

        if (t == 'object') {
            if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') {
                for (i = 0, v = '['; i < o.length; i++) {
                    v += (i > 0 ? ',' : '') + serialize(o[i], quote);
                }

                return v + ']';
            }

            v = '{';

            for (name in o) {
                if (o.hasOwnProperty(name)) {
                    v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name +
                        quote + ':' + serialize(o[name], quote) : '';
                }
            }

            return v + '}';
        }

        return '' + o;
    }

    return {
        /**
         * Serializes the specified object as a JSON string.
         *
         * @method serialize
         * @param {Object} obj Object to serialize as a JSON string.
         * @param {String} quote Optional quote string defaults to ".
         * @return {string} JSON string serialized from input.
         */
        serialize: serialize,

        /**
         * Unserializes/parses the specified JSON string into a object.
         *
         * @method parse
         * @param {string} s JSON String to parse into a JavaScript object.
         * @return {Object} Object from input JSON string or undefined if it failed.
         */
        parse: function(text) {
            try {
                // Trick uglify JS
                return window[String.fromCharCode(101) + 'val']('(' + text + ')');
            } catch (ex) {
                // Ignore
            }
        }

        /**#@-*/
    };
});

// Included from: js/tinymce/classes/util/JSONRequest.js

/**
 * JSONRequest.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class enables you to use JSON-RPC to call backend methods.
 *
 * @class tinymce.util.JSONRequest
 * @example
 * var json = new tinymce.util.JSONRequest({
 *     url: 'somebackend.php'
 * });
 *
 * // Send RPC call 1
 * json.send({
 *     method: 'someMethod1',
 *     params: ['a', 'b'],
 *     success: function(result) {
 *         console.dir(result);
 *     }
 * });
 *
 * // Send RPC call 2
 * json.send({
 *     method: 'someMethod2',
 *     params: ['a', 'b'],
 *     success: function(result) {
 *         console.dir(result);
 *     }
 * });
 */
define("tinymce/util/JSONRequest", [
    "tinymce/util/JSON",
    "tinymce/util/XHR",
    "tinymce/util/Tools"
], function(JSON, XHR, Tools) {
    var extend = Tools.extend;

    function JSONRequest(settings) {
        this.settings = extend({}, settings);
        this.count = 0;
    }

    /**
     * Simple helper function to send a JSON-RPC request without the need to initialize an object.
     * Consult the Wiki API documentation for more details on what you can pass to this function.
     *
     * @method sendRPC
     * @static
     * @param {Object} o Call object where there are three field id, method and params this object should also contain callbacks etc.
     */
    JSONRequest.sendRPC = function(o) {
        return new JSONRequest().send(o);
    };

    JSONRequest.prototype = {
        /**
         * Sends a JSON-RPC call. Consult the Wiki API documentation for more details on what you can pass to this function.
         *
         * @method send
         * @param {Object} args Call object where there are three field id, method and params this object should also contain callbacks etc.
         */
        send: function(args) {
            var ecb = args.error, scb = args.success;

            args = extend(this.settings, args);

            args.success = function(c, x) {
                c = JSON.parse(c);

                if (typeof(c) == 'undefined') {
                    c = {
                        error : 'JSON Parse error.'
                    };
                }

                if (c.error) {
                    ecb.call(args.error_scope || args.scope, c.error, x);
                } else {
                    scb.call(args.success_scope || args.scope, c.result);
                }
            };

            args.error = function(ty, x) {
                if (ecb) {
                    ecb.call(args.error_scope || args.scope, ty, x);
                }
            };

            args.data = JSON.serialize({
                id: args.id || 'c' + (this.count++),
                method: args.method,
                params: args.params
            });

            // JSON content type for Ruby on rails. Bug: #1883287
            args.content_type = 'application/json';

            XHR.send(args);
        }
    };

    return JSONRequest;
});

// Included from: js/tinymce/classes/util/JSONP.js

/**
 * JSONP.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

define("tinymce/util/JSONP", [
    "tinymce/dom/DOMUtils"
], function(DOMUtils) {
    return {
        callbacks: {},
        count: 0,

        send: function(settings) {
            var self = this, dom = DOMUtils.DOM, count = settings.count !== undefined ? settings.count : self.count;
            var id = 'tinymce_jsonp_' + count;

            self.callbacks[count] = function(json) {
                dom.remove(id);
                delete self.callbacks[count];

                settings.callback(json);
            };

            dom.add(dom.doc.body, 'script', {
                id: id,
                src: settings.url,
                type: 'text/javascript'
            });

            self.count++;
        }
    };
});

// Included from: js/tinymce/classes/util/LocalStorage.js

/**
 * LocalStorage.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class will simulate LocalStorage on IE 7 and return the native version on modern browsers.
 * Storage is done using userData on IE 7 and a special serialization format. The format is designed
 * to be as small as possible by making sure that the keys and values doesn't need to be encoded. This
 * makes it possible to store for example HTML data.
 *
 * Storage format for userData:
 * <base 32 key length>,<key string>,<base 32 value length>,<value>,...
 *
 * For example this data key1=value1,key2=value2 would be:
 * 4,key1,6,value1,4,key2,6,value2
 *
 * @class tinymce.util.LocalStorage
 * @static
 * @version 4.0
 * @example
 * tinymce.util.LocalStorage.setItem('key', 'value');
 * var value = tinymce.util.LocalStorage.getItem('key');
 */
define("tinymce/util/LocalStorage", [], function() {
    var LocalStorage, storageElm, items, keys, userDataKey, hasOldIEDataSupport;

    // Check for native support
    try {
        if (window.localStorage) {
            return localStorage;
        }
    } catch (ex) {
        // Ignore
    }

    userDataKey = "tinymce";
    storageElm = document.documentElement;
    hasOldIEDataSupport = !!storageElm.addBehavior;

    if (hasOldIEDataSupport) {
        storageElm.addBehavior('#default#userData');
    }

    /**
     * Gets the keys names and updates LocalStorage.length property. Since IE7 doesn't have any getters/setters.
     */
    function updateKeys() {
        keys = [];

        for (var key in items) {
            keys.push(key);
        }

        LocalStorage.length = keys.length;
    }

    /**
     * Loads the userData string and parses it into the items structure.
     */
    function load() {
        var key, data, value, pos = 0;

        items = {};

        // localStorage can be disabled on WebKit/Gecko so make a dummy storage
        if (!hasOldIEDataSupport) {
            return;
        }

        function next(end) {
            var value, nextPos;

            nextPos = end !== undefined ? pos + end : data.indexOf(',', pos);
            if (nextPos === -1 || nextPos > data.length) {
                return null;
            }

            value = data.substring(pos, nextPos);
            pos = nextPos + 1;

            return value;
        }

        storageElm.load(userDataKey);
        data = storageElm.getAttribute(userDataKey) || '';

        do {
            var offset = next();
            if (offset === null) {
                break;
            }

            key = next(parseInt(offset, 32) || 0);
            if (key !== null) {
                offset = next();
                if (offset === null) {
                    break;
                }

                value = next(parseInt(offset, 32) || 0);

                if (key) {
                    items[key] = value;
                }
            }
        } while (key !== null);

        updateKeys();
    }

    /**
     * Saves the items structure into a the userData format.
     */
    function save() {
        var value, data = '';

        // localStorage can be disabled on WebKit/Gecko so make a dummy storage
        if (!hasOldIEDataSupport) {
            return;
        }

        for (var key in items) {
            value = items[key];
            data += (data ? ',' : '') + key.length.toString(32) + ',' + key + ',' + value.length.toString(32) + ',' + value;
        }

        storageElm.setAttribute(userDataKey, data);

        try {
            storageElm.save(userDataKey);
        } catch (ex) {
            // Ignore disk full
        }

        updateKeys();
    }

    LocalStorage = {
        /**
         * Length of the number of items in storage.
         *
         * @property length
         * @type Number
         * @return {Number} Number of items in storage.
         */
        //length:0,

        /**
         * Returns the key name by index.
         *
         * @method key
         * @param {Number} index Index of key to return.
         * @return {String} Key value or null if it wasn't found.
         */
        key: function(index) {
            return keys[index];
        },

        /**
         * Returns the value if the specified key or null if it wasn't found.
         *
         * @method getItem
         * @param {String} key Key of item to retrive.
         * @return {String} Value of the specified item or null if it wasn't found.
         */
        getItem: function(key) {
            return key in items ? items[key] : null;
        },

        /**
         * Sets the value of the specified item by it's key.
         *
         * @method setItem
         * @param {String} key Key of the item to set.
         * @param {String} value Value of the item to set.
         */
        setItem: function(key, value) {
            items[key] = "" + value;
            save();
        },

        /**
         * Removes the specified item by key.
         *
         * @method removeItem
         * @param {String} key Key of item to remove.
         */
        removeItem: function(key) {
            delete items[key];
            save();
        },

        /**
         * Removes all items.
         *
         * @method clear
         */
        clear: function() {
            items = {};
            save();
        }
    };

    load();

    return LocalStorage;
});

// Included from: js/tinymce/classes/Compat.js

/**
 * Compat.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * TinyMCE core class.
 *
 * @static
 * @class tinymce
 * @borrow-members tinymce.EditorManager
 * @borrow-members tinymce.util.Tools
 */
define("tinymce/Compat", [
    "tinymce/dom/DOMUtils",
    "tinymce/dom/EventUtils",
    "tinymce/dom/ScriptLoader",
    "tinymce/AddOnManager",
    "tinymce/util/Tools",
    "tinymce/Env"
], function(DOMUtils, EventUtils, ScriptLoader, AddOnManager, Tools, Env) {
    var tinymce = window.tinymce;

    /**
     * @property {tinymce.dom.DOMUtils} DOM Global DOM instance.
     * @property {tinymce.dom.ScriptLoader} ScriptLoader Global ScriptLoader instance.
     * @property {tinymce.AddOnManager} PluginManager Global PluginManager instance.
     * @property {tinymce.AddOnManager} ThemeManager Global ThemeManager instance.
     */
    tinymce.DOM = DOMUtils.DOM;
    tinymce.ScriptLoader = ScriptLoader.ScriptLoader;
    tinymce.PluginManager = AddOnManager.PluginManager;
    tinymce.ThemeManager = AddOnManager.ThemeManager;

    tinymce.dom = tinymce.dom || {};
    tinymce.dom.Event = EventUtils.Event;

    Tools.each(Tools, function(func, key) {
        tinymce[key] = func;
    });

    Tools.each('isOpera isWebKit isIE isGecko isMac'.split(' '), function(name) {
        tinymce[name] = Env[name.substr(2).toLowerCase()];
    });

    return {};
});

// Describe the different namespaces

/**
 * Root level namespace this contains classes directly releated to the TinyMCE editor.
 *
 * @namespace tinymce
 */

/**
 * Contains classes for handling the browsers DOM.
 *
 * @namespace tinymce.dom
 */

/**
 * Contains html parser and serializer logic.
 *
 * @namespace tinymce.html
 */

/**
 * Contains the different UI types such as buttons, listboxes etc.
 *
 * @namespace tinymce.ui
 */

/**
 * Contains various utility classes such as json parser, cookies etc.
 *
 * @namespace tinymce.util
 */

// Included from: js/tinymce/classes/ui/Layout.js

/**
 * Layout.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Base layout manager class.
 *
 * @class tinymce.ui.Layout
 */
define("tinymce/ui/Layout", [
    "tinymce/util/Class",
    "tinymce/util/Tools"
], function(Class, Tools) {
    "use strict";

    return Class.extend({
        Defaults: {
            firstControlClass: 'first',
            lastControlClass: 'last'
        },

        /**
         * Constructs a layout instance with the specified settings.
         *
         * @constructor
         * @param {Object} settings Name/value object with settings.
         */
        init: function(settings) {
            this.settings = Tools.extend({}, this.Defaults, settings);
        },

        /**
         * This method gets invoked before the layout renders the controls.
         *
         * @method preRender
         * @param {tinymce.ui.Container} container Container instance to preRender.
         */
        preRender: function(container) {
            container.addClass(this.settings.containerClass, 'body');
        },

        /**
         * Applies layout classes to the container.
         *
         * @private
         */
        applyClasses: function(container) {
            var self = this, settings = self.settings, items, firstClass, lastClass;

            items = container.items().filter(':visible');
            firstClass = settings.firstControlClass;
            lastClass = settings.lastControlClass;

            items.each(function(item) {
                item.removeClass(firstClass).removeClass(lastClass);

                if (settings.controlClass) {
                    item.addClass(settings.controlClass);
                }
            });

            items.eq(0).addClass(firstClass);
            items.eq(-1).addClass(lastClass);
        },

        /**
         * Renders the specified container and any layout specific HTML.
         *
         * @method renderHtml
         * @param {tinymce.ui.Container} container Container to render HTML for.
         */
        renderHtml: function(container) {
            var self = this, settings = self.settings, items, html = '';

            items = container.items();
            items.eq(0).addClass(settings.firstControlClass);
            items.eq(-1).addClass(settings.lastControlClass);

            items.each(function(item) {
                if (settings.controlClass) {
                    item.addClass(settings.controlClass);
                }

                html += item.renderHtml();
            });

            return html;
        },

        /**
         * Recalculates the positions of the controls in the specified container.
         *
         * @method recalc
         * @param {tinymce.ui.Container} container Container instance to recalc.
         */
        recalc: function() {
        },

        /**
         * This method gets invoked after the layout renders the controls.
         *
         * @method postRender
         * @param {tinymce.ui.Container} container Container instance to postRender.
         */
        postRender: function() {
        }
    });
});

// Included from: js/tinymce/classes/ui/AbsoluteLayout.js

/**
 * AbsoluteLayout.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * LayoutManager for absolute positioning. This layout manager is more of
 * a base class for other layouts but can be created and used directly.
 *
 * @-x-less AbsoluteLayout.less
 * @class tinymce.ui.AbsoluteLayout
 * @extends tinymce.ui.Layout
 */
define("tinymce/ui/AbsoluteLayout", [
    "tinymce/ui/Layout"
], function(Layout) {
    "use strict";

    return Layout.extend({
        Defaults: {
            containerClass: 'abs-layout',
            controlClass: 'abs-layout-item'
        },

        /**
         * Recalculates the positions of the controls in the specified container.
         *
         * @method recalc
         * @param {tinymce.ui.Container} container Container instance to recalc.
         */
        recalc: function(container) {
            container.items().filter(':visible').each(function(ctrl) {
                var settings = ctrl.settings;

                ctrl.layoutRect({
                    x: settings.x,
                    y: settings.y,
                    w: settings.w,
                    h: settings.h
                });

                if (ctrl.recalc) {
                    ctrl.recalc();
                }
            });
        },

        /**
         * Renders the specified container and any layout specific HTML.
         *
         * @method renderHtml
         * @param {tinymce.ui.Container} container Container to render HTML for.
         */
        renderHtml: function(container) {
            return '<div id="' + container._id + '-absend" class="' + container.classPrefix + 'abs-end"></div>' + this._super(container);
        }
    });
});

// Included from: js/tinymce/classes/ui/Tooltip.js

/**
 * Tooltip.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Creates a tooltip instance.
 *
 * @-x-less ToolTip.less
 * @class tinymce.ui.ToolTip
 * @extends tinymce.ui.Control
 * @mixes tinymce.ui.Movable
 */
define("tinymce/ui/Tooltip", [
    "tinymce/ui/Control",
    "tinymce/ui/Movable"
], function(Control, Movable) {
    return Control.extend({
        Mixins: [Movable],

        Defaults: {
            classes: 'widget tooltip tooltip-n'
        },

        /**
         * Sets/gets the current label text.
         *
         * @method text
         * @param {String} [text] New label text.
         * @return {String|tinymce.ui.Tooltip} Current text or current label instance.
         */
        text: function(value) {
            var self = this;

            if (typeof(value) != "undefined") {
                self._value = value;

                if (self._rendered) {
                    self.getEl().lastChild.innerHTML = self.encode(value);
                }

                return self;
            }

            return self._value;
        },

        /**
         * Renders the control as a HTML string.
         *
         * @method renderHtml
         * @return {String} HTML representing the control.
         */
        renderHtml: function() {
            var self = this, prefix = self.classPrefix;

            return (
                '<div id="' + self._id + '" class="' + self.classes() + '" role="presentation">' +
                    '<div class="' + prefix + 'tooltip-arrow"></div>' +
                    '<div class="' + prefix + 'tooltip-inner">' + self.encode(self._text) + '</div>' +
                '</div>'
            );
        },

        /**
         * Repaints the control after a layout operation.
         *
         * @method repaint
         */
        repaint: function() {
            var self = this, style, rect;

            style = self.getEl().style;
            rect = self._layoutRect;

            style.left = rect.x + 'px';
            style.top = rect.y + 'px';
            style.zIndex = 0xFFFF + 0xFFFF;
        }
    });
});

// Included from: js/tinymce/classes/ui/Widget.js

/**
 * Widget.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Widget base class a widget is a control that has a tooltip and some basic states.
 *
 * @class tinymce.ui.Widget
 * @extends tinymce.ui.Control
 */
define("tinymce/ui/Widget", [
    "tinymce/ui/Control",
    "tinymce/ui/Tooltip"
], function(Control, Tooltip) {
    "use strict";

    var tooltip;

    var Widget = Control.extend({
        /**
         * Constructs a instance with the specified settings.
         *
         * @constructor
         * @param {Object} settings Name/value object with settings.
         * @setting {String} tooltip Tooltip text to display when hovering.
         * @setting {Boolean} autofocus True if the control should be focused when rendered.
         * @setting {String} text Text to display inside widget.
         */
        init: function(settings) {
            var self = this;

            self._super(settings);
            settings = self.settings;
            self.canFocus = true;

            if (settings.tooltip && Widget.tooltips !== false) {
                self.on('mouseenter', function(e) {
                    var tooltip = self.tooltip().moveTo(-0xFFFF);

                    if (e.control == self) {
                        var rel = tooltip.text(settings.tooltip).show().testMoveRel(self.getEl(), ['bc-tc', 'bc-tl', 'bc-tr']);

                        tooltip.toggleClass('tooltip-n', rel == 'bc-tc');
                        tooltip.toggleClass('tooltip-nw', rel == 'bc-tl');
                        tooltip.toggleClass('tooltip-ne', rel == 'bc-tr');

                        tooltip.moveRel(self.getEl(), rel);
                    } else {
                        tooltip.hide();
                    }
                });

                self.on('mouseleave mousedown click', function() {
                    self.tooltip().hide();
                });
            }

            self.aria('label', settings.ariaLabel || settings.tooltip);
        },

        /**
         * Returns the current tooltip instance.
         *
         * @method tooltip
         * @return {tinymce.ui.Tooltip} Tooltip instance.
         */
        tooltip: function() {
            if (!tooltip) {
                tooltip = new Tooltip({type: 'tooltip'});
                tooltip.renderTo();
            }

            return tooltip;
        },

        /**
         * Sets/gets the active state of the widget.
         *
         * @method active
         * @param {Boolean} [state] State if the control is active.
         * @return {Boolean|tinymce.ui.Widget} True/false or current widget instance.
         */
        active: function(state) {
            var self = this, undef;

            if (state !== undef) {
                self.aria('pressed', state);
                self.toggleClass('active', state);
            }

            return self._super(state);
        },

        /**
         * Sets/gets the disabled state of the widget.
         *
         * @method disabled
         * @param {Boolean} [state] State if the control is disabled.
         * @return {Boolean|tinymce.ui.Widget} True/false or current widget instance.
         */
        disabled: function(state) {
            var self = this, undef;

            if (state !== undef) {
                self.aria('disabled', state);
                self.toggleClass('disabled', state);
            }

            return self._super(state);
        },

        /**
         * Called after the control has been rendered.
         *
         * @method postRender
         */
        postRender: function() {
            var self = this, settings = self.settings;

            self._rendered = true;

            self._super();

            if (!self.parent() && (settings.width || settings.height)) {
                self.initLayoutRect();
                self.repaint();
            }

            if (settings.autofocus) {
                self.focus();
            }
        },

        /**
         * Removes the current control from DOM and from UI collections.
         *
         * @method remove
         * @return {tinymce.ui.Control} Current control instance.
         */
        remove: function() {
            this._super();

            if (tooltip) {
                tooltip.remove();
                tooltip = null;
            }
        }
    });

    return Widget;
});

// Included from: js/tinymce/classes/ui/Button.js

/**
 * Button.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class is used to create buttons. You can create them directly or through the Factory.
 *
 * @example
 * // Create and render a button to the body element
 * tinymce.ui.Factory.create({
 *     type: 'button',
 *     text: 'My button'
 * }).renderTo(document.body);
 *
 * @-x-less Button.less
 * @class tinymce.ui.Button
 * @extends tinymce.ui.Widget
 */
define("tinymce/ui/Button", [
    "tinymce/ui/Widget"
], function(Widget) {
    "use strict";

    return Widget.extend({
        Defaults: {
            classes: "widget btn",
            role: "button"
        },

        /**
         * Constructs a new button instance with the specified settings.
         *
         * @constructor
         * @param {Object} settings Name/value object with settings.
         * @setting {String} size Size of the button small|medium|large.
         * @setting {String} image Image to use for icon.
         * @setting {String} icon Icon to use for button.
         */
        init: function(settings) {
            var self = this, size;

            self.on('click mousedown', function(e) {
                e.preventDefault();
            });

            self._super(settings);
            size = settings.size;

            if (settings.subtype) {
                self.addClass(settings.subtype);
            }

            if (size) {
                self.addClass('btn-' + size);
            }
        },

        /**
         * Sets/gets the current button icon.
         *
         * @method icon
         * @param {String} [icon] New icon identifier.
         * @return {String|tinymce.ui.MenuButton} Current icon or current MenuButton instance.
         */
        icon: function(icon) {
            var self = this, prefix = self.classPrefix;

            if (typeof(icon) == 'undefined') {
                return self.settings.icon;
            }

            self.settings.icon = icon;
            icon = icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';

            if (self._rendered) {
                var btnElm = self.getEl().firstChild, iconElm = btnElm.getElementsByTagName('i')[0];

                if (icon) {
                    if (!iconElm || iconElm != btnElm.firstChild) {
                        iconElm = document.createElement('i');
                        btnElm.insertBefore(iconElm, btnElm.firstChild);
                    }

                    iconElm.className = icon;
                } else if (iconElm) {
                    btnElm.removeChild(iconElm);
                }

                self.text(self._text); // Set text again to fix whitespace between icon + text
            }

            return self;
        },

        /**
         * Repaints the button for example after it's been resizes by a layout engine.
         *
         * @method repaint
         */
        repaint: function() {
            var btnStyle = this.getEl().firstChild.style;

            btnStyle.width = btnStyle.height = "100%";

            this._super();
        },

        /**
         * Sets/gets the current button text.
         *
         * @method text
         * @param {String} [text] New button text.
         * @return {String|tinymce.ui.Button} Current text or current Button instance.
         */
        text: function(text) {
            var self = this;

            if (self._rendered) {
                var textNode = self.getEl().lastChild.lastChild;
                if (textNode) {
                    textNode.data = self.translate(text);
                }
            }

            return self._super(text);
        },

        /**
         * Renders the control as a HTML string.
         *
         * @method renderHtml
         * @return {String} HTML representing the control.
         */
        renderHtml: function() {
            var self = this, id = self._id, prefix = self.classPrefix;
            var icon = self.settings.icon, image;

            image = self.settings.image;
            if (image) {
                icon = 'none';

                // Support for [high dpi, low dpi] image sources
                if (typeof image != "string") {
                    image = window.getSelection ? image[0] : image[1];
                }

                image = ' style="background-image: url(\'' + image + '\')"';
            } else {
                image = '';
            }

            icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';

            return (
                '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1" aria-labelledby="' + id + '">' +
                    '<button role="presentation" type="button" tabindex="-1">' +
                        (icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
                        (self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') +
                    '</button>' +
                '</div>'
            );
        }
    });
});

// Included from: js/tinymce/classes/ui/ButtonGroup.js

/**
 * ButtonGroup.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This control enables you to put multiple buttons into a group. This is
 * useful when you want to combine similar toolbar buttons into a group.
 *
 * @example
 * // Create and render a buttongroup with two buttons to the body element
 * tinymce.ui.Factory.create({
 *     type: 'buttongroup',
 *     items: [
 *         {text: 'Button A'},
 *         {text: 'Button B'}
 *     ]
 * }).renderTo(document.body);
 *
 * @-x-less ButtonGroup.less
 * @class tinymce.ui.ButtonGroup
 * @extends tinymce.ui.Container
 */
define("tinymce/ui/ButtonGroup", [
    "tinymce/ui/Container"
], function(Container) {
    "use strict";

    return Container.extend({
        Defaults: {
            defaultType: 'button',
            role: 'group'
        },

        /**
         * Renders the control as a HTML string.
         *
         * @method renderHtml
         * @return {String} HTML representing the control.
         */
        renderHtml: function() {
            var self = this, layout = self._layout;

            self.addClass('btn-group');
            self.preRender();
            layout.preRender(self);

            return (
                '<div id="' + self._id + '" class="' + self.classes() + '">' +
                    '<div id="' + self._id + '-body">' +
                        (self.settings.html || '') + layout.renderHtml(self) +
                    '</div>' +
                '</div>'
            );
        }
    });
});

// Included from: js/tinymce/classes/ui/Checkbox.js

/**
 * Checkbox.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This control creates a custom checkbox.
 *
 * @example
 * // Create and render a checkbox to the body element
 * tinymce.ui.Factory.create({
 *     type: 'checkbox',
 *     checked: true,
 *     text: 'My checkbox'
 * }).renderTo(document.body);
 *
 * @-x-less Checkbox.less
 * @class tinymce.ui.Checkbox
 * @extends tinymce.ui.Widget
 */
define("tinymce/ui/Checkbox", [
    "tinymce/ui/Widget"
], function(Widget) {
    "use strict";

    return Widget.extend({
        Defaults: {
            classes: "checkbox",
            role: "checkbox",
            checked: false
        },

        /**
         * Constructs a new Checkbox instance with the specified settings.
         *
         * @constructor
         * @param {Object} settings Name/value object with settings.
         * @setting {Boolean} checked True if the checkbox should be checked by default.
         */
        init: function(settings) {
            var self = this;

            self._super(settings);

            self.on('click mousedown', function(e) {
                e.preventDefault();
            });

            self.on('click', function(e) {
                e.preventDefault();

                if (!self.disabled()) {
                    self.checked(!self.checked());
                }
            });

            self.checked(self.settings.checked);
        },

        /**
         * Getter/setter function for the checked state.
         *
         * @method checked
         * @param {Boolean} [state] State to be set.
         * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation.
         */
        checked: function(state) {
            var self = this;

            if (typeof state != "undefined") {
                if (state) {
                    self.addClass('checked');
                } else {
                    self.removeClass('checked');
                }

                self._checked = state;
                self.aria('checked', state);

                return self;
            }

            return self._checked;
        },

        /**
         * Getter/setter function for the value state.
         *
         * @method value
         * @param {Boolean} [state] State to be set.
         * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation.
         */
        value: function(state) {
            return this.checked(state);
        },

        /**
         * Renders the control as a HTML string.
         *
         * @method renderHtml
         * @return {String} HTML representing the control.
         */
        renderHtml: function() {
            var self = this, id = self._id, prefix = self.classPrefix;

            return (
                '<div id="' + id + '" class="' + self.classes() + '" unselectable="on" aria-labelledby="' + id + '-al" tabindex="-1">' +
                    '<i class="' + prefix + 'ico ' + prefix + 'i-checkbox"></i>' +
                    '<span id="' + id + '-al" class="' + prefix + 'label">' + self.encode(self._text) + '</span>' +
                '</div>'
            );
        }
    });
});

// Included from: js/tinymce/classes/ui/ComboBox.js

/**
 * ComboBox.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class creates a combobox control. Select box that you select a value from or
 * type a value into.
 *
 * @-x-less ComboBox.less
 * @class tinymce.ui.ComboBox
 * @extends tinymce.ui.Widget
 */
define("tinymce/ui/ComboBox", [
    "tinymce/ui/Widget",
    "tinymce/ui/Factory",
    "tinymce/ui/DomUtils"
], function(Widget, Factory, DomUtils) {
    "use strict";

    return Widget.extend({
        /**
         * Constructs a new control instance with the specified settings.
         *
         * @constructor
         * @param {Object} settings Name/value object with settings.
         * @setting {String} placeholder Placeholder text to display.
         */
        init: function(settings) {
            var self = this;

            self._super(settings);
            self.addClass('combobox');
            self.subinput = true;
            self.ariaTarget = 'inp'; // TODO: Figure out a better way

            settings = self.settings;
            settings.menu = settings.menu || settings.values;

            if (settings.menu) {
                settings.icon = 'caret';
            }

            self.on('click', function(e) {
                var elm = e.target, root = self.getEl();

                while (elm && elm != root) {
                    if (elm.id && elm.id.indexOf('-open') != -1) {
                        self.fire('action');

                        if (settings.menu) {
                            self.showMenu();

                            if (e.aria) {
                                self.menu.items()[0].focus();
                            }
                        }
                    }

                    elm = elm.parentNode;
                }
            });

            // TODO: Rework this
            self.on('keydown', function(e) {
                if (e.target.nodeName == "INPUT" && e.keyCode == 13) {
                    self.parents().reverse().each(function(ctrl) {
                        e.preventDefault();
                        self.fire('change');

                        if (ctrl.hasEventListeners('submit') && ctrl.toJSON) {
                            ctrl.fire('submit', {data: ctrl.toJSON()});
                            return false;
                        }
                    });
                }
            });

            if (settings.placeholder) {
                self.addClass('placeholder');

                self.on('focusin', function() {
                    if (!self._hasOnChange) {
                        DomUtils.on(self.getEl('inp'), 'change', function() {
                            self.fire('change');
                        });

                        self._hasOnChange = true;
                    }

                    if (self.hasClass('placeholder')) {
                        self.getEl('inp').value = '';
                        self.removeClass('placeholder');
                    }
                });

                self.on('focusout', function() {
                    if (self.value().length === 0) {
                        self.getEl('inp').value = settings.placeholder;
                        self.addClass('placeholder');
                    }
                });
            }
        },

        showMenu: function() {
            var self = this, settings = self.settings, menu;

            if (!self.menu) {
                menu = settings.menu || [];

                // Is menu array then auto constuct menu control
                if (menu.length) {
                    menu = {
                        type: 'menu',
                        items: menu
                    };
                } else {
                    menu.type = menu.type || 'menu';
                }

                self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm());
                self.fire('createmenu');
                self.menu.reflow();
                self.menu.on('cancel', function(e) {
                    if (e.control === self.menu) {
                        self.focus();
                    }
                });

                self.menu.on('show hide', function(e) {
                    e.control.items().each(function(ctrl) {
                        ctrl.active(ctrl.value() == self.value());
                    });
                }).fire('show');

                self.menu.on('select', function(e) {
                    self.value(e.control.value());
                });

                self.on('focusin', function(e) {
                    if (e.target.tagName.toUpperCase() == 'INPUT') {
                        self.menu.hide();
                    }
                });

                self.aria('expanded', true);
            }

            self.menu.show();
            self.menu.layoutRect({w: self.layoutRect().w});
            self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']);
        },

        /**
         * Getter/setter function for the control value.
         *
         * @method value
         * @param {String} [value] Value to be set.
         * @return {String|tinymce.ui.ComboBox} Value or self if it's a set operation.
         */
        value: function(value) {
            var self = this;

            if (typeof(value) != "undefined") {
                self._value = value;
                self.removeClass('placeholder');

                if (self._rendered) {
                    self.getEl('inp').value = value;
                }

                return self;
            }

            if (self._rendered) {
                value = self.getEl('inp').value;

                if (value != self.settings.placeholder) {
                    return value;
                }

                return '';
            }

            return self._value;
        },

        /**
         * Getter/setter function for the disabled state.
         *
         * @method value
         * @param {Boolean} [state] State to be set.
         * @return {Boolean|tinymce.ui.ComboBox} True/false or self if it's a set operation.
         */
        disabled: function(state) {
            var self = this;

            if (self._rendered && typeof(state) != 'undefined') {
                self.getEl('inp').disabled = state;
            }

            return self._super(state);
        },

        /**
         * Focuses the input area of the control.
         *
         * @method focus
         */
        focus: function() {
            this.getEl('inp').focus();
        },

        /**
         * Repaints the control after a layout operation.
         *
         * @method repaint
         */
        repaint: function() {
            var self = this, elm = self.getEl(), openElm = self.getEl('open'), rect = self.layoutRect();
            var width, lineHeight;

            if (openElm) {
                width = rect.w - DomUtils.getSize(openElm).width - 10;
            } else {
                width = rect.w - 10;
            }

            // Detect old IE 7+8 add lineHeight to align caret vertically in the middle
            var doc = document;
            if (doc.all && (!doc.documentMode || doc.documentMode <= 8)) {
                lineHeight = (self.layoutRect().h - 2) + 'px';
            }

            DomUtils.css(elm.firstChild, {
                width: width,
                lineHeight: lineHeight
            });

            self._super();

            return self;
        },

        /**
         * Post render method. Called after the control has been rendered to the target.
         *
         * @method postRender
         * @return {tinymce.ui.ComboBox} Current combobox instance.
         */
        postRender: function() {
            var self = this;

            DomUtils.on(this.getEl('inp'), 'change', function() {
                self.fire('change');
            });

            return self._super();
        },

        remove: function() {
            DomUtils.off(this.getEl('inp'));
            this._super();
        },

        /**
         * Renders the control as a HTML string.
         *
         * @method renderHtml
         * @return {String} HTML representing the control.
         */
        renderHtml: function() {
            var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix;
            var value = settings.value || settings.placeholder || '';
            var icon, text, openBtnHtml = '', extraAttrs = '';

            if ("spellcheck" in settings) {
                extraAttrs += ' spellcheck="' + settings.spellcheck + '"';
            }

            if (settings.maxLength) {
                extraAttrs += ' maxlength="' + settings.maxLength + '"';
            }

            if (settings.size) {
                extraAttrs += ' size="' + settings.size + '"';
            }

            if (settings.subtype) {
                extraAttrs += ' type="' + settings.subtype + '"';
            }

            if (self.disabled()) {
                extraAttrs += ' disabled="disabled"';
            }

            icon = settings.icon;
            if (icon && icon != 'caret') {
                icon = prefix + 'ico ' + prefix + 'i-' + settings.icon;
            }

            text = self._text;

            if (icon || text) {
                openBtnHtml = (
                    '<div id="' + id + '-open" class="' + prefix + 'btn ' + prefix + 'open" tabIndex="-1" role="button">' +
                        '<button id="' + id + '-action" type="button" hidefocus="1" tabindex="-1">' +
                            (icon != 'caret' ? '<i class="' + icon + '"></i>' : '<i class="' + prefix + 'caret"></i>') +
                            (text ? (icon ? ' ' : '') + text : '') +
                        '</button>' +
                    '</div>'
                );

                self.addClass('has-open');
            }

            return (
                '<div id="' + id + '" class="' + self.classes() + '">' +
                    '<input id="' + id + '-inp" class="' + prefix + 'textbox ' + prefix + 'placeholder" value="' +
                    value + '" hidefocus="1"' + extraAttrs + ' />' +
                    openBtnHtml +
                '</div>'
            );
        }
    });
});

// Included from: js/tinymce/classes/ui/ColorBox.js

/**
 * ColorBox.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This widget lets you enter colors and browse for colors by pressing the color button. It also displays
 * a preview of the current color.
 *
 * @-x-less ColorBox.less
 * @class tinymce.ui.ColorBox
 * @extends tinymce.ui.ComboBox
 */
define("tinymce/ui/ColorBox", [
    "tinymce/ui/ComboBox"
], function(ComboBox) {
    "use strict";

    return ComboBox.extend({
        /**
         * Constructs a new control instance with the specified settings.
         *
         * @constructor
         * @param {Object} settings Name/value object with settings.
         */
        init: function(settings) {
            var self = this;

            settings.spellcheck = false;
            settings.icon = 'none';

            self._super(settings);

            self.addClass('colorbox');
            self.on('change keyup postrender', function() {
                self.repaintColor(self.value());
            });
        },

        repaintColor: function(value) {
            this.getEl().getElementsByTagName('i')[0].style.background = value;
        },

        value: function(value) {
            var self = this;

            if (typeof value != "undefined") {
                if (self._rendered) {
                    self.repaintColor(value);
                }
            }

            return self._super(value);
        }
    });
});

// Included from: js/tinymce/classes/ui/PanelButton.js

/**
 * PanelButton.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Creates a new panel button.
 *
 * @class tinymce.ui.PanelButton
 * @extends tinymce.ui.Button
 */
define("tinymce/ui/PanelButton", [
    "tinymce/ui/Button",
    "tinymce/ui/FloatPanel"
], function(Button, FloatPanel) {
    "use strict";

    return Button.extend({
        /**
         * Shows the panel for the button.
         *
         * @method showPanel
         */
        showPanel: function() {
            var self = this, settings = self.settings;

            self.active(true);

            if (!self.panel) {
                var panelSettings = settings.panel;

                // Wrap panel in grid layout if type if specified
                // This makes it possible to add forms or other containers directly in the panel option
                if (panelSettings.type) {
                    panelSettings = {
                        layout: 'grid',
                        items: panelSettings
                    };
                }

                panelSettings.role = panelSettings.role || 'dialog';
                panelSettings.popover = true;
                panelSettings.autohide = true;
                panelSettings.ariaRoot = true;

                self.panel = new FloatPanel(panelSettings).on('hide', function() {
                    self.active(false);
                }).on('cancel', function(e) {
                    e.stopPropagation();
                    self.focus();
                    self.hidePanel();
                }).parent(self).renderTo(self.getContainerElm());

                self.panel.fire('show');
                self.panel.reflow();
            } else {
                self.panel.show();
            }

            self.panel.moveRel(self.getEl(), settings.popoverAlign || (self.isRtl() ? ['bc-tr', 'bc-tc'] : ['bc-tl', 'bc-tc']));
        },

        /**
         * Hides the panel for the button.
         *
         * @method hidePanel
         */
        hidePanel: function() {
            var self = this;

            if (self.panel) {
                self.panel.hide();
            }
        },

        /**
         * Called after the control has been rendered.
         *
         * @method postRender
         */
        postRender: function() {
            var self = this;

            self.aria('haspopup', true);

            self.on('click', function(e) {
                if (e.control === self) {
                    if (self.panel && self.panel.visible()) {
                        self.hidePanel();
                    } else {
                        self.showPanel();
                        self.panel.focus(!!e.aria);
                    }
                }
            });

            return self._super();
        }
    });
});

// Included from: js/tinymce/classes/ui/ColorButton.js

/**
 * ColorButton.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class creates a color button control. This is a split button in which the main
 * button has a visual representation of the currently selected color. When clicked
 * the caret button displays a color picker, allowing the user to select a new color.
 *
 * @-x-less ColorButton.less
 * @class tinymce.ui.ColorButton
 * @extends tinymce.ui.PanelButton
 */
define("tinymce/ui/ColorButton", [
    "tinymce/ui/PanelButton",
    "tinymce/dom/DOMUtils"
], function(PanelButton, DomUtils) {
    "use strict";

    var DOM = DomUtils.DOM;

    return PanelButton.extend({
        /**
         * Constructs a new ColorButton instance with the specified settings.
         *
         * @constructor
         * @param {Object} settings Name/value object with settings.
         */
        init: function(settings) {
            this._super(settings);
            this.addClass('colorbutton');
        },

        /**
         * Getter/setter for the current color.
         *
         * @method color
         * @param {String} [color] Color to set.
         * @return {String|tinymce.ui.ColorButton} Current color or current instance.
         */
        color: function(color) {
            if (color) {
                this._color = color;
                this.getEl('preview').style.backgroundColor = color;
                return this;
            }

            return this._color;
        },

        /**
         * Renders the control as a HTML string.
         *
         * @method renderHtml
         * @return {String} HTML representing the control.
         */
        renderHtml: function() {
            var self = this, id = self._id, prefix = self.classPrefix;
            var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
            var image = self.settings.image ? ' style="background-image: url(\'' + self.settings.image + '\')"' : '';

            return (
                '<div id="' + id + '" class="' + self.classes() + '" role="button" tabindex="-1" aria-haspopup="true">' +
                    '<button role="presentation" hidefocus="1" type="button" tabindex="-1">' +
                        (icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
                        '<span id="' + id + '-preview" class="' + prefix + 'preview"></span>' +
                        (self._text ? (icon ? ' ' : '') + (self._text) : '') +
                    '</button>' +
                    '<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' +
                        ' <i class="' + prefix + 'caret"></i>' +
                    '</button>' +
                '</div>'
            );
        },

        /**
         * Called after the control has been rendered.
         *
         * @method postRender
         */
        postRender: function() {
            var self = this, onClickHandler = self.settings.onclick;

            self.on('click', function(e) {
                if (e.aria && e.aria.key == 'down') {
                    return;
                }

                if (e.control == self && !DOM.getParent(e.target, '.' + self.classPrefix + 'open')) {
                    e.stopImmediatePropagation();
                    onClickHandler.call(self, e);
                }
            });

            delete self.settings.onclick;

            return self._super();
        }
    });
});

// Included from: js/tinymce/classes/util/Color.js

/**
 * Color.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class lets you parse/serialize colors and convert rgb/hsb.
 *
 * @class tinymce.util.Color
 * @example
 * var white = new tinymce.util.Color({r: 255, g: 255, b: 255});
 * var red = new tinymce.util.Color('#FF0000');
 *
 * console.log(white.toHex(), red.toHsv());
 */
define("tinymce/util/Color", [], function() {
    var min = Math.min, max = Math.max, round = Math.round;

    /**
     * Constructs a new color instance.
     *
     * @constructor
     * @method Color
     * @param {String} value Optional initial value to parse.
     */
    function Color(value) {
        var self = this, r = 0, g = 0, b = 0;

        function rgb2hsv(r, g, b) {
            var h, s, v, d, minRGB, maxRGB;

            h = 0;
            s = 0;
            v = 0;
            r = r / 255;
            g = g / 255;
            b = b / 255;

            minRGB = min(r, min(g, b));
            maxRGB = max(r, max(g, b));

            if (minRGB == maxRGB) {
                v = minRGB;

                return {
                    h: 0,
                    s: 0,
                    v: v * 100
                };
            }

            /*eslint no-nested-ternary:0 */
            d = (r == minRGB) ? g - b : ((b == minRGB) ? r - g : b - r);
            h = (r == minRGB) ? 3 : ((b == minRGB) ? 1 : 5);
            h = 60 * (h - d / (maxRGB - minRGB));
            s = (maxRGB - minRGB) / maxRGB;
            v = maxRGB;

            return {
                h: round(h),
                s: round(s * 100),
                v: round(v * 100)
            };
        }

        function hsvToRgb(hue, saturation, brightness) {
            var side, chroma, x, match;

            hue = (parseInt(hue, 10) || 0) % 360;
            saturation = parseInt(saturation, 10) / 100;
            brightness = parseInt(brightness, 10) / 100;
            saturation = max(0, min(saturation, 1));
            brightness = max(0, min(brightness, 1));

            if (saturation === 0) {
                r = g = b = round(255 * brightness);
                return;
            }

            side = hue / 60;
            chroma = brightness * saturation;
            x = chroma * (1 - Math.abs(side % 2 - 1));
            match = brightness - chroma;

            switch (Math.floor(side)) {
                case 0:
                    r = chroma;
                    g = x;
                    b = 0;
                    break;

                case 1:
                    r = x;
                    g = chroma;
                    b = 0;
                    break;

                case 2:
                    r = 0;
                    g = chroma;
                    b = x;
                    break;

                case 3:
                    r = 0;
                    g = x;
                    b = chroma;
                    break;

                case 4:
                    r = x;
                    g = 0;
                    b = chroma;
                    break;

                case 5:
                    r = chroma;
                    g = 0;
                    b = x;
                    break;

                default:
                    r = g = b = 0;
            }

            r = round(255 * (r + match));
            g = round(255 * (g + match));
            b = round(255 * (b + match));
        }

        /**
         * Returns the hex string of the current color. For example: #ff00ff
         *
         * @method toHex
         * @return {String} Hex string of current color.
         */
        function toHex() {
            function hex(val) {
                val = parseInt(val, 10).toString(16);

                return val.length > 1 ? val : '0' + val;
            }

            return '#' + hex(r) + hex(g) + hex(b);
        }

        /**
         * Returns the r, g, b values of the color. Each channel has a range from 0-255.
         *
         * @method toRgb
         * @return {Object} Object with r, g, b fields.
         */
        function toRgb() {
            return {
                r: r,
                g: g,
                b: b
            };
        }

        /**
         * Returns the h, s, v values of the color. Ranges: h=0-360, s=0-100, v=0-100.
         *
         * @method toHsv
         * @return {Object} Object with h, s, v fields.
         */
        function toHsv() {
            return rgb2hsv(r, g, b);
        }

        /**
         * Parses the specified value and populates the color instance.
         *
         * Supported format examples:
         *  * rbg(255,0,0)
         *  * #ff0000
         *  * #fff
         *  * {r: 255, g: 0, b: 0}
         *  * {h: 360, s: 100, v: 100}
         *
         * @method parse
         * @param {Object/String} value Color value to parse.
         * @return {tinymce.util.Color} Current color instance.
         */
        function parse(value) {
            var matches;

            if (typeof value == 'object') {
                if ("r" in value) {
                    r = value.r;
                    g = value.g;
                    b = value.b;
                } else if ("v" in value) {
                    hsvToRgb(value.h, value.s, value.v);
                }
            } else {
                if ((matches = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)[^\)]*\)/gi.exec(value))) {
                    r = parseInt(matches[1], 10);
                    g = parseInt(matches[2], 10);
                    b = parseInt(matches[3], 10);
                } else if ((matches = /#([0-F]{2})([0-F]{2})([0-F]{2})/gi.exec(value))) {
                    r = parseInt(matches[1], 16);
                    g = parseInt(matches[2], 16);
                    b = parseInt(matches[3], 16);
                } else if ((matches = /#([0-F])([0-F])([0-F])/gi.exec(value))) {
                    r = parseInt(matches[1] + matches[1], 16);
                    g = parseInt(matches[2] + matches[2], 16);
                    b = parseInt(matches[3] + matches[3], 16);
                }
            }

            r = r < 0 ? 0 : (r > 255 ? 255 : r);
            g = g < 0 ? 0 : (g > 255 ? 255 : g);
            b = b < 0 ? 0 : (b > 255 ? 255 : b);

            return self;
        }

        if (value) {
            parse(value);
        }

        self.toRgb = toRgb;
        self.toHsv = toHsv;
        self.toHex = toHex;
        self.parse = parse;
    }

    return Color;
});

// Included from: js/tinymce/classes/ui/ColorPicker.js

/**
 * ColorPicker.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Color picker widget lets you select colors.
 *
 * @-x-less ColorPicker.less
 * @class tinymce.ui.ColorPicker
 * @extends tinymce.ui.Widget
 */
define("tinymce/ui/ColorPicker", [
    "tinymce/ui/Widget",
    "tinymce/ui/DragHelper",
    "tinymce/ui/DomUtils",
    "tinymce/util/Color"
], function(Widget, DragHelper, DomUtils, Color) {
    "use strict";

    return Widget.extend({
        Defaults: {
            classes: "widget colorpicker"
        },

        /**
         * Constructs a new colorpicker instance with the specified settings.
         *
         * @constructor
         * @param {Object} settings Name/value object with settings.
         * @setting {String} color Initial color value.
         */
        init: function(settings) {
            this._super(settings);
        },

        postRender: function() {
            var self = this, color = self.color(), hsv, hueRootElm, huePointElm, svRootElm, svPointElm;

            hueRootElm = self.getEl('h');
            huePointElm = self.getEl('hp');
            svRootElm = self.getEl('sv');
            svPointElm = self.getEl('svp');

            function getPos(elm, event) {
                var pos = DomUtils.getPos(elm), x, y;

                x = event.pageX - pos.x;
                y = event.pageY - pos.y;

                x = Math.max(0, Math.min(x / elm.clientWidth, 1));
                y = Math.max(0, Math.min(y / elm.clientHeight, 1));

                return {
                    x: x,
                    y: y
                };
            }

            function updateColor(hsv, hueUpdate) {
                var hue = (360 - hsv.h) / 360;

                DomUtils.css(huePointElm, {
                    top: (hue * 100) + '%'
                });

                if (!hueUpdate) {
                    DomUtils.css(svPointElm, {
                        left: hsv.s + '%',
                        top: (100 - hsv.v) + '%'
                    });
                }

                svRootElm.style.background = new Color({s: 100, v: 100, h: hsv.h}).toHex();
                self.color().parse({s: hsv.s, v: hsv.v, h: hsv.h});
            }

            function updateSaturationAndValue(e) {
                var pos;

                pos = getPos(svRootElm, e);
                hsv.s = pos.x * 100;
                hsv.v = (1 - pos.y) * 100;

                updateColor(hsv);
                self.fire('change');
            }

            function updateHue(e) {
                var pos;

                pos = getPos(hueRootElm, e);
                hsv = color.toHsv();
                hsv.h = (1 - pos.y) * 360;
                updateColor(hsv, true);
                self.fire('change');
            }

            self._repaint = function() {
                hsv = color.toHsv();
                updateColor(hsv);
            };

            self._super();

            self._svdraghelper = new DragHelper(self._id + '-sv', {
                start: updateSaturationAndValue,
                drag: updateSaturationAndValue
            });

            self._hdraghelper = new DragHelper(self._id + '-h', {
                start: updateHue,
                drag: updateHue
            });

            self._repaint();
        },

        rgb: function() {
            return this.color().toRgb();
        },

        value: function(value) {
            var self = this;

            if (arguments.length) {
                self.color().parse(value);

                if (self._rendered) {
                    self._repaint();
                }
            } else {
                return self.color().toHex();
            }
        },

        color: function() {
            if (!this._color) {
                this._color = new Color();
            }

            return this._color;
        },

        /**
         * Renders the control as a HTML string.
         *
         * @method renderHtml
         * @return {String} HTML representing the control.
         */
        renderHtml: function() {
            var self = this, id = self._id, prefix = self.classPrefix, hueHtml;
            var stops = '#ff0000,#ff0080,#ff00ff,#8000ff,#0000ff,#0080ff,#00ffff,#00ff80,#00ff00,#80ff00,#ffff00,#ff8000,#ff0000';

            function getOldIeFallbackHtml() {
                var i, l, html = '', gradientPrefix, stopsList;

                gradientPrefix = 'filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=';
                stopsList = stops.split(',');
                for (i = 0, l = stopsList.length - 1; i < l; i++) {
                    html += (
                        '<div class="' + prefix + 'colorpicker-h-chunk" style="' +
                            'height:' + (100 / l) + '%;' +
                            gradientPrefix + stopsList[i] + ',endColorstr=' + stopsList[i + 1] + ');' +
                            '-ms-' + gradientPrefix + stopsList[i] + ',endColorstr=' + stopsList[i + 1] + ')' +
                        '"></div>'
                    );
                }

                return html;
            }

            var gradientCssText = (
                'background: -ms-linear-gradient(top,' + stops + ');' +
                'background: linear-gradient(to bottom,' + stops + ');'
            );

            hueHtml = (
                '<div id="' + id + '-h" class="' + prefix + 'colorpicker-h" style="' + gradientCssText + '">' +
                    getOldIeFallbackHtml() +
                    '<div id="' + id + '-hp" class="' + prefix + 'colorpicker-h-marker"></div>' +
                '</div>'
            );

            return (
                '<div id="' + id + '" class="' + self.classes() + '">' +
                    '<div id="' + id + '-sv" class="' + prefix + 'colorpicker-sv">' +
                        '<div class="' + prefix + 'colorpicker-overlay1">' +
                            '<div class="' + prefix + 'colorpicker-overlay2">' +
                                '<div id="' + id + '-svp" class="' + prefix + 'colorpicker-selector1">' +
                                    '<div class="' + prefix + 'colorpicker-selector2"></div>' +
                                '</div>' +
                            '</div>' +
                        '</div>' +
                    '</div>' +
                    hueHtml +
                '</div>'
            );
        }
    });
});

// Included from: js/tinymce/classes/ui/Path.js

/**
 * Path.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Creates a new path control.
 *
 * @-x-less Path.less
 * @class tinymce.ui.Path
 * @extends tinymce.ui.Widget
 */
define("tinymce/ui/Path", [
    "tinymce/ui/Widget"
], function(Widget) {
    "use strict";

    return Widget.extend({
        /**
         * Constructs a instance with the specified settings.
         *
         * @constructor
         * @param {Object} settings Name/value object with settings.
         * @setting {String} delimiter Delimiter to display between items in path.
         */
        init: function(settings) {
            var self = this;

            if (!settings.delimiter) {
                settings.delimiter = '\u00BB';
            }

            self._super(settings);
            self.addClass('path');
            self.canFocus = true;

            self.on('click', function(e) {
                var index, target = e.target;

                if ((index = target.getAttribute('data-index'))) {
                    self.fire('select', {value: self.data()[index], index: index});
                }
            });
        },

        /**
         * Focuses the current control.
         *
         * @method focus
         * @return {tinymce.ui.Control} Current control instance.
         */
        focus: function() {
            var self = this;

            self.getEl().firstChild.focus();

            return self;
        },

        /**
         * Sets/gets the data to be used for the path.
         *
         * @method data
         * @param {Array} data Array with items name is rendered to path.
         */
        data: function(data) {
            var self = this;

            if (typeof(data) !== "undefined") {
                self._data = data;
                self.update();

                return self;
            }

            return self._data;
        },

        /**
         * Updated the path.
         *
         * @private
         */
        update: function() {
            this.innerHtml(this._getPathHtml());
        },

        /**
         * Called after the control has been rendered.
         *
         * @method postRender
         */
        postRender: function() {
            var self = this;

            self._super();

            self.data(self.settings.data);
        },

        /**
         * Renders the control as a HTML string.
         *
         * @method renderHtml
         * @return {String} HTML representing the control.
         */
        renderHtml: function() {
            var self = this;

            return (
                '<div id="' + self._id + '" class="' + self.classes() + '">' +
                    self._getPathHtml() +
                '</div>'
            );
        },

        _getPathHtml: function() {
            var self = this, parts = self._data || [], i, l, html = '', prefix = self.classPrefix;

            for (i = 0, l = parts.length; i < l; i++) {
                html += (
                    (i > 0 ? '<div class="' + prefix + 'divider" aria-hidden="true"> ' + self.settings.delimiter + ' </div>' : '') +
                    '<div role="button" class="' + prefix + 'path-item' + (i == l - 1 ? ' ' + prefix + 'last' : '') + '" data-index="' +
                    i + '" tabindex="-1" id="' + self._id + '-' + i + '" aria-level="' + i + '">' + parts[i].name + '</div>'
                );
            }

            if (!html) {
                html = '<div class="' + prefix + 'path-item">\u00a0</div>';
            }

            return html;
        }
    });
});

// Included from: js/tinymce/classes/ui/ElementPath.js

/**
 * ElementPath.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This control creates an path for the current selections parent elements in TinyMCE.
 *
 * @class tinymce.ui.ElementPath
 * @extends tinymce.ui.Path
 */
define("tinymce/ui/ElementPath", [
    "tinymce/ui/Path",
    "tinymce/EditorManager"
], function(Path, EditorManager) {
    return Path.extend({
        /**
         * Post render method. Called after the control has been rendered to the target.
         *
         * @method postRender
         * @return {tinymce.ui.ElementPath} Current combobox instance.
         */
        postRender: function() {
            var self = this, editor = EditorManager.activeEditor;

            function isHidden(elm) {
                if (elm.nodeType === 1) {
                    if (elm.nodeName == "BR" || !!elm.getAttribute('data-mce-bogus')) {
                        return true;
                    }

                    if (elm.getAttribute('data-mce-type') === 'bookmark') {
                        return true;
                    }
                }

                return false;
            }

            self.on('select', function(e) {
                editor.focus();
                editor.selection.select(this.data()[e.index].element);
                editor.nodeChanged();
            });

            editor.on('nodeChange', function(e) {
                var outParents = [], parents = e.parents, i = parents.length;

                while (i--) {
                    if (parents[i].nodeType == 1 && !isHidden(parents[i])) {
                        var args = editor.fire('ResolveName', {
                            name: parents[i].nodeName.toLowerCase(),
                            target: parents[i]
                        });

                        if (!args.isDefaultPrevented()) {
                            outParents.push({name: args.name, element: parents[i]});
                        }

                        if (args.isPropagationStopped()) {
                            break;
                        }
                    }
                }

                self.data(outParents);
            });

            return self._super();
        }
    });
});

// Included from: js/tinymce/classes/ui/FormItem.js

/**
 * FormItem.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class is a container created by the form element with
 * a label and control item.
 *
 * @class tinymce.ui.FormItem
 * @extends tinymce.ui.Container
 * @setting {String} label Label to display for the form item.
 */
define("tinymce/ui/FormItem", [
    "tinymce/ui/Container"
], function(Container) {
    "use strict";

    return Container.extend({
        Defaults: {
            layout: 'flex',
            align: 'center',
            defaults: {
                flex: 1
            }
        },

        /**
         * Renders the control as a HTML string.
         *
         * @method renderHtml
         * @return {String} HTML representing the control.
         */
        renderHtml: function() {
            var self = this, layout = self._layout, prefix = self.classPrefix;

            self.addClass('formitem');
            layout.preRender(self);

            return (
                '<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
                    (self.settings.title ? ('<div id="' + self._id + '-title" class="' + prefix + 'title">' +
                        self.settings.title + '</div>') : '') +
                    '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
                        (self.settings.html || '') + layout.renderHtml(self) +
                    '</div>' +
                '</div>'
            );
        }
    });
});

// Included from: js/tinymce/classes/ui/Form.js

/**
 * Form.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class creates a form container. A form container has the ability
 * to automatically wrap items in tinymce.ui.FormItem instances.
 *
 * Each FormItem instance is a container for the label and the item.
 *
 * @example
 * tinymce.ui.Factory.create({
 *     type: 'form',
 *     items: [
 *         {type: 'textbox', label: 'My text box'}
 *     ]
 * }).renderTo(document.body);
 *
 * @class tinymce.ui.Form
 * @extends tinymce.ui.Container
 */
define("tinymce/ui/Form", [
    "tinymce/ui/Container",
    "tinymce/ui/FormItem",
    "tinymce/util/Tools"
], function(Container, FormItem, Tools) {
    "use strict";

    return Container.extend({
        Defaults: {
            containerCls: 'form',
            layout: 'flex',
            direction: 'column',
            align: 'stretch',
            flex: 1,
            padding: 20,
            labelGap: 30,
            spacing: 10,
            callbacks: {
                submit: function() {
                    this.submit();
                }
            }
        },

        /**
         * This method gets invoked before the control is rendered.
         *
         * @method preRender
         */
        preRender: function() {
            var self = this, items = self.items();

            if (!self.settings.formItemDefaults) {
                self.settings.formItemDefaults = {
                    layout: 'flex',
                    autoResize: "overflow",
                    defaults: {flex: 1}
                };
            }

            // Wrap any labeled items in FormItems
            items.each(function(ctrl) {
                var formItem, label = ctrl.settings.label;

                if (label) {
                    formItem = new FormItem(Tools.extend({
                        items: {
                            type: 'label',
                            id: ctrl._id + '-l',
                            text: label,
                            flex: 0,
                            forId: ctrl._id,
                            disabled: ctrl.disabled()
                        }
                    }, self.settings.formItemDefaults));

                    formItem.type = 'formitem';
                    ctrl.aria('labelledby', ctrl._id + '-l');

                    if (typeof(ctrl.settings.flex) == "undefined") {
                        ctrl.settings.flex = 1;
                    }

                    self.replace(ctrl, formItem);
                    formItem.add(ctrl);
                }
            });
        },

        /**
         * Recalcs label widths.
         *
         * @private
         */
        recalcLabels: function() {
            var self = this, maxLabelWidth = 0, labels = [], i, labelGap, items;

            if (self.settings.labelGapCalc === false) {
                return;
            }

            if (self.settings.labelGapCalc == "children") {
                items = self.find('formitem');
            } else {
                items = self.items();
            }

            items.filter('formitem').each(function(item) {
                var labelCtrl = item.items()[0], labelWidth = labelCtrl.getEl().clientWidth;

                maxLabelWidth = labelWidth > maxLabelWidth ? labelWidth : maxLabelWidth;
                labels.push(labelCtrl);
            });

            labelGap = self.settings.labelGap || 0;

            i = labels.length;
            while (i--) {
                labels[i].settings.minWidth = maxLabelWidth + labelGap;
            }
        },

        /**
         * Getter/setter for the visibility state.
         *
         * @method visible
         * @param {Boolean} [state] True/false state to show/hide.
         * @return {tinymce.ui.Form|Boolean} True/false state or current control.
         */
        visible: function(state) {
            var val = this._super(state);

            if (state === true && this._rendered) {
                this.recalcLabels();
            }

            return val;
        },

        /**
         * Fires a submit event with the serialized form.
         *
         * @method submit
         * @return {Object} Event arguments object.
         */
        submit: function() {
            return this.fire('submit', {data: this.toJSON()});
        },

        /**
         * Post render method. Called after the control has been rendered to the target.
         *
         * @method postRender
         * @return {tinymce.ui.ComboBox} Current combobox instance.
         */
        postRender: function() {
            var self = this;

            self._super();
            self.recalcLabels();
            self.fromJSON(self.settings.data);
        }
    });
});

// Included from: js/tinymce/classes/ui/FieldSet.js

/**
 * FieldSet.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class creates fieldset containers.
 *
 * @-x-less FieldSet.less
 * @class tinymce.ui.FieldSet
 * @extends tinymce.ui.Form
 */
define("tinymce/ui/FieldSet", [
    "tinymce/ui/Form"
], function(Form) {
    "use strict";

    return Form.extend({
        Defaults: {
            containerCls: 'fieldset',
            layout: 'flex',
            direction: 'column',
            align: 'stretch',
            flex: 1,
            padding: "25 15 5 15",
            labelGap: 30,
            spacing: 10,
            border: 1
        },

        /**
         * Renders the control as a HTML string.
         *
         * @method renderHtml
         * @return {String} HTML representing the control.
         */
        renderHtml: function() {
            var self = this, layout = self._layout, prefix = self.classPrefix;

            self.preRender();
            layout.preRender(self);

            return (
                '<fieldset id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
                    (self.settings.title ? ('<legend id="' + self._id + '-title" class="' + prefix + 'fieldset-title">' +
                        self.settings.title + '</legend>') : '') +
                    '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
                        (self.settings.html || '') + layout.renderHtml(self) +
                    '</div>' +
                '</fieldset>'
            );
        }
    });
});

// Included from: js/tinymce/classes/ui/FilePicker.js

/**
 * FilePicker.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/*global tinymce:true */

/**
 * This class creates a file picker control.
 *
 * @class tinymce.ui.FilePicker
 * @extends tinymce.ui.ComboBox
 */
define("tinymce/ui/FilePicker", [
    "tinymce/ui/ComboBox",
    "tinymce/util/Tools"
], function(ComboBox, Tools) {
    "use strict";

    return ComboBox.extend({
        /**
         * Constructs a new control instance with the specified settings.
         *
         * @constructor
         * @param {Object} settings Name/value object with settings.
         */
        init: function(settings) {
            var self = this, editor = tinymce.activeEditor, editorSettings = editor.settings;
            var actionCallback, fileBrowserCallback, fileBrowserCallbackTypes;

            settings.spellcheck = false;

            fileBrowserCallbackTypes = editorSettings.file_picker_types || editorSettings.file_browser_callback_types;
            if (fileBrowserCallbackTypes) {
                fileBrowserCallbackTypes = Tools.makeMap(fileBrowserCallbackTypes, /[, ]/);
            }

            if (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype]) {
                fileBrowserCallback = editorSettings.file_picker_callback;
                if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype])) {
                    actionCallback = function() {
                        var meta = self.fire('beforecall').meta;

                        meta = Tools.extend({filetype: settings.filetype}, meta);

                        // file_picker_callback(callback, currentValue, metaData)
                        fileBrowserCallback.call(
                            editor,
                            function(value, meta) {
                                self.value(value).fire('change', {meta: meta});
                            },
                            self.value(),
                            meta
                        );
                    };
                } else {
                    // Legacy callback: file_picker_callback(id, currentValue, filetype, window)
                    fileBrowserCallback = editorSettings.file_browser_callback;
                    if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype])) {
                        actionCallback = function() {
                            fileBrowserCallback(
                                self.getEl('inp').id,
                                self.value(),
                                settings.filetype,
                                window
                            );
                        };
                    }
                }
            }

            if (actionCallback) {
                settings.icon = 'browse';
                settings.onaction = actionCallback;
            }

            self._super(settings);
        }
    });
});

// Included from: js/tinymce/classes/ui/FitLayout.js

/**
 * FitLayout.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This layout manager will resize the control to be the size of it's parent container.
 * In other words width: 100% and height: 100%.
 *
 * @-x-less FitLayout.less
 * @class tinymce.ui.FitLayout
 * @extends tinymce.ui.AbsoluteLayout
 */
define("tinymce/ui/FitLayout", [
    "tinymce/ui/AbsoluteLayout"
], function(AbsoluteLayout) {
    "use strict";

    return AbsoluteLayout.extend({
        /**
         * Recalculates the positions of the controls in the specified container.
         *
         * @method recalc
         * @param {tinymce.ui.Container} container Container instance to recalc.
         */
        recalc: function(container) {
            var contLayoutRect = container.layoutRect(), paddingBox = container.paddingBox();

            container.items().filter(':visible').each(function(ctrl) {
                ctrl.layoutRect({
                    x: paddingBox.left,
                    y: paddingBox.top,
                    w: contLayoutRect.innerW - paddingBox.right - paddingBox.left,
                    h: contLayoutRect.innerH - paddingBox.top - paddingBox.bottom
                });

                if (ctrl.recalc) {
                    ctrl.recalc();
                }
            });
        }
    });
});

// Included from: js/tinymce/classes/ui/FlexLayout.js

/**
 * FlexLayout.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This layout manager works similar to the CSS flex box.
 *
 * @setting {String} direction row|row-reverse|column|column-reverse
 * @setting {Number} flex A positive-number to flex by.
 * @setting {String} align start|end|center|stretch
 * @setting {String} pack start|end|justify
 *
 * @class tinymce.ui.FlexLayout
 * @extends tinymce.ui.AbsoluteLayout
 */
define("tinymce/ui/FlexLayout", [
    "tinymce/ui/AbsoluteLayout"
], function(AbsoluteLayout) {
    "use strict";

    return AbsoluteLayout.extend({
        /**
         * Recalculates the positions of the controls in the specified container.
         *
         * @method recalc
         * @param {tinymce.ui.Container} container Container instance to recalc.
         */
        recalc: function(container) {
            // A ton of variables, needs to be in the same scope for performance
            var i, l, items, contLayoutRect, contPaddingBox, contSettings, align, pack, spacing, totalFlex, availableSpace, direction;
            var ctrl, ctrlLayoutRect, ctrlSettings, flex, maxSizeItems = [], size, maxSize, ratio, rect, pos, maxAlignEndPos;
            var sizeName, minSizeName, posName, maxSizeName, beforeName, innerSizeName, deltaSizeName, contentSizeName;
            var alignAxisName, alignInnerSizeName, alignSizeName, alignMinSizeName, alignBeforeName, alignAfterName;
            var alignDeltaSizeName, alignContentSizeName;
            var max = Math.max, min = Math.min;

            // Get container items, properties and settings
            items = container.items().filter(':visible');
            contLayoutRect = container.layoutRect();
            contPaddingBox = container._paddingBox;
            contSettings = container.settings;
            direction = container.isRtl() ? (contSettings.direction || 'row-reversed') : contSettings.direction;
            align = contSettings.align;
            pack = container.isRtl() ? (contSettings.pack || 'end') : contSettings.pack;
            spacing = contSettings.spacing || 0;

            if (direction == "row-reversed" || direction == "column-reverse") {
                items = items.set(items.toArray().reverse());
                direction = direction.split('-')[0];
            }

            // Setup axis variable name for row/column direction since the calculations is the same
            if (direction == "column") {
                posName = "y";
                sizeName = "h";
                minSizeName = "minH";
                maxSizeName = "maxH";
                innerSizeName = "innerH";
                beforeName = 'top';
                deltaSizeName = "deltaH";
                contentSizeName = "contentH";

                alignBeforeName = "left";
                alignSizeName = "w";
                alignAxisName = "x";
                alignInnerSizeName = "innerW";
                alignMinSizeName = "minW";
                alignAfterName = "right";
                alignDeltaSizeName = "deltaW";
                alignContentSizeName = "contentW";
            } else {
                posName = "x";
                sizeName = "w";
                minSizeName = "minW";
                maxSizeName = "maxW";
                innerSizeName = "innerW";
                beforeName = 'left';
                deltaSizeName = "deltaW";
                contentSizeName = "contentW";

                alignBeforeName = "top";
                alignSizeName = "h";
                alignAxisName = "y";
                alignInnerSizeName = "innerH";
                alignMinSizeName = "minH";
                alignAfterName = "bottom";
                alignDeltaSizeName = "deltaH";
                alignContentSizeName = "contentH";
            }

            // Figure out total flex, availableSpace and collect any max size elements
            availableSpace = contLayoutRect[innerSizeName] - contPaddingBox[beforeName] - contPaddingBox[beforeName];
            maxAlignEndPos = totalFlex = 0;
            for (i = 0, l = items.length; i < l; i++) {
                ctrl = items[i];
                ctrlLayoutRect = ctrl.layoutRect();
                ctrlSettings = ctrl.settings;
                flex = ctrlSettings.flex;
                availableSpace -= (i < l - 1 ? spacing : 0);

                if (flex > 0) {
                    totalFlex += flex;

                    // Flexed item has a max size then we need to check if we will hit that size
                    if (ctrlLayoutRect[maxSizeName]) {
                        maxSizeItems.push(ctrl);
                    }

                    ctrlLayoutRect.flex = flex;
                }

                availableSpace -= ctrlLayoutRect[minSizeName];

                // Calculate the align end position to be used to check for overflow/underflow
                size = contPaddingBox[alignBeforeName] + ctrlLayoutRect[alignMinSizeName] + contPaddingBox[alignAfterName];
                if (size > maxAlignEndPos) {
                    maxAlignEndPos = size;
                }
            }

            // Calculate minW/minH
            rect = {};
            if (availableSpace < 0) {
                rect[minSizeName] = contLayoutRect[minSizeName] - availableSpace + contLayoutRect[deltaSizeName];
            } else {
                rect[minSizeName] = contLayoutRect[innerSizeName] - availableSpace + contLayoutRect[deltaSizeName];
            }

            rect[alignMinSizeName] = maxAlignEndPos + contLayoutRect[alignDeltaSizeName];

            rect[contentSizeName] = contLayoutRect[innerSizeName] - availableSpace;
            rect[alignContentSizeName] = maxAlignEndPos;
            rect.minW = min(rect.minW, contLayoutRect.maxW);
            rect.minH = min(rect.minH, contLayoutRect.maxH);
            rect.minW = max(rect.minW, contLayoutRect.startMinWidth);
            rect.minH = max(rect.minH, contLayoutRect.startMinHeight);

            // Resize container container if minSize was changed
            if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) {
                rect.w = rect.minW;
                rect.h = rect.minH;

                container.layoutRect(rect);
                this.recalc(container);

                // Forced recalc for example if items are hidden/shown
                if (container._lastRect === null) {
                    var parentCtrl = container.parent();
                    if (parentCtrl) {
                        parentCtrl._lastRect = null;
                        parentCtrl.recalc();
                    }
                }

                return;
            }

            // Handle max size elements, check if they will become to wide with current options
            ratio = availableSpace / totalFlex;
            for (i = 0, l = maxSizeItems.length; i < l; i++) {
                ctrl = maxSizeItems[i];
                ctrlLayoutRect = ctrl.layoutRect();
                maxSize = ctrlLayoutRect[maxSizeName];
                size = ctrlLayoutRect[minSizeName] + ctrlLayoutRect.flex * ratio;

                if (size > maxSize) {
                    availableSpace -= (ctrlLayoutRect[maxSizeName] - ctrlLayoutRect[minSizeName]);
                    totalFlex -= ctrlLayoutRect.flex;
                    ctrlLayoutRect.flex = 0;
                    ctrlLayoutRect.maxFlexSize = maxSize;
                } else {
                    ctrlLayoutRect.maxFlexSize = 0;
                }
            }

            // Setup new ratio, target layout rect, start position
            ratio = availableSpace / totalFlex;
            pos = contPaddingBox[beforeName];
            rect = {};

            // Handle pack setting moves the start position to end, center
            if (totalFlex === 0) {
                if (pack == "end") {
                    pos = availableSpace + contPaddingBox[beforeName];
                } else if (pack == "center") {
                    pos = Math.round(
                        (contLayoutRect[innerSizeName] / 2) - ((contLayoutRect[innerSizeName] - availableSpace) / 2)
                    ) + contPaddingBox[beforeName];

                    if (pos < 0) {
                        pos = contPaddingBox[beforeName];
                    }
                } else if (pack == "justify") {
                    pos = contPaddingBox[beforeName];
                    spacing = Math.floor(availableSpace / (items.length - 1));
                }
            }

            // Default aligning (start) the other ones needs to be calculated while doing the layout
            rect[alignAxisName] = contPaddingBox[alignBeforeName];

            // Start laying out controls
            for (i = 0, l = items.length; i < l; i++) {
                ctrl = items[i];
                ctrlLayoutRect = ctrl.layoutRect();
                size = ctrlLayoutRect.maxFlexSize || ctrlLayoutRect[minSizeName];

                // Align the control on the other axis
                if (align === "center") {
                    rect[alignAxisName] = Math.round((contLayoutRect[alignInnerSizeName] / 2) - (ctrlLayoutRect[alignSizeName] / 2));
                } else if (align === "stretch") {
                    rect[alignSizeName] = max(
                        ctrlLayoutRect[alignMinSizeName] || 0,
                        contLayoutRect[alignInnerSizeName] - contPaddingBox[alignBeforeName] - contPaddingBox[alignAfterName]
                    );
                    rect[alignAxisName] = contPaddingBox[alignBeforeName];
                } else if (align === "end") {
                    rect[alignAxisName] = contLayoutRect[alignInnerSizeName]  - ctrlLayoutRect[alignSizeName]  - contPaddingBox.top;
                }

                // Calculate new size based on flex
                if (ctrlLayoutRect.flex > 0) {
                    size += ctrlLayoutRect.flex * ratio;
                }

                rect[sizeName] = size;
                rect[posName] = pos;
                ctrl.layoutRect(rect);

                // Recalculate containers
                if (ctrl.recalc) {
                    ctrl.recalc();
                }

                // Move x/y position
                pos += size + spacing;
            }
        }
    });
});

// Included from: js/tinymce/classes/ui/FlowLayout.js

/**
 * FlowLayout.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This layout manager will place the controls by using the browsers native layout.
 *
 * @-x-less FlowLayout.less
 * @class tinymce.ui.FlowLayout
 * @extends tinymce.ui.Layout
 */
define("tinymce/ui/FlowLayout", [
    "tinymce/ui/Layout"
], function(Layout) {
    return Layout.extend({
        Defaults: {
            containerClass: 'flow-layout',
            controlClass: 'flow-layout-item',
            endClass : 'break'
        },

        /**
         * Recalculates the positions of the controls in the specified container.
         *
         * @method recalc
         * @param {tinymce.ui.Container} container Container instance to recalc.
         */
        recalc: function(container) {
            container.items().filter(':visible').each(function(ctrl) {
                if (ctrl.recalc) {
                    ctrl.recalc();
                }
            });
        }
    });
});

// Included from: js/tinymce/classes/ui/FormatControls.js

/**
 * FormatControls.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Internal class containing all TinyMCE specific control types such as
 * format listboxes, fontlist boxes, toolbar buttons etc.
 *
 * @class tinymce.ui.FormatControls
 */
define("tinymce/ui/FormatControls", [
    "tinymce/ui/Control",
    "tinymce/ui/Widget",
    "tinymce/ui/FloatPanel",
    "tinymce/util/Tools",
    "tinymce/EditorManager",
    "tinymce/Env"
], function(Control, Widget, FloatPanel, Tools, EditorManager, Env) {
    var each = Tools.each;

    EditorManager.on('AddEditor', function(e) {
        if (e.editor.rtl) {
            Control.rtl = true;
        }

        registerControls(e.editor);
    });

    Control.translate = function(text) {
        return EditorManager.translate(text);
    };

    Widget.tooltips = !Env.iOS;

    function registerControls(editor) {
        var formatMenu;

        function createListBoxChangeHandler(items, formatName) {
            return function() {
                var self = this;

                editor.on('nodeChange', function(e) {
                    var formatter = editor.formatter;
                    var value = null;

                    each(e.parents, function(node) {
                        each(items, function(item) {
                            if (formatName) {
                                if (formatter.matchNode(node, formatName, {value: item.value})) {
                                    value = item.value;
                                }
                            } else {
                                if (formatter.matchNode(node, item.value)) {
                                    value = item.value;
                                }
                            }

                            if (value) {
                                return false;
                            }
                        });

                        if (value) {
                            return false;
                        }
                    });

                    self.value(value);
                });
            };
        }

        function createFormats(formats) {
            formats = formats.replace(/;$/, '').split(';');

            var i = formats.length;
            while (i--) {
                formats[i] = formats[i].split('=');
            }

            return formats;
        }

        function createFormatMenu() {
            var count = 0, newFormats = [];

            var defaultStyleFormats = [
                {title: 'Headings', items: [
                    {title: 'Heading 1', format: 'h1'},
                    {title: 'Heading 2', format: 'h2'},
                    {title: 'Heading 3', format: 'h3'},
                    {title: 'Heading 4', format: 'h4'},
                    {title: 'Heading 5', format: 'h5'},
                    {title: 'Heading 6', format: 'h6'}
                ]},

                {title: 'Inline', items: [
                    {title: 'Bold', icon: 'bold', format: 'bold'},
                    {title: 'Italic', icon: 'italic', format: 'italic'},
                    {title: 'Underline', icon: 'underline', format: 'underline'},
                    {title: 'Strikethrough', icon: 'strikethrough', format: 'strikethrough'},
                    {title: 'Superscript', icon: 'superscript', format: 'superscript'},
                    {title: 'Subscript', icon: 'subscript', format: 'subscript'},
                    {title: 'Code', icon: 'code', format: 'code'}
                ]},

                {title: 'Blocks', items: [
                    {title: 'Paragraph', format: 'p'},
                    {title: 'Blockquote', format: 'blockquote'},
                    {title: 'Div', format: 'div'},
                    {title: 'Pre', format: 'pre'}
                ]},

                {title: 'Alignment', items: [
                    {title: 'Left', icon: 'alignleft', format: 'alignleft'},
                    {title: 'Center', icon: 'aligncenter', format: 'aligncenter'},
                    {title: 'Right', icon: 'alignright', format: 'alignright'},
                    {title: 'Justify', icon: 'alignjustify', format: 'alignjustify'}
                ]}
            ];

            function createMenu(formats) {
                var menu = [];

                if (!formats) {
                    return;
                }

                each(formats, function(format) {
                    var menuItem = {
                        text: format.title,
                        icon: format.icon
                    };

                    if (format.items) {
                        menuItem.menu = createMenu(format.items);
                    } else {
                        var formatName = format.format || "custom" + count++;

                        if (!format.format) {
                            format.name = formatName;
                            newFormats.push(format);
                        }

                        menuItem.format = formatName;
                        menuItem.cmd = format.cmd;
                    }

                    menu.push(menuItem);
                });

                return menu;
            }

            function createStylesMenu() {
                var menu;

                if (editor.settings.style_formats_merge) {
                    if (editor.settings.style_formats) {
                        menu = createMenu(defaultStyleFormats.concat(editor.settings.style_formats));
                    } else {
                        menu = createMenu(defaultStyleFormats);
                    }
                } else {
                    menu = createMenu(editor.settings.style_formats || defaultStyleFormats);
                }

                return menu;
            }

            editor.on('init', function() {
                each(newFormats, function(format) {
                    editor.formatter.register(format.name, format);
                });
            });

            return {
                type: 'menu',
                items: createStylesMenu(),
                onPostRender: function(e) {
                    editor.fire('renderFormatsMenu', {control: e.control});
                },
                itemDefaults: {
                    preview: true,

                    textStyle: function() {
                        if (this.settings.format) {
                            return editor.formatter.getCssText(this.settings.format);
                        }
                    },

                    onPostRender: function() {
                        var self = this;

                        self.parent().on('show', function() {
                            var formatName, command;

                            formatName = self.settings.format;
                            if (formatName) {
                                self.disabled(!editor.formatter.canApply(formatName));
                                self.active(editor.formatter.match(formatName));
                            }

                            command = self.settings.cmd;
                            if (command) {
                                self.active(editor.queryCommandState(command));
                            }
                        });
                    },

                    onclick: function() {
                        if (this.settings.format) {
                            toggleFormat(this.settings.format);
                        }

                        if (this.settings.cmd) {
                            editor.execCommand(this.settings.cmd);
                        }
                    }
                }
            };
        }

        formatMenu = createFormatMenu();

        // Simple format controls <control/format>:<UI text>
        each({
            bold: 'Bold',
            italic: 'Italic',
            underline: 'Underline',
            strikethrough: 'Strikethrough',
            subscript: 'Subscript',
            superscript: 'Superscript'
        }, function(text, name) {
            editor.addButton(name, {
                tooltip: text,
                onPostRender: function() {
                    var self = this;

                    // TODO: Fix this
                    if (editor.formatter) {
                        editor.formatter.formatChanged(name, function(state) {
                            self.active(state);
                        });
                    } else {
                        editor.on('init', function() {
                            editor.formatter.formatChanged(name, function(state) {
                                self.active(state);
                            });
                        });
                    }
                },
                onclick: function() {
                    toggleFormat(name);
                }
            });
        });

        // Simple command controls <control>:[<UI text>,<Command>]
        each({
            outdent: ['Decrease indent', 'Outdent'],
            indent: ['Increase indent', 'Indent'],
            cut: ['Cut', 'Cut'],
            copy: ['Copy', 'Copy'],
            paste: ['Paste', 'Paste'],
            help: ['Help', 'mceHelp'],
            selectall: ['Select all', 'SelectAll'],
            hr: ['Insert horizontal rule', 'InsertHorizontalRule'],
            removeformat: ['Clear formatting', 'RemoveFormat'],
            visualaid: ['Visual aids', 'mceToggleVisualAid'],
            newdocument: ['New document', 'mceNewDocument']
        }, function(item, name) {
            editor.addButton(name, {
                tooltip: item[0],
                cmd: item[1]
            });
        });

        // Simple command controls with format state
        each({
            blockquote: ['Blockquote', 'mceBlockQuote'],
            numlist: ['Numbered list', 'InsertOrderedList'],
            bullist: ['Bullet list', 'InsertUnorderedList'],
            subscript: ['Subscript', 'Subscript'],
            superscript: ['Superscript', 'Superscript'],
            alignleft: ['Align left', 'JustifyLeft'],
            aligncenter: ['Align center', 'JustifyCenter'],
            alignright: ['Align right', 'JustifyRight'],
            alignjustify: ['Justify', 'JustifyFull']
        }, function(item, name) {
            editor.addButton(name, {
                tooltip: item[0],
                cmd: item[1],
                onPostRender: function() {
                    var self = this;

                    // TODO: Fix this
                    if (editor.formatter) {
                        editor.formatter.formatChanged(name, function(state) {
                            self.active(state);
                        });
                    } else {
                        editor.on('init', function() {
                            editor.formatter.formatChanged(name, function(state) {
                                self.active(state);
                            });
                        });
                    }
                }
            });
        });

        function toggleUndoRedoState(type) {
            return function() {
                var self = this;

                type = type == 'redo' ? 'hasRedo' : 'hasUndo';

                function checkState() {
                    return editor.undoManager ? editor.undoManager[type]() : false;
                }

                self.disabled(!checkState());
                editor.on('Undo Redo AddUndo TypingUndo ClearUndos', function() {
                    self.disabled(!checkState());
                });
            };
        }

        function toggleVisualAidState() {
            var self = this;

            editor.on('VisualAid', function(e) {
                self.active(e.hasVisual);
            });

            self.active(editor.hasVisual);
        }

        editor.addButton('undo', {
            tooltip: 'Undo',
            onPostRender: toggleUndoRedoState('undo'),
            cmd: 'undo'
        });

        editor.addButton('redo', {
            tooltip: 'Redo',
            onPostRender: toggleUndoRedoState('redo'),
            cmd: 'redo'
        });

        editor.addMenuItem('newdocument', {
            text: 'New document',
            shortcut: 'Ctrl+N',
            icon: 'newdocument',
            cmd: 'mceNewDocument'
        });

        editor.addMenuItem('undo', {
            text: 'Undo',
            icon: 'undo',
            shortcut: 'Ctrl+Z',
            onPostRender: toggleUndoRedoState('undo'),
            cmd: 'undo'
        });

        editor.addMenuItem('redo', {
            text: 'Redo',
            icon: 'redo',
            shortcut: 'Ctrl+Y',
            onPostRender: toggleUndoRedoState('redo'),
            cmd: 'redo'
        });

        editor.addMenuItem('visualaid', {
            text: 'Visual aids',
            selectable: true,
            onPostRender: toggleVisualAidState,
            cmd: 'mceToggleVisualAid'
        });

        each({
            cut: ['Cut', 'Cut', 'Ctrl+X'],
            copy: ['Copy', 'Copy', 'Ctrl+C'],
            paste: ['Paste', 'Paste', 'Ctrl+V'],
            selectall: ['Select all', 'SelectAll', 'Ctrl+A'],
            bold: ['Bold', 'Bold', 'Ctrl+B'],
            italic: ['Italic', 'Italic', 'Ctrl+I'],
            underline: ['Underline', 'Underline'],
            strikethrough: ['Strikethrough', 'Strikethrough'],
            subscript: ['Subscript', 'Subscript'],
            superscript: ['Superscript', 'Superscript'],
            removeformat: ['Clear formatting', 'RemoveFormat']
        }, function(item, name) {
            editor.addMenuItem(name, {
                text: item[0],
                icon: name,
                shortcut: item[2],
                cmd: item[1]
            });
        });

        editor.on('mousedown', function() {
            FloatPanel.hideAll();
        });

        function toggleFormat(fmt) {
            if (fmt.control) {
                fmt = fmt.control.value();
            }

            if (fmt) {
                editor.execCommand('mceToggleFormat', false, fmt);
            }
        }

        editor.addButton('styleselect', {
            type: 'menubutton',
            text: 'Formats',
            menu: formatMenu
        });

        editor.addButton('formatselect', function() {
            var items = [], blocks = createFormats(editor.settings.block_formats ||
                'Paragraph=p;' +
                'Address=address;' +
                'Pre=pre;' +
                'Heading 1=h1;' +
                'Heading 2=h2;' +
                'Heading 3=h3;' +
                'Heading 4=h4;' +
                'Heading 5=h5;' +
                'Heading 6=h6'
            );

            each(blocks, function(block) {
                items.push({
                    text: block[0],
                    value: block[1],
                    textStyle: function() {
                        return editor.formatter.getCssText(block[1]);
                    }
                });
            });

            return {
                type: 'listbox',
                text: blocks[0][0],
                values: items,
                fixedWidth: true,
                onselect: toggleFormat,
                onPostRender: createListBoxChangeHandler(items)
            };
        });

        editor.addButton('fontselect', function() {
            var defaultFontsFormats =
                'Andale Mono=andale mono,times;' +
                'Arial=arial,helvetica,sans-serif;' +
                'Arial Black=arial black,avant garde;' +
                'Book Antiqua=book antiqua,palatino;' +
                'Comic Sans MS=comic sans ms,sans-serif;' +
                'Courier New=courier new,courier;' +
                'Georgia=georgia,palatino;' +
                'Helvetica=helvetica;' +
                'Impact=impact,chicago;' +
                'Symbol=symbol;' +
                'Tahoma=tahoma,arial,helvetica,sans-serif;' +
                'Terminal=terminal,monaco;' +
                'Times New Roman=times new roman,times;' +
                'Trebuchet MS=trebuchet ms,geneva;' +
                'Verdana=verdana,geneva;' +
                'Webdings=webdings;' +
                'Wingdings=wingdings,zapf dingbats';

            var items = [], fonts = createFormats(editor.settings.font_formats || defaultFontsFormats);

            each(fonts, function(font) {
                items.push({
                    text: {raw: font[0]},
                    value: font[1],
                    textStyle: font[1].indexOf('dings') == -1 ? 'font-family:' + font[1] : ''
                });
            });

            return {
                type: 'listbox',
                text: 'Font Family',
                tooltip: 'Font Family',
                values: items,
                fixedWidth: true,
                onPostRender: createListBoxChangeHandler(items, 'fontname'),
                onselect: function(e) {
                    if (e.control.settings.value) {
                        editor.execCommand('FontName', false, e.control.settings.value);
                    }
                }
            };
        });

        editor.addButton('fontsizeselect', function() {
            var items = [], defaultFontsizeFormats = '8pt 10pt 12pt 14pt 18pt 24pt 36pt';
            var fontsize_formats = editor.settings.fontsize_formats || defaultFontsizeFormats;

            each(fontsize_formats.split(' '), function(item) {
                var text = item, value = item;
                // Allow text=value font sizes.
                var values = item.split('=');
                if (values.length > 1) {
                    text = values[0];
                    value = values[1];
                }
                items.push({text: text, value: value});
            });

            return {
                type: 'listbox',
                text: 'Font Sizes',
                tooltip: 'Font Sizes',
                values: items,
                fixedWidth: true,
                onPostRender: createListBoxChangeHandler(items, 'fontsize'),
                onclick: function(e) {
                    if (e.control.settings.value) {
                        editor.execCommand('FontSize', false, e.control.settings.value);
                    }
                }
            };
        });

        editor.addMenuItem('formats', {
            text: 'Formats',
            menu: formatMenu
        });
    }
});

// Included from: js/tinymce/classes/ui/GridLayout.js

/**
 * GridLayout.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This layout manager places controls in a grid.
 *
 * @setting {Number} spacing Spacing between controls.
 * @setting {Number} spacingH Horizontal spacing between controls.
 * @setting {Number} spacingV Vertical spacing between controls.
 * @setting {Number} columns Number of columns to use.
 * @setting {String/Array} alignH start|end|center|stretch or array of values for each column.
 * @setting {String/Array} alignV start|end|center|stretch or array of values for each column.
 * @setting {String} pack start|end
 *
 * @class tinymce.ui.GridLayout
 * @extends tinymce.ui.AbsoluteLayout
 */
define("tinymce/ui/GridLayout", [
    "tinymce/ui/AbsoluteLayout"
], function(AbsoluteLayout) {
    "use strict";

    return AbsoluteLayout.extend({
        /**
         * Recalculates the positions of the controls in the specified container.
         *
         * @method recalc
         * @param {tinymce.ui.Container} container Container instance to recalc.
         */
        recalc: function(container) {
            var settings = container.settings, rows, cols, items, contLayoutRect, width, height, rect,
                ctrlLayoutRect, ctrl, x, y, posX, posY, ctrlSettings, contPaddingBox, align, spacingH, spacingV, alignH, alignV, maxX, maxY,
                colWidths = [], rowHeights = [], ctrlMinWidth, ctrlMinHeight, availableWidth, availableHeight, reverseRows, idx;

            // Get layout settings
            settings = container.settings;
            items = container.items().filter(':visible');
            contLayoutRect = container.layoutRect();
            cols = settings.columns || Math.ceil(Math.sqrt(items.length));
            rows = Math.ceil(items.length / cols);
            spacingH = settings.spacingH || settings.spacing || 0;
            spacingV = settings.spacingV || settings.spacing || 0;
            alignH = settings.alignH || settings.align;
            alignV = settings.alignV || settings.align;
            contPaddingBox = container._paddingBox;
            reverseRows = 'reverseRows' in settings ? settings.reverseRows : container.isRtl();

            if (alignH && typeof(alignH) == "string") {
                alignH = [alignH];
            }

            if (alignV && typeof(alignV) == "string") {
                alignV = [alignV];
            }

            // Zero padd columnWidths
            for (x = 0; x < cols; x++) {
                colWidths.push(0);
            }

            // Zero padd rowHeights
            for (y = 0; y < rows; y++) {
                rowHeights.push(0);
            }

            // Calculate columnWidths and rowHeights
            for (y = 0; y < rows; y++) {
                for (x = 0; x < cols; x++) {
                    ctrl = items[y * cols + x];

                    // Out of bounds
                    if (!ctrl) {
                        break;
                    }

                    ctrlLayoutRect = ctrl.layoutRect();
                    ctrlMinWidth = ctrlLayoutRect.minW;
                    ctrlMinHeight = ctrlLayoutRect.minH;

                    colWidths[x] = ctrlMinWidth > colWidths[x] ? ctrlMinWidth : colWidths[x];
                    rowHeights[y] = ctrlMinHeight > rowHeights[y] ? ctrlMinHeight : rowHeights[y];
                }
            }

            // Calculate maxX
            availableWidth = contLayoutRect.innerW - contPaddingBox.left - contPaddingBox.right;
            for (maxX = 0, x = 0; x < cols; x++) {
                maxX += colWidths[x] + (x > 0 ? spacingH : 0);
                availableWidth -= (x > 0 ? spacingH : 0) + colWidths[x];
            }

            // Calculate maxY
            availableHeight = contLayoutRect.innerH - contPaddingBox.top - contPaddingBox.bottom;
            for (maxY = 0, y = 0; y < rows; y++) {
                maxY += rowHeights[y] + (y > 0 ? spacingV : 0);
                availableHeight -= (y > 0 ? spacingV : 0) + rowHeights[y];
            }

            maxX += contPaddingBox.left + contPaddingBox.right;
            maxY += contPaddingBox.top + contPaddingBox.bottom;

            // Calculate minW/minH
            rect = {};
            rect.minW = maxX + (contLayoutRect.w - contLayoutRect.innerW);
            rect.minH = maxY + (contLayoutRect.h - contLayoutRect.innerH);

            rect.contentW = rect.minW - contLayoutRect.deltaW;
            rect.contentH = rect.minH - contLayoutRect.deltaH;
            rect.minW = Math.min(rect.minW, contLayoutRect.maxW);
            rect.minH = Math.min(rect.minH, contLayoutRect.maxH);
            rect.minW = Math.max(rect.minW, contLayoutRect.startMinWidth);
            rect.minH = Math.max(rect.minH, contLayoutRect.startMinHeight);

            // Resize container container if minSize was changed
            if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) {
                rect.w = rect.minW;
                rect.h = rect.minH;

                container.layoutRect(rect);
                this.recalc(container);

                // Forced recalc for example if items are hidden/shown
                if (container._lastRect === null) {
                    var parentCtrl = container.parent();
                    if (parentCtrl) {
                        parentCtrl._lastRect = null;
                        parentCtrl.recalc();
                    }
                }

                return;
            }

            // Update contentW/contentH so absEnd moves correctly
            if (contLayoutRect.autoResize) {
                rect = container.layoutRect(rect);
                rect.contentW = rect.minW - contLayoutRect.deltaW;
                rect.contentH = rect.minH - contLayoutRect.deltaH;
            }

            var flexV;

            if (settings.packV == 'start') {
                flexV = 0;
            } else {
                flexV = availableHeight > 0 ? Math.floor(availableHeight / rows) : 0;
            }

            // Calculate totalFlex
            var totalFlex = 0;
            var flexWidths = settings.flexWidths;
            if (flexWidths) {
                for (x = 0; x < flexWidths.length; x++) {
                    totalFlex += flexWidths[x];
                }
            } else {
                totalFlex = cols;
            }

            // Calculate new column widths based on flex values
            var ratio = availableWidth / totalFlex;
            for (x = 0; x < cols; x++) {
                colWidths[x] += flexWidths ? flexWidths[x] * ratio : ratio;
            }

            // Move/resize controls
            posY = contPaddingBox.top;
            for (y = 0; y < rows; y++) {
                posX = contPaddingBox.left;
                height = rowHeights[y] + flexV;

                for (x = 0; x < cols; x++) {
                    if (reverseRows) {
                        idx = y * cols + cols - 1 - x;
                    } else {
                        idx = y * cols + x;
                    }

                    ctrl = items[idx];

                    // No more controls to render then break
                    if (!ctrl) {
                        break;
                    }

                    // Get control settings and calculate x, y
                    ctrlSettings = ctrl.settings;
                    ctrlLayoutRect = ctrl.layoutRect();
                    width = Math.max(colWidths[x], ctrlLayoutRect.startMinWidth);
                    ctrlLayoutRect.x = posX;
                    ctrlLayoutRect.y = posY;

                    // Align control horizontal
                    align = ctrlSettings.alignH || (alignH ? (alignH[x] || alignH[0]) : null);
                    if (align == "center") {
                        ctrlLayoutRect.x = posX + (width / 2) - (ctrlLayoutRect.w / 2);
                    } else if (align == "right") {
                        ctrlLayoutRect.x = posX + width - ctrlLayoutRect.w;
                    } else if (align == "stretch") {
                        ctrlLayoutRect.w = width;
                    }

                    // Align control vertical
                    align = ctrlSettings.alignV || (alignV ? (alignV[x] || alignV[0]) : null);
                    if (align == "center") {
                        ctrlLayoutRect.y = posY + (height / 2) - (ctrlLayoutRect.h / 2);
                    } else if (align == "bottom") {
                        ctrlLayoutRect.y = posY + height - ctrlLayoutRect.h;
                    } else if (align == "stretch") {
                        ctrlLayoutRect.h = height;
                    }

                    ctrl.layoutRect(ctrlLayoutRect);

                    posX += width + spacingH;

                    if (ctrl.recalc) {
                        ctrl.recalc();
                    }
                }

                posY += height + spacingV;
            }
        }
    });
});

// Included from: js/tinymce/classes/ui/Iframe.js

/**
 * Iframe.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/*jshint scripturl:true */

/**
 * This class creates an iframe.
 *
 * @setting {String} url Url to open in the iframe.
 *
 * @-x-less Iframe.less
 * @class tinymce.ui.Iframe
 * @extends tinymce.ui.Widget
 */
define("tinymce/ui/Iframe", [
    "tinymce/ui/Widget"
], function(Widget) {
    "use strict";

    return Widget.extend({
        /**
         * Renders the control as a HTML string.
         *
         * @method renderHtml
         * @return {String} HTML representing the control.
         */
        renderHtml: function() {
            var self = this;

            self.addClass('iframe');
            self.canFocus = false;

            /*eslint no-script-url:0 */
            return (
                '<iframe id="' + self._id + '" class="' + self.classes() + '" tabindex="-1" src="' +
                (self.settings.url || "javascript:\'\'") + '" frameborder="0"></iframe>'
            );
        },

        /**
         * Setter for the iframe source.
         *
         * @method src
         * @param {String} src Source URL for iframe.
         */
        src: function(src) {
            this.getEl().src = src;
        },

        /**
         * Inner HTML for the iframe.
         *
         * @method html
         * @param {String} html HTML string to set as HTML inside the iframe.
         * @param {function} callback Optional callback to execute when the iframe body is filled with contents.
         * @return {tinymce.ui.Iframe} Current iframe control.
         */
        html: function(html, callback) {
            var self = this, body = this.getEl().contentWindow.document.body;

            // Wait for iframe to initialize IE 10 takes time
            if (!body) {
                setTimeout(function() {
                    self.html(html);
                }, 0);
            } else {
                body.innerHTML = html;

                if (callback) {
                    callback();
                }
            }

            return this;
        }
    });
});

// Included from: js/tinymce/classes/ui/Label.js

/**
 * Label.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class creates a label element. A label is a simple text control
 * that can be bound to other controls.
 *
 * @-x-less Label.less
 * @class tinymce.ui.Label
 * @extends tinymce.ui.Widget
 */
define("tinymce/ui/Label", [
    "tinymce/ui/Widget",
    "tinymce/ui/DomUtils"
], function(Widget, DomUtils) {
    "use strict";

    return Widget.extend({
        /**
         * Constructs a instance with the specified settings.
         *
         * @constructor
         * @param {Object} settings Name/value object with settings.
         * @param {Boolean} multiline Multiline label.
         */
        init: function(settings) {
            var self = this;

            self._super(settings);
            self.addClass('widget');
            self.addClass('label');
            self.canFocus = false;

            if (settings.multiline) {
                self.addClass('autoscroll');
            }

            if (settings.strong) {
                self.addClass('strong');
            }
        },

        /**
         * Initializes the current controls layout rect.
         * This will be executed by the layout managers to determine the
         * default minWidth/minHeight etc.
         *
         * @method initLayoutRect
         * @return {Object} Layout rect instance.
         */
        initLayoutRect: function() {
            var self = this, layoutRect = self._super();

            if (self.settings.multiline) {
                var size = DomUtils.getSize(self.getEl());

                // Check if the text fits within maxW if not then try word wrapping it
                if (size.width > layoutRect.maxW) {
                    layoutRect.minW = layoutRect.maxW;
                    self.addClass('multiline');
                }

                self.getEl().style.width = layoutRect.minW + 'px';
                layoutRect.startMinH = layoutRect.h = layoutRect.minH = Math.min(layoutRect.maxH, DomUtils.getSize(self.getEl()).height);
            }

            return layoutRect;
        },

        /**
         * Repaints the control after a layout operation.
         *
         * @method repaint
         */
        repaint: function() {
            var self = this;

            if (!self.settings.multiline) {
                self.getEl().style.lineHeight = self.layoutRect().h + 'px';
            }

            return self._super();
        },

        /**
         * Sets/gets the current label text.
         *
         * @method text
         * @param {String} [text] New label text.
         * @return {String|tinymce.ui.Label} Current text or current label instance.
         */
        text: function(text) {
            var self = this;

            if (self._rendered && text) {
                this.innerHtml(self.encode(text));
            }

            return self._super(text);
        },

        /**
         * Renders the control as a HTML string.
         *
         * @method renderHtml
         * @return {String} HTML representing the control.
         */
        renderHtml: function() {
            var self = this, forId = self.settings.forId;

            return (
                '<label id="' + self._id + '" class="' + self.classes() + '"' + (forId ? ' for="' + forId + '"' : '') + '>' +
                    self.encode(self._text) +
                '</label>'
            );
        }
    });
});

// Included from: js/tinymce/classes/ui/Toolbar.js

/**
 * Toolbar.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Creates a new toolbar.
 *
 * @class tinymce.ui.Toolbar
 * @extends tinymce.ui.Container
 */
define("tinymce/ui/Toolbar", [
    "tinymce/ui/Container"
], function(Container) {
    "use strict";

    return Container.extend({
        Defaults: {
            role: 'toolbar',
            layout: 'flow'
        },

        /**
         * Constructs a instance with the specified settings.
         *
         * @constructor
         * @param {Object} settings Name/value object with settings.
         */
        init: function(settings) {
            var self = this;

            self._super(settings);
            self.addClass('toolbar');
        },

        /**
         * Called after the control has been rendered.
         *
         * @method postRender
         */
        postRender: function() {
            var self = this;

            self.items().addClass('toolbar-item');

            return self._super();
        }
    });
});

// Included from: js/tinymce/classes/ui/MenuBar.js

/**
 * MenuBar.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Creates a new menubar.
 *
 * @-x-less MenuBar.less
 * @class tinymce.ui.MenuBar
 * @extends tinymce.ui.Toolbar
 */
define("tinymce/ui/MenuBar", [
    "tinymce/ui/Toolbar"
], function(Toolbar) {
    "use strict";

    return Toolbar.extend({
        Defaults: {
            role: 'menubar',
            containerCls: 'menubar',
            ariaRoot: true,
            defaults: {
                type: 'menubutton'
            }
        }
    });
});

// Included from: js/tinymce/classes/ui/MenuButton.js

/**
 * MenuButton.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Creates a new menu button.
 *
 * @-x-less MenuButton.less
 * @class tinymce.ui.MenuButton
 * @extends tinymce.ui.Button
 */
define("tinymce/ui/MenuButton", [
    "tinymce/ui/Button",
    "tinymce/ui/Factory",
    "tinymce/ui/MenuBar"
], function(Button, Factory, MenuBar) {
    "use strict";

    // TODO: Maybe add as some global function
    function isChildOf(node, parent) {
        while (node) {
            if (parent === node) {
                return true;
            }

            node = node.parentNode;
        }

        return false;
    }

    var MenuButton = Button.extend({
        /**
         * Constructs a instance with the specified settings.
         *
         * @constructor
         * @param {Object} settings Name/value object with settings.
         */
        init: function(settings) {
            var self = this;

            self._renderOpen = true;
            self._super(settings);

            self.addClass('menubtn');

            if (settings.fixedWidth) {
                self.addClass('fixed-width');
            }

            self.aria('haspopup', true);
            self.hasPopup = true;
        },

        /**
         * Shows the menu for the button.
         *
         * @method showMenu
         */
        showMenu: function() {
            var self = this, settings = self.settings, menu;

            if (self.menu && self.menu.visible()) {
                return self.hideMenu();
            }

            if (!self.menu) {
                menu = settings.menu || [];

                // Is menu array then auto constuct menu control
                if (menu.length) {
                    menu = {
                        type: 'menu',
                        items: menu
                    };
                } else {
                    menu.type = menu.type || 'menu';
                }

                self.menu = Factory.create(menu).parent(self).renderTo();
                self.fire('createmenu');
                self.menu.reflow();
                self.menu.on('cancel', function(e) {
                    if (e.control.parent() === self.menu) {
                        e.stopPropagation();
                        self.focus();
                        self.hideMenu();
                    }
                });

                // Move focus to button when a menu item is selected/clicked
                self.menu.on('select', function() {
                    self.focus();
                });

                self.menu.on('show hide', function(e) {
                    if (e.control == self.menu) {
                        self.activeMenu(e.type == 'show');
                    }

                    self.aria('expanded', e.type == 'show');
                }).fire('show');
            }

            self.menu.show();
            self.menu.layoutRect({w: self.layoutRect().w});
            self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']);
        },

        /**
         * Hides the menu for the button.
         *
         * @method hideMenu
         */
        hideMenu: function() {
            var self = this;

            if (self.menu) {
                self.menu.items().each(function(item) {
                    if (item.hideMenu) {
                        item.hideMenu();
                    }
                });

                self.menu.hide();
            }
        },

        /**
         * Sets the active menu state.
         *
         * @private
         */
        activeMenu: function(state) {
            this.toggleClass('active', state);
        },

        /**
         * Renders the control as a HTML string.
         *
         * @method renderHtml
         * @return {String} HTML representing the control.
         */
        renderHtml: function() {
            var self = this, id = self._id, prefix = self.classPrefix;
            var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';

            self.aria('role', self.parent() instanceof MenuBar ? 'menuitem' : 'button');

            return (
                '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1" aria-labelledby="' + id + '">' +
                    '<button id="' + id + '-open" role="presentation" type="button" tabindex="-1">' +
                        (icon ? '<i class="' + icon + '"></i>' : '') +
                        '<span>' + (self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') + '</span>' +
                        ' <i class="' + prefix + 'caret"></i>' +
                    '</button>' +
                '</div>'
            );
        },

        /**
         * Gets invoked after the control has been rendered.
         *
         * @method postRender
         */
        postRender: function() {
            var self = this;

            self.on('click', function(e) {
                if (e.control === self && isChildOf(e.target, self.getEl())) {
                    self.showMenu();

                    if (e.aria) {
                        self.menu.items()[0].focus();
                    }
                }
            });

            self.on('mouseenter', function(e) {
                var overCtrl = e.control, parent = self.parent(), hasVisibleSiblingMenu;

                if (overCtrl && parent && overCtrl instanceof MenuButton && overCtrl.parent() == parent) {
                    parent.items().filter('MenuButton').each(function(ctrl) {
                        if (ctrl.hideMenu && ctrl != overCtrl) {
                            if (ctrl.menu && ctrl.menu.visible()) {
                                hasVisibleSiblingMenu = true;
                            }

                            ctrl.hideMenu();
                        }
                    });

                    if (hasVisibleSiblingMenu) {
                        overCtrl.focus(); // Fix for: #5887
                        overCtrl.showMenu();
                    }
                }
            });

            return self._super();
        },

        /**
         * Sets/gets the current button text.
         *
         * @method text
         * @param {String} [text] New button text.
         * @return {String|tinymce.ui.MenuButton} Current text or current MenuButton instance.
         */
        text: function(text) {
            var self = this, i, children;

            if (self._rendered) {
                children = self.getEl('open').getElementsByTagName('span');
                for (i = 0; i < children.length; i++) {
                    children[i].innerHTML = (self.settings.icon && text ? '\u00a0' : '') + self.encode(text);
                }
            }

            return this._super(text);
        },

        /**
         * Removes the control and it's menus.
         *
         * @method remove
         */
        remove: function() {
            this._super();

            if (this.menu) {
                this.menu.remove();
            }
        }
    });

    return MenuButton;
});

// Included from: js/tinymce/classes/ui/ListBox.js

/**
 * ListBox.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Creates a new list box control.
 *
 * @-x-less ListBox.less
 * @class tinymce.ui.ListBox
 * @extends tinymce.ui.MenuButton
 */
define("tinymce/ui/ListBox", [
    "tinymce/ui/MenuButton"
], function(MenuButton) {
    "use strict";

    return MenuButton.extend({
        /**
         * Constructs a instance with the specified settings.
         *
         * @constructor
         * @param {Object} settings Name/value object with settings.
         * @setting {Array} values Array with values to add to list box.
         */
        init: function(settings) {
            var self = this, values, selected, selectedText, lastItemCtrl;

            function setSelected(menuValues) {
                // Try to find a selected value
                for (var i = 0; i < menuValues.length; i++) {
                    selected = menuValues[i].selected || settings.value === menuValues[i].value;

                    if (selected) {
                        selectedText = selectedText || menuValues[i].text;
                        self._value = menuValues[i].value;
                        break;
                    }

                    // If the value has a submenu, try to find the selected values in that menu
                    if (menuValues[i].menu) {
                        setSelected(menuValues[i].menu);
                    }
                }
            }

            self._values = values = settings.values;
            if (values) {
                if (typeof settings.value != "undefined") {
                    setSelected(values);
                }

                // Default with first item
                if (!selected && values.length > 0) {
                    selectedText = values[0].text;
                    self._value = values[0].value;
                }

                settings.menu = values;
            }

            settings.text = settings.text || selectedText || values[0].text;

            self._super(settings);
            self.addClass('listbox');

            self.on('select', function(e) {
                var ctrl = e.control;

                if (lastItemCtrl) {
                    e.lastControl = lastItemCtrl;
                }

                if (settings.multiple) {
                    ctrl.active(!ctrl.active());
                } else {
                    self.value(e.control.settings.value);
                }

                lastItemCtrl = ctrl;
            });
        },

        /**
         * Getter/setter function for the control value.
         *
         * @method value
         * @param {String} [value] Value to be set.
         * @return {Boolean/tinymce.ui.ListBox} Value or self if it's a set operation.
         */
        value: function(value) {
            var self = this, active, selectedText, menu;

            function activateByValue(menu, value) {
                menu.items().each(function(ctrl) {
                    active = ctrl.value() === value;

                    if (active) {
                        selectedText = selectedText || ctrl.text();
                    }

                    ctrl.active(active);

                    if (ctrl.menu) {
                        activateByValue(ctrl.menu, value);
                    }
                });
            }

            function setActiveValues(menuValues) {
                for (var i = 0; i < menuValues.length; i++) {
                    active = menuValues[i].value == value;

                    if (active) {
                        selectedText = selectedText || menuValues[i].text;
                    }

                    menuValues[i].active = active;

                    if (menuValues[i].menu) {
                        setActiveValues(menuValues[i].menu);
                    }
                }
            }

            if (typeof(value) != "undefined") {
                if (self.menu) {
                    activateByValue(self.menu, value);
                } else {
                    menu = self.settings.menu;
                    setActiveValues(menu);
                }

                self.text(selectedText || this.settings.text);
            }

            return self._super(value);
        }
    });
});

// Included from: js/tinymce/classes/ui/MenuItem.js

/**
 * MenuItem.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Creates a new menu item.
 *
 * @-x-less MenuItem.less
 * @class tinymce.ui.MenuItem
 * @extends tinymce.ui.Widget
 */
define("tinymce/ui/MenuItem", [
    "tinymce/ui/Widget",
    "tinymce/ui/Factory",
    "tinymce/Env"
], function(Widget, Factory, Env) {
    "use strict";

    return Widget.extend({
        Defaults: {
            border: 0,
            role: 'menuitem'
        },

        /**
         * Constructs a instance with the specified settings.
         *
         * @constructor
         * @param {Object} settings Name/value object with settings.
         * @setting {Boolean} selectable Selectable menu.
         * @setting {Array} menu Submenu array with items.
         * @setting {String} shortcut Shortcut to display for menu item. Example: Ctrl+X
         */
        init: function(settings) {
            var self = this;

            self.hasPopup = true;

            self._super(settings);

            settings = self.settings;

            self.addClass('menu-item');

            if (settings.menu) {
                self.addClass('menu-item-expand');
            }

            if (settings.preview) {
                self.addClass('menu-item-preview');
            }

            if (self._text === '-' || self._text === '|') {
                self.addClass('menu-item-sep');
                self.aria('role', 'separator');
                self._text = '-';
            }

            if (settings.selectable) {
                self.aria('role', 'menuitemcheckbox');
                self.addClass('menu-item-checkbox');
                settings.icon = 'selected';
            }

            if (!settings.preview && !settings.selectable) {
                self.addClass('menu-item-normal');
            }

            self.on('mousedown', function(e) {
                e.preventDefault();
            });

            if (settings.menu && !settings.ariaHideMenu) {
                self.aria('haspopup', true);
            }
        },

        /**
         * Returns true/false if the menuitem has sub menu.
         *
         * @method hasMenus
         * @return {Boolean} True/false state if it has submenu.
         */
        hasMenus: function() {
            return !!this.settings.menu;
        },

        /**
         * Shows the menu for the menu item.
         *
         * @method showMenu
         */
        showMenu: function() {
            var self = this, settings = self.settings, menu, parent = self.parent();

            parent.items().each(function(ctrl) {
                if (ctrl !== self) {
                    ctrl.hideMenu();
                }
            });

            if (settings.menu) {
                menu = self.menu;

                if (!menu) {
                    menu = settings.menu;

                    // Is menu array then auto constuct menu control
                    if (menu.length) {
                        menu = {
                            type: 'menu',
                            items: menu
                        };
                    } else {
                        menu.type = menu.type || 'menu';
                    }

                    if (parent.settings.itemDefaults) {
                        menu.itemDefaults = parent.settings.itemDefaults;
                    }

                    menu = self.menu = Factory.create(menu).parent(self).renderTo();
                    menu.reflow();
                    menu.on('cancel', function(e) {
                        e.stopPropagation();
                        self.focus();
                        menu.hide();
                    });
                    menu.on('show hide', function(e) {
                        e.control.items().each(function(ctrl) {
                            ctrl.active(ctrl.settings.selected);
                        });
                    }).fire('show');

                    menu.on('hide', function(e) {
                        if (e.control === menu) {
                            self.removeClass('selected');
                        }
                    });

                    menu.submenu = true;
                } else {
                    menu.show();
                }

                menu._parentMenu = parent;

                menu.addClass('menu-sub');

                var rel = menu.testMoveRel(
                    self.getEl(),
                    self.isRtl() ? ['tl-tr', 'bl-br', 'tr-tl', 'br-bl'] : ['tr-tl', 'br-bl', 'tl-tr', 'bl-br']
                );

                menu.moveRel(self.getEl(), rel);
                menu.rel = rel;

                rel = 'menu-sub-' + rel;
                menu.removeClass(menu._lastRel);
                menu.addClass(rel);
                menu._lastRel = rel;

                self.addClass('selected');
                self.aria('expanded', true);
            }
        },

        /**
         * Hides the menu for the menu item.
         *
         * @method hideMenu
         */
        hideMenu: function() {
            var self = this;

            if (self.menu) {
                self.menu.items().each(function(item) {
                    if (item.hideMenu) {
                        item.hideMenu();
                    }
                });

                self.menu.hide();
                self.aria('expanded', false);
            }

            return self;
        },

        /**
         * Renders the control as a HTML string.
         *
         * @method renderHtml
         * @return {String} HTML representing the control.
         */
        renderHtml: function() {
            var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix, text = self.encode(self._text);
            var icon = self.settings.icon, image = '', shortcut = settings.shortcut;

            if (icon) {
                self.parent().addClass('menu-has-icons');
            }

            if (settings.image) {
                icon = 'none';
                image = ' style="background-image: url(\'' + settings.image + '\')"';
            }

            if (shortcut && Env.mac) {
                // format shortcut for Mac
                shortcut = shortcut.replace(/ctrl\+alt\+/i, '&#x2325;&#x2318;'); // ctrl+cmd
                shortcut = shortcut.replace(/ctrl\+/i, '&#x2318;'); // ctrl symbol
                shortcut = shortcut.replace(/alt\+/i, '&#x2325;'); // cmd symbol
                shortcut = shortcut.replace(/shift\+/i, '&#x21E7;'); // shift symbol
            }

            icon = prefix + 'ico ' + prefix + 'i-' + (self.settings.icon || 'none');

            return (
                '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' +
                    (text !== '-' ? '<i class="' + icon + '"' + image + '></i>\u00a0' : '') +
                    (text !== '-' ? '<span id="' + id + '-text" class="' + prefix + 'text">' + text + '</span>' : '') +
                    (shortcut ? '<div id="' + id + '-shortcut" class="' + prefix + 'menu-shortcut">' + shortcut + '</div>' : '') +
                    (settings.menu ? '<div class="' + prefix + 'caret"></div>' : '') +
                '</div>'
            );
        },

        /**
         * Gets invoked after the control has been rendered.
         *
         * @method postRender
         */
        postRender: function() {
            var self = this, settings = self.settings;

            var textStyle = settings.textStyle;
            if (typeof(textStyle) == "function") {
                textStyle = textStyle.call(this);
            }

            if (textStyle) {
                var textElm = self.getEl('text');
                if (textElm) {
                    textElm.setAttribute('style', textStyle);
                }
            }

            self.on('mouseenter click', function(e) {
                if (e.control === self) {
                    if (!settings.menu && e.type === 'click') {
                        self.fire('select');
                        self.parent().hideAll();
                    } else {
                        self.showMenu();

                        if (e.aria) {
                            self.menu.focus(true);
                        }
                    }
                }
            });

            self._super();

            return self;
        },

        active: function(state) {
            if (typeof(state) != "undefined") {
                this.aria('checked', state);
            }

            return this._super(state);
        },

        /**
         * Removes the control and it's menus.
         *
         * @method remove
         */
        remove: function() {
            this._super();

            if (this.menu) {
                this.menu.remove();
            }
        }
    });
});

// Included from: js/tinymce/classes/ui/Menu.js

/**
 * Menu.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Creates a new menu.
 *
 * @-x-less Menu.less
 * @class tinymce.ui.Menu
 * @extends tinymce.ui.FloatPanel
 */
define("tinymce/ui/Menu", [
    "tinymce/ui/FloatPanel",
    "tinymce/ui/MenuItem",
    "tinymce/util/Tools"
], function(FloatPanel, MenuItem, Tools) {
    "use strict";

    var Menu = FloatPanel.extend({
        Defaults: {
            defaultType: 'menuitem',
            border: 1,
            layout: 'stack',
            role: 'application',
            bodyRole: 'menu',
            ariaRoot: true
        },

        /**
         * Constructs a instance with the specified settings.
         *
         * @constructor
         * @param {Object} settings Name/value object with settings.
         */
        init: function(settings) {
            var self = this;

            settings.autohide = true;
            settings.constrainToViewport = true;

            if (settings.itemDefaults) {
                var items = settings.items, i = items.length;

                while (i--) {
                    items[i] = Tools.extend({}, settings.itemDefaults, items[i]);
                }
            }

            self._super(settings);
            self.addClass('menu');
        },

        /**
         * Repaints the control after a layout operation.
         *
         * @method repaint
         */
        repaint: function() {
            this.toggleClass('menu-align', true);

            this._super();

            this.getEl().style.height = '';
            this.getEl('body').style.height = '';

            return this;
        },

        /**
         * Hides/closes the menu.
         *
         * @method cancel
         */
        cancel: function() {
            var self = this;

            self.hideAll();
            self.fire('select');
        },

        /**
         * Hide menu and all sub menus.
         *
         * @method hideAll
         */
        hideAll: function() {
            var self = this;

            this.find('menuitem').exec('hideMenu');

            return self._super();
        },
/*
        getContainerElm: function() {
            var doc = document, id = this.classPrefix + 'menucontainer';

            var elm = doc.getElementById(id);
            if (!elm) {
                elm = doc.createElement('div');
                elm.id = id;
                elm.setAttribute('role', 'application');
                elm.className = this.classPrefix + '-reset';
                elm.style.position = 'absolute';
                elm.style.top = elm.style.left = '0';
                elm.style.overflow = 'visible';
                doc.body.appendChild(elm);
            }

            return elm;
        },
*/
        /**
         * Invoked before the menu is rendered.
         *
         * @method preRender
         */
        preRender: function() {
            var self = this;

            self.items().each(function(ctrl) {
                var settings = ctrl.settings;

                if (settings.icon || settings.selectable) {
                    self._hasIcons = true;
                    return false;
                }
            });

            return self._super();
        }
    });

    return Menu;
});

// Included from: js/tinymce/classes/ui/Radio.js

/**
 * Radio.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Creates a new radio button.
 *
 * @-x-less Radio.less
 * @class tinymce.ui.Radio
 * @extends tinymce.ui.Checkbox
 */
define("tinymce/ui/Radio", [
    "tinymce/ui/Checkbox"
], function(Checkbox) {
    "use strict";

    return Checkbox.extend({
        Defaults: {
            classes: "radio",
            role: "radio"
        }
    });
});

// Included from: js/tinymce/classes/ui/ResizeHandle.js

/**
 * ResizeHandle.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Renders a resize handle that fires ResizeStart, Resize and ResizeEnd events.
 *
 * @-x-less ResizeHandle.less
 * @class tinymce.ui.ResizeHandle
 * @extends tinymce.ui.Widget
 */
define("tinymce/ui/ResizeHandle", [
    "tinymce/ui/Widget",
    "tinymce/ui/DragHelper"
], function(Widget, DragHelper) {
    "use strict";

    return Widget.extend({
        /**
         * Renders the control as a HTML string.
         *
         * @method renderHtml
         * @return {String} HTML representing the control.
         */
        renderHtml: function() {
            var self = this, prefix = self.classPrefix;

            self.addClass('resizehandle');

            if (self.settings.direction == "both") {
                self.addClass('resizehandle-both');
            }

            self.canFocus = false;

            return (
                '<div id="' + self._id + '" class="' + self.classes() + '">' +
                    '<i class="' + prefix + 'ico ' + prefix + 'i-resize"></i>' +
                '</div>'
            );
        },

        /**
         * Called after the control has been rendered.
         *
         * @method postRender
         */
        postRender: function() {
            var self = this;

            self._super();

            self.resizeDragHelper = new DragHelper(this._id, {
                start: function() {
                    self.fire('ResizeStart');
                },

                drag: function(e) {
                    if (self.settings.direction != "both") {
                        e.deltaX = 0;
                    }

                    self.fire('Resize', e);
                },

                stop: function() {
                    self.fire('ResizeEnd');
                }
            });
        },

        remove: function() {
            if (this.resizeDragHelper) {
                this.resizeDragHelper.destroy();
            }

            return this._super();
        }
    });
});

// Included from: js/tinymce/classes/ui/Spacer.js

/**
 * Spacer.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Creates a spacer. This control is used in flex layouts for example.
 *
 * @-x-less Spacer.less
 * @class tinymce.ui.Spacer
 * @extends tinymce.ui.Widget
 */
define("tinymce/ui/Spacer", [
    "tinymce/ui/Widget"
], function(Widget) {
    "use strict";

    return Widget.extend({
        /**
         * Renders the control as a HTML string.
         *
         * @method renderHtml
         * @return {String} HTML representing the control.
         */
        renderHtml: function() {
            var self = this;

            self.addClass('spacer');
            self.canFocus = false;

            return '<div id="' + self._id + '" class="' + self.classes() + '"></div>';
        }
    });
});

// Included from: js/tinymce/classes/ui/SplitButton.js

/**
 * SplitButton.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Creates a split button.
 *
 * @-x-less SplitButton.less
 * @class tinymce.ui.SplitButton
 * @extends tinymce.ui.MenuButton
 */
define("tinymce/ui/SplitButton", [
    "tinymce/ui/MenuButton",
    "tinymce/ui/DomUtils"
], function(MenuButton, DomUtils) {
    return MenuButton.extend({
        Defaults: {
            classes: "widget btn splitbtn",
            role: "button"
        },

        /**
         * Repaints the control after a layout operation.
         *
         * @method repaint
         */
        repaint: function() {
            var self = this, elm = self.getEl(), rect = self.layoutRect(), mainButtonElm, menuButtonElm;

            self._super();

            mainButtonElm = elm.firstChild;
            menuButtonElm = elm.lastChild;

            DomUtils.css(mainButtonElm, {
                width: rect.w - DomUtils.getSize(menuButtonElm).width,
                height: rect.h - 2
            });

            DomUtils.css(menuButtonElm, {
                height: rect.h - 2
            });

            return self;
        },

        /**
         * Sets the active menu state.
         *
         * @private
         */
        activeMenu: function(state) {
            var self = this;

            DomUtils.toggleClass(self.getEl().lastChild, self.classPrefix + 'active', state);
        },

        /**
         * Renders the control as a HTML string.
         *
         * @method renderHtml
         * @return {String} HTML representing the control.
         */
        renderHtml: function() {
            var self = this, id = self._id, prefix = self.classPrefix;
            var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';

            return (
                '<div id="' + id + '" class="' + self.classes() + '" role="button" tabindex="-1">' +
                    '<button type="button" hidefocus="1" tabindex="-1">' +
                        (icon ? '<i class="' + icon + '"></i>' : '') +
                        (self._text ? (icon ? ' ' : '') + self._text : '') +
                    '</button>' +
                    '<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' +
                        //(icon ? '<i class="' + icon + '"></i>' : '') +
                        (self._menuBtnText ? (icon ? '\u00a0' : '') + self._menuBtnText : '') +
                        ' <i class="' + prefix + 'caret"></i>' +
                    '</button>' +
                '</div>'
            );
        },

        /**
         * Called after the control has been rendered.
         *
         * @method postRender
         */
        postRender: function() {
            var self = this, onClickHandler = self.settings.onclick;

            self.on('click', function(e) {
                var node = e.target;

                if (e.control == this) {
                    // Find clicks that is on the main button
                    while (node) {
                        if ((e.aria && e.aria.key != 'down') || (node.nodeName == 'BUTTON' && node.className.indexOf('open') == -1)) {
                            e.stopImmediatePropagation();
                            onClickHandler.call(this, e);
                            return;
                        }

                        node = node.parentNode;
                    }
                }
            });

            delete self.settings.onclick;

            return self._super();
        }
    });
});

// Included from: js/tinymce/classes/ui/StackLayout.js

/**
 * StackLayout.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This layout uses the browsers layout when the items are blocks.
 *
 * @-x-less StackLayout.less
 * @class tinymce.ui.StackLayout
 * @extends tinymce.ui.FlowLayout
 */
define("tinymce/ui/StackLayout", [
    "tinymce/ui/FlowLayout"
], function(FlowLayout) {
    "use strict";

    return FlowLayout.extend({
        Defaults: {
            containerClass: 'stack-layout',
            controlClass: 'stack-layout-item',
            endClass : 'break'
        }
    });
});

// Included from: js/tinymce/classes/ui/TabPanel.js

/**
 * TabPanel.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Creates a tab panel control.
 *
 * @-x-less TabPanel.less
 * @class tinymce.ui.TabPanel
 * @extends tinymce.ui.Panel
 *
 * @setting {Number} activeTab Active tab index.
 */
define("tinymce/ui/TabPanel", [
    "tinymce/ui/Panel",
    "tinymce/ui/DomUtils"
], function(Panel, DomUtils) {
    "use strict";

    return Panel.extend({
        Defaults: {
            layout: 'absolute',
            defaults: {
                type: 'panel'
            }
        },

        /**
         * Activates the specified tab by index.
         *
         * @method activateTab
         * @param {Number} idx Index of the tab to activate.
         */
        activateTab: function(idx) {
            var activeTabElm;

            if (this.activeTabId) {
                activeTabElm = this.getEl(this.activeTabId);
                DomUtils.removeClass(activeTabElm, this.classPrefix + 'active');
                activeTabElm.setAttribute('aria-selected', "false");
            }

            this.activeTabId = 't' + idx;

            activeTabElm = this.getEl('t' + idx);
            activeTabElm.setAttribute('aria-selected', "true");
            DomUtils.addClass(activeTabElm, this.classPrefix + 'active');

            this.items()[idx].show().fire('showtab');
            this.reflow();

            this.items().each(function(item, i) {
                if (idx != i) {
                    item.hide();
                }
            });
        },

        /**
         * Renders the control as a HTML string.
         *
         * @method renderHtml
         * @return {String} HTML representing the control.
         */
        renderHtml: function() {
            var self = this, layout = self._layout, tabsHtml = '', prefix = self.classPrefix;

            self.preRender();
            layout.preRender(self);

            self.items().each(function(ctrl, i) {
                var id = self._id + '-t' + i;

                ctrl.aria('role', 'tabpanel');
                ctrl.aria('labelledby', id);

                tabsHtml += (
                    '<div id="' + id + '" class="' + prefix + 'tab" ' +
                        'unselectable="on" role="tab" aria-controls="' + ctrl._id + '" aria-selected="false" tabIndex="-1">' +
                        self.encode(ctrl.settings.title) +
                    '</div>'
                );
            });

            return (
                '<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
                    '<div id="' + self._id + '-head" class="' + prefix + 'tabs" role="tablist">' +
                        tabsHtml +
                    '</div>' +
                    '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
                        layout.renderHtml(self) +
                    '</div>' +
                '</div>'
            );
        },

        /**
         * Called after the control has been rendered.
         *
         * @method postRender
         */
        postRender: function() {
            var self = this;

            self._super();

            self.settings.activeTab = self.settings.activeTab || 0;
            self.activateTab(self.settings.activeTab);

            this.on('click', function(e) {
                var targetParent = e.target.parentNode;

                if (e.target.parentNode.id == self._id + '-head') {
                    var i = targetParent.childNodes.length;

                    while (i--) {
                        if (targetParent.childNodes[i] == e.target) {
                            self.activateTab(i);
                        }
                    }
                }
            });
        },

        /**
         * Initializes the current controls layout rect.
         * This will be executed by the layout managers to determine the
         * default minWidth/minHeight etc.
         *
         * @method initLayoutRect
         * @return {Object} Layout rect instance.
         */
        initLayoutRect: function() {
            var self = this, rect, minW, minH;

            minW = DomUtils.getSize(self.getEl('head')).width;
            minW = minW < 0 ? 0 : minW;
            minH = 0;

            self.items().each(function(item) {
                minW = Math.max(minW, item.layoutRect().minW);
                minH = Math.max(minH, item.layoutRect().minH);
            });

            self.items().each(function(ctrl) {
                ctrl.settings.x = 0;
                ctrl.settings.y = 0;
                ctrl.settings.w = minW;
                ctrl.settings.h = minH;

                ctrl.layoutRect({
                    x: 0,
                    y: 0,
                    w: minW,
                    h: minH
                });
            });

            var headH = DomUtils.getSize(self.getEl('head')).height;

            self.settings.minWidth = minW;
            self.settings.minHeight = minH + headH;

            rect = self._super();
            rect.deltaH += headH;
            rect.innerH = rect.h - rect.deltaH;

            return rect;
        }
    });
});

// Included from: js/tinymce/classes/ui/TextBox.js

/**
 * TextBox.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * Creates a new textbox.
 *
 * @-x-less TextBox.less
 * @class tinymce.ui.TextBox
 * @extends tinymce.ui.Widget
 */
define("tinymce/ui/TextBox", [
    "tinymce/ui/Widget",
    "tinymce/ui/DomUtils"
], function(Widget, DomUtils) {
    "use strict";

    return Widget.extend({
        /**
         * Constructs a instance with the specified settings.
         *
         * @constructor
         * @param {Object} settings Name/value object with settings.
         * @setting {Boolean} multiline True if the textbox is a multiline control.
         * @setting {Number} maxLength Max length for the textbox.
         * @setting {Number} size Size of the textbox in characters.
         */
        init: function(settings) {
            var self = this;

            self._super(settings);

            self._value = settings.value || '';
            self.addClass('textbox');

            if (settings.multiline) {
                self.addClass('multiline');
            } else {
                // TODO: Rework this
                self.on('keydown', function(e) {
                    if (e.keyCode == 13) {
                        self.parents().reverse().each(function(ctrl) {
                            e.preventDefault();

                            if (ctrl.hasEventListeners('submit') && ctrl.toJSON) {
                                ctrl.fire('submit', {data: ctrl.toJSON()});
                                return false;
                            }
                        });
                    }
                });
            }
        },

        /**
         * Getter/setter function for the disabled state.
         *
         * @method value
         * @param {Boolean} [state] State to be set.
         * @return {Boolean|tinymce.ui.ComboBox} True/false or self if it's a set operation.
         */
        disabled: function(state) {
            var self = this;

            if (self._rendered && typeof(state) != 'undefined') {
                self.getEl().disabled = state;
            }

            return self._super(state);
        },

        /**
         * Getter/setter function for the control value.
         *
         * @method value
         * @param {String} [value] Value to be set.
         * @return {String|tinymce.ui.ComboBox} Value or self if it's a set operation.
         */
        value: function(value) {
            var self = this;

            if (typeof(value) != "undefined") {
                self._value = value;

                if (self._rendered) {
                    self.getEl().value = value;
                }

                return self;
            }

            if (self._rendered) {
                return self.getEl().value;
            }

            return self._value;
        },

        /**
         * Repaints the control after a layout operation.
         *
         * @method repaint
         */
        repaint: function() {
            var self = this, style, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect;

            style = self.getEl().style;
            rect = self._layoutRect;
            lastRepaintRect = self._lastRepaintRect || {};

            // Detect old IE 7+8 add lineHeight to align caret vertically in the middle
            var doc = document;
            if (!self.settings.multiline && doc.all && (!doc.documentMode || doc.documentMode <= 8)) {
                style.lineHeight = (rect.h - borderH) + 'px';
            }

            borderBox = self._borderBox;
            borderW = borderBox.left + borderBox.right + 8;
            borderH = borderBox.top + borderBox.bottom + (self.settings.multiline ? 8 : 0);

            if (rect.x !== lastRepaintRect.x) {
                style.left = rect.x + 'px';
                lastRepaintRect.x = rect.x;
            }

            if (rect.y !== lastRepaintRect.y) {
                style.top = rect.y + 'px';
                lastRepaintRect.y = rect.y;
            }

            if (rect.w !== lastRepaintRect.w) {
                style.width = (rect.w - borderW) + 'px';
                lastRepaintRect.w = rect.w;
            }

            if (rect.h !== lastRepaintRect.h) {
                style.height = (rect.h - borderH) + 'px';
                lastRepaintRect.h = rect.h;
            }

            self._lastRepaintRect = lastRepaintRect;
            self.fire('repaint', {}, false);

            return self;
        },

        /**
         * Renders the control as a HTML string.
         *
         * @method renderHtml
         * @return {String} HTML representing the control.
         */
        renderHtml: function() {
            var self = this, id = self._id, settings = self.settings, value = self.encode(self._value, false), extraAttrs = '';

            if ("spellcheck" in settings) {
                extraAttrs += ' spellcheck="' + settings.spellcheck + '"';
            }

            if (settings.maxLength) {
                extraAttrs += ' maxlength="' + settings.maxLength + '"';
            }

            if (settings.size) {
                extraAttrs += ' size="' + settings.size + '"';
            }

            if (settings.subtype) {
                extraAttrs += ' type="' + settings.subtype + '"';
            }

            if (self.disabled()) {
                extraAttrs += ' disabled="disabled"';
            }

            if (settings.multiline) {
                return (
                    '<textarea id="' + id + '" class="' + self.classes() + '" ' +
                    (settings.rows ? ' rows="' + settings.rows + '"' : '') +
                    ' hidefocus="1"' + extraAttrs + '>' + value +
                    '</textarea>'
                );
            }

            return '<input id="' + id + '" class="' + self.classes() + '" value="' + value + '" hidefocus="1"' + extraAttrs + ' />';
        },

        /**
         * Called after the control has been rendered.
         *
         * @method postRender
         */
        postRender: function() {
            var self = this;

            DomUtils.on(self.getEl(), 'change', function(e) {
                self.fire('change', e);
            });

            return self._super();
        },

        remove: function() {
            DomUtils.off(this.getEl());
            this._super();
        }
    });
});

// Included from: js/tinymce/classes/ui/Throbber.js

/**
 * Throbber.js
 *
 * Copyright, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class enables you to display a Throbber for any element.
 *
 * @-x-less Throbber.less
 * @class tinymce.ui.Throbber
 */
define("tinymce/ui/Throbber", [
    "tinymce/ui/DomUtils",
    "tinymce/ui/Control"
], function(DomUtils, Control) {
    "use strict";

    /**
     * Constructs a new throbber.
     *
     * @constructor
     * @param {Element} elm DOM Html element to display throbber in.
     * @param {Boolean} inline Optional true/false state if the throbber should be appended to end of element for infinite scroll.
     */
    return function(elm, inline) {
        var self = this, state, classPrefix = Control.classPrefix;

        /**
         * Shows the throbber.
         *
         * @method show
         * @param {Number} [time] Time to wait before showing.
         * @return {tinymce.ui.Throbber} Current throbber instance.
         */
        self.show = function(time) {
            self.hide();

            state = true;

            window.setTimeout(function() {
                if (state) {
                    elm.appendChild(DomUtils.createFragment(
                        '<div class="' + classPrefix + 'throbber' + (inline ? ' ' + classPrefix + 'throbber-inline' : '') + '"></div>'
                    ));
                }
            }, time || 0);

            return self;
        };

        /**
         * Hides the throbber.
         *
         * @method hide
         * @return {tinymce.ui.Throbber} Current throbber instance.
         */
        self.hide = function() {
            var child = elm.lastChild;

            if (child && child.className.indexOf('throbber') != -1) {
                child.parentNode.removeChild(child);
            }

            state = false;

            return self;
        };
    };
});

expose(["tinymce/dom/EventUtils","tinymce/dom/Sizzle","tinymce/util/Tools","tinymce/Env","tinymce/dom/DomQuery","tinymce/html/Styles","tinymce/dom/TreeWalker","tinymce/dom/Range","tinymce/html/Entities","tinymce/dom/DOMUtils","tinymce/dom/ScriptLoader","tinymce/AddOnManager","tinymce/html/Node","tinymce/html/Schema","tinymce/html/SaxParser","tinymce/html/DomParser","tinymce/html/Writer","tinymce/html/Serializer","tinymce/dom/Serializer","tinymce/dom/TridentSelection","tinymce/util/VK","tinymce/dom/ControlSelection","tinymce/dom/BookmarkManager","tinymce/dom/Selection","tinymce/dom/ElementUtils","tinymce/Formatter","tinymce/UndoManager","tinymce/EnterKey","tinymce/ForceBlocks","tinymce/EditorCommands","tinymce/util/URI","tinymce/util/Class","tinymce/util/EventDispatcher","tinymce/ui/Selector","tinymce/ui/Collection","tinymce/ui/DomUtils","tinymce/ui/Control","tinymce/ui/Factory","tinymce/ui/KeyboardNavigation","tinymce/ui/Container","tinymce/ui/DragHelper","tinymce/ui/Scrollable","tinymce/ui/Panel","tinymce/ui/Movable","tinymce/ui/Resizable","tinymce/ui/FloatPanel","tinymce/ui/Window","tinymce/ui/MessageBox","tinymce/WindowManager","tinymce/util/Quirks","tinymce/util/Observable","tinymce/EditorObservable","tinymce/Shortcuts","tinymce/Editor","tinymce/util/I18n","tinymce/FocusManager","tinymce/EditorManager","tinymce/LegacyInput","tinymce/util/XHR","tinymce/util/JSON","tinymce/util/JSONRequest","tinymce/util/JSONP","tinymce/util/LocalStorage","tinymce/Compat","tinymce/ui/Layout","tinymce/ui/AbsoluteLayout","tinymce/ui/Tooltip","tinymce/ui/Widget","tinymce/ui/Button","tinymce/ui/ButtonGroup","tinymce/ui/Checkbox","tinymce/ui/ComboBox","tinymce/ui/ColorBox","tinymce/ui/PanelButton","tinymce/ui/ColorButton","tinymce/util/Color","tinymce/ui/ColorPicker","tinymce/ui/Path","tinymce/ui/ElementPath","tinymce/ui/FormItem","tinymce/ui/Form","tinymce/ui/FieldSet","tinymce/ui/FilePicker","tinymce/ui/FitLayout","tinymce/ui/FlexLayout","tinymce/ui/FlowLayout","tinymce/ui/FormatControls","tinymce/ui/GridLayout","tinymce/ui/Iframe","tinymce/ui/Label","tinymce/ui/Toolbar","tinymce/ui/MenuBar","tinymce/ui/MenuButton","tinymce/ui/ListBox","tinymce/ui/MenuItem","tinymce/ui/Menu","tinymce/ui/Radio","tinymce/ui/ResizeHandle","tinymce/ui/Spacer","tinymce/ui/SplitButton","tinymce/ui/StackLayout","tinymce/ui/TabPanel","tinymce/ui/TextBox","tinymce/ui/Throbber"]);
})(this);