# Source: core.js

    var _wrs_popupWindow;

    wrs_addEvent(window, 'message', function (e) {
        if (e.source = _wrs_popupWindow && typeof e.wrs_processed == 'undefined' && typeof e.data.isWirisMessage != 'undefined') {
            e.wrs_processed = true;
            var postVariable = {};
            postVariable.id = e.data.id;
            if (e.data.hasOwnProperty('methodName')) {
                var object = (e.data.objectName == null) ? this : window[e.data.objectName];
                postVariable.value = object[e.data.methodName].apply(object, e.data.arguments);
            } else {
                var postVariables = {};
                e.data.varNames.forEach(function(varName){
                    postVariables[varName] = window[varName];
                });
                postVariable.value = postVariables;
            }
            if (typeof(e.source) != 'undefined') { // Avoid sent message when popupWindows has been closed.
                e.source.postMessage(postVariable, _wrs_conf_path);
            }
        }
    });

    /**
     * Fires an element event.
     * @param {object} element element where event should be fired.
     * @param {string} event event to fire.
     * @ignore
     */
    function wrs_fireEvent(element, event) {
        if (document.createEvent){
            var eventObject = document.createEvent('HTMLEvents');
            eventObject.initEvent(event, true, true);
            return !element.dispatchEvent(eventObject);
        }

        var eventObject = document.createEventObject();
        return element.fireEvent('on' + event, eventObject)
    }

    wrs_addEvent(window, 'mouseup', function (e) {

        if (typeof(_wrs_modalWindow) !== 'undefined' && _wrs_modalWindow != null) {
            if (_wrs_modalWindow.properties.state != "maximized") {
                _wrs_modalWindow.overlayDiv.style.display = 'none';
            }
            _wrs_modalWindow.fireEditorEvent('mouseup');
        }
    });

    // Vars.
    var _wrs_currentPath = window.location.toString().substr(0, window.location.toString().lastIndexOf('/') + 1);
    var _wrs_editMode = typeof _wrs_editMode != 'undefined' ? _wrs_editMode : undefined;
    var _wrs_isNewElement = typeof _wrs_isNewElement != 'undefined' ? _wrs_isNewElement : true;
    var _wrs_temporalImage;
    var _wrs_temporalFocusElement;
    var _wrs_range;

    // LaTex client cache.
    var _wrs_int_LatexCache = {};

    // Accessible client cache.
    var _wrs_int_AccessibleCache = {};

    var _wrs_xmlCharacters = {
        'tagOpener': '<',       // Hex: \x3C.
        'tagCloser': '>',       // Hex: \x3E.
        'doubleQuote': '"',     // Hex: \x22.
        'ampersand': '&',       // Hex: \x26.
        'quote': '\''           // Hex: \x27.
    };

    var _wrs_safeXmlCharacters = {
        'tagOpener': '«',       // Hex: \xAB.
        'tagCloser': '»',       // Hex: \xBB.
        'doubleQuote': '¨',     // Hex: \xA8.
        'ampersand': '§',       // Hex: \xA7.
        'quote': '`',           // Hex: \x60.
        'realDoubleQuote': '¨'
    };

    var _wrs_safeXmlCharactersEntities = {
        'tagOpener': '&laquo;',
        'tagCloser': '&raquo;',
        'doubleQuote': '&uml;',
        'realDoubleQuote': '&quot;'
    }

    var _wrs_safeBadBlackboardCharacters = {
        'ltElement': '«mo»<«/mo»',
        'gtElement': '«mo»>«/mo»',
        'ampElement': '«mo»&«/mo»'
    }

    var _wrs_safeGoodBlackboardCharacters = {
        'ltElement': '«mo»§lt;«/mo»',
        'gtElement': '«mo»§gt;«/mo»',
        'ampElement': '«mo»§amp;«/mo»'
    }

    var _wrs_staticNodeLengths = {
        'IMG': 1,
        'BR': 1
    }

    // Translated languages.
    var _wrs_languages = 'ar,ca,cs,da,de,en,es,et,eu,fi,fr,gl,he,hr,hu,it,ja,ko,nl,no,pl,pt,pt_br,ru,sv,tr,zh,el';

    // Backwards compatibily.

    if (!(window._wrs_conf_imageClassName)) {
        _wrs_conf_imageClassName = 'Wirisformula';
    }

    if (!(window._wrs_conf_CASClassName)) {
        _wrs_conf_CASClassName = 'Wiriscas';
    }

    // Mutation observers to avoid wiris image formulas class be removed.
    if (typeof MutationObserver != 'undefined') {
        var wrs_observer = new MutationObserver(function(mutations) {
            mutations.forEach(function(mutation) {
                if (mutation.oldValue == _wrs_conf_imageClassName && mutation.attributeName == 'class' && mutation.target.className.indexOf(_wrs_conf_imageClassName) == -1 ) {
                    mutation.target.className = _wrs_conf_imageClassName;
                }
            });
        });

        var wrs_observer_config = { attributes: true, attributeOldValue:true };
    }

    // Plugin listeners for custom callbacks. This variable
    // can be setted by the user using wrs_addPluginListener.
    var wrs_pluginListeners = [];

    var _wrs_css_loaded = false;

    var _wrs_modalWindowProperties = typeof _wrs_modalWindowProperties != 'undefined' ? _wrs_modalWindowProperties : {};
    var _wrs_editor = typeof _wrs_editor != 'undefined' ? _wrs_editor : null;
    var _wrs_modalWindow = typeof _wrs_modalWindow != 'undefined' ? _wrs_modalWindow : null;

    /**
     * Adds element events.
     * @param {object} target Target
     * @param {function} doubleClickHandler Function to run when user double clicks the element
     * @param {function} mousedownHandler Function to run when user mousedowns the element
     * @param {function} mouseupHandler Function to run when user mouseups the element
     * @ignore
     */
    function wrs_addElementEvents(target, doubleClickHandler, mousedownHandler, mouseupHandler) {
        if (doubleClickHandler) {
            wrs_addEvent(target, 'dblclick', function (event) {
                var realEvent = (event) ? event : window.event;
                var element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;
                doubleClickHandler(target, element, realEvent);
            });
        }

        if (mousedownHandler) {
            wrs_addEvent(target, 'mousedown', function (event) {
                var realEvent = (event) ? event : window.event;
                var element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;
                _wrs_temporalFocusElement = element;
                mousedownHandler(target, element, realEvent);
            });
        }

        if (mouseupHandler) {
            wrs_addEvent(target, 'mouseup', function (event) {
                var realEvent = (event) ? event : window.event;
                var element = realEvent.srcElement ? realEvent.srcElement : realEvent.target;
                mouseupHandler(target, element, realEvent);
            });
        }
    }

    /**
     * Cross-browser addEventListener/attachEvent function.
     * @param {object} element Element target
     * @param {event} event Event
     * @param {function} func Function to run
     * @ignore
     */
    function wrs_addEvent(element, event, func) {
        if (element.addEventListener) {
            element.addEventListener(event, func, true);
        }
        else if (element.attachEvent) {
            element.attachEvent('on' + event, func);
        }
    }

    /**
     * Adds iframe events.
     * @param {object} iframe Target
     * @param {function} doubleClickHandler Function to run when user double clicks the iframe
     * @param {function} mousedownHandler Function to run when user mousedowns the iframe
     * @param {function} mouseupHandler Function to run when user mouseups the iframe
     * @ignore
     */
    function wrs_addIframeEvents(iframe, doubleClickHandler, mousedownHandler, mouseupHandler) {
        wrs_initSetSize();
        wrs_addElementEvents(iframe.contentWindow.document,
            function (target, element, event) {
                doubleClickHandler(iframe, element, event);
            },
            function (target, element, event) {
                mousedownHandler(iframe, element, event);
            },
            function (target, element, event) {
                mouseupHandler(iframe, element, event);
            }
        );
    }

    /**
     * Adds textarea events.
     * @param {object} textarea Target
     * @param {function} clickHandler Function to run when user clicks the textarea.
     * @ignore
     */
    function wrs_addTextareaEvents(textarea, clickHandler) {
        if (clickHandler) {
            wrs_addEvent(textarea, 'click', function (event) {
                var realEvent = (event) ? event : window.event;
                clickHandler(textarea, realEvent);
            });
        }
    }

    /**
     * Converts applet code to img object.
     * @param {object} creator Object with "createElement" method
     * @param {string} appletCode Applet code
     * @param {string} image Base 64 image stream
     * @param {int} imageWidth Image width
     * @param {int} imageHeight Image height
     * @return object img object.
     * @ignore
     */
    function wrs_appletCodeToImgObject(creator, appletCode, image, imageWidth, imageHeight) {
        var imageSrc = wrs_createImageCASSrc(image);
        var imgObject = creator.createElement('img');

        imgObject.src = imageSrc;
        imgObject.align = 'middle';
        imgObject.width = imageWidth;
        imgObject.height = imageHeight;
        imgObject.setAttribute(_wrs_conf_CASMathmlAttribute, wrs_mathmlEncode(appletCode));
        imgObject.className = _wrs_conf_CASClassName;

        return imgObject;
    }

    /**
     * Checks if a determined array contains a determined element.
     * @param {array} stack
     * @param {object} element
     * @return bool
     * @ignore
     */
    function wrs_arrayContains(stack, element) {
        for (var i = stack.length - 1; i >= 0; --i) {
            if (stack[i] === element) {
                return i;
            }
        }

        return -1;
    }

    /**
     * Adds a specific className to given element
     * @param  {object} element
     * @param  {string} className
     * @ignore
     */
    function wrs_addClass(element, className) {
        if (!wrs_containsClass(element, className)) {
            element.className += " " + className;
        }
    }

    /**
     * Checks if an element contains a class.
     * @param {object} element
     * @param {string} className
     * @return bool
     * @ignore
     */
    function wrs_containsClass(element, className) {
        if (element == null || !('className' in element)) {
            return false;
        }

        var currentClasses = element.className.split(' ');

        for (var i = currentClasses.length - 1; i >= 0; --i) {
            if (currentClasses[i] == className) {
                return true;
            }
        }

        return false;
    }

    /**
     * Remove a specific class
     * @param {object} element
     * @param {string} className
     * @ignore
     */
    function wrs_removeClass(element, className) {
        var newClassName = '';
        var classes = element.className.split(" ");

        for (var i = 0; i < classes.length; i++) {
            if(classes[i] != className) {
                newClassName += classes[i] + " ";
            }
        }
        element.className = newClassName.trim();
    }

    /**
     * Converts old xmlinitialtext attribute (with «») to the correct one(with §lt;§gt;)
     * @param {string} text String containtg safeXml characters
     * @return {string} String with the safeXml charaters parsed.
     * @ignore
     */
    function wrs_convertOldXmlinitialtextAttribute(text){
        // Used to fix a bug with Cas imported from Moodle 1.9 to Moodle 2.x.
        // This could be removed in future.
        var val = 'value=';

        var xitpos = text.indexOf('xmlinitialtext');
        var valpos = text.indexOf(val, xitpos);
        var quote = text.charAt(valpos + val.length);
        var startquote = valpos + val.length + 1;
        var endquote = text.indexOf(quote, startquote);

        var value = text.substring(startquote, endquote);

        var newvalue = value.split('«').join('§lt;');
        newvalue = newvalue.split('»').join('§gt;');
        newvalue = newvalue.split('&').join('§');
        newvalue = newvalue.split('¨').join('§quot;');

        text = text.split(value).join(newvalue);
        return text;
    }

    /**
     * Cross-browser solution for creating new elements.
     *
     * It fixes some browser bugs.
     *
     * @param {string} elementName The tag name of the wished element.
     * @param {object} attributes An object where each key is a wished attribute name and each value is its value.
     * @param {object} creator Optional param. If supplied, this function will use the "createElement" method from this param. Else, "document" will be used.
     * @return {object} The DOM element with the specified attributes assignated.
     * @ignore
     */
    function wrs_createElement(elementName, attributes, creator) {
        if (attributes === undefined) {
            attributes = {};
        }

        if (creator === undefined) {
            creator = document;
        }

        var element;

        /*
         * Internet Explorer fix:
         * If you create a new object dynamically, you can't set a non-standard attribute.
         * For example, you can't set the "src" attribute on an "applet" object.
         * Other browsers will throw an exception and will run the standard code.
         */

        try {
            var html = '<' + elementName;

            for (var attributeName in attributes) {
                html += ' ' + attributeName + '="' + wrs_htmlentities(attributes[attributeName]) + '"';
            }

            html += '>';
            element = creator.createElement(html);
        }
        catch (e) {
            element = creator.createElement(elementName);

            for (var attributeName in attributes) {
                element.setAttribute(attributeName, attributes[attributeName]);
            }
        }

        return element;
    }

    /**
     * Cross-browser httpRequest creation.
     * @return {object} httpRequest request object.
     * @ignore
     */
    function wrs_createHttpRequest() {
        if (_wrs_currentPath.substr(0, 7) == 'file://') {
            throw 'Cross site scripting is only allowed for HTTP.';
        }

        if (typeof XMLHttpRequest != 'undefined') {
            return new XMLHttpRequest();
        }

        try {
            return new ActiveXObject('Msxml2.XMLHTTP');
        }
        catch (e) {
            try {
                return new ActiveXObject('Microsoft.XMLHTTP');
            }
            catch (oc) {
            }
        }

        return false;
    }

    /**
     * Gets CAS image src with AJAX.
     * @param {string} image Base 64 image stream
     * @return {string} CAS image src.
     * @ignore
     */
    function wrs_createImageCASSrc(image, appletCode) {
        var data = {
            'image': image,
            'mml': appletCode
        };

        return wrs_getContent(_wrs_conf_createcasimagePath, data);
    }

    /**
     * Gets formula image src with AJAX.
     * @param {mathml} Mathml code.
     * @param {object} data wiris properties object.
     * @return string Image src.
     * @ignore
     */
    function wrs_createImageSrc(mathml, data) {
        // Full base64 method (edit & save).
        if (_wrs_conf_saveMode == 'base64' && _wrs_conf_editMode == 'default') {
            data['base64'] = true;
        }

        var result = wrs_getContent(_wrs_conf_createimagePath, data);

        if (result.indexOf('@BASE@') != -1) {
            // Replacing '@BASE@' with the base URL of createimage.
            var baseParts = _wrs_conf_createimagePath.split('/');
            baseParts.pop();
            result = result.split('@BASE@').join(baseParts.join('/'));
        }

        return result;
    }

    function wrs_createShowImageSrc(mathml, data, language) {
        var dataMd5 = []
        var renderParams = 'mml,color,centerbaseline,zoom,dpi,fontSize,fontFamily,defaultStretchy,backgroundColor,format';
        var renderParamsArray = renderParams.split(',');
        for (var key in renderParamsArray) {
            var param = renderParamsArray[key];
            if (typeof data[param] != 'undefined') {
                dataMd5[param] = data[param];
            }
        }
        // Data variables to get.
        var dataObject = {};
        for (var key in data) {
            // We don't need mathml in this request we try to get cached so we only need the formula md5 calculated before.
            if (key != 'mml') {
                dataObject[key] = data[key];
            }
        }
        dataObject.formula = com.wiris.js.JsPluginTools.md5encode(wrs_propertiesToString(dataMd5));
        dataObject.lang = (typeof language == 'undefined') ? 'en' : language;
        dataObject.version = _wrs_conf_version;

        var result = wrs_getContent(_wrs_conf_showimagePath + '?' + wrs_httpBuildQuery(dataObject));
        return result;
    }

    /**
     * Creates new object using its html code.
     * @param {string} objectCode html code
     * @return {object} html object.
     * @ignore
     */
    function wrs_createObject(objectCode, creator) {
        if (creator === undefined) {
            creator = document;
        }

        // Internet Explorer can't include "param" tag when is setting an innerHTML property.
        objectCode = objectCode.split('<applet ').join('<span wirisObject="WirisApplet" ').split('<APPLET ').join('<span wirisObject="WirisApplet" ');  // It is a 'span' because 'span' objects can contain 'br' nodes.
        objectCode = objectCode.split('</applet>').join('</span>').split('</APPLET>').join('</span>');

        objectCode = objectCode.split('<param ').join('<br wirisObject="WirisParam" ').split('<PARAM ').join('<br wirisObject="WirisParam" ');          // It is a 'br' because 'br' can't contain nodes.
        objectCode = objectCode.split('</param>').join('</br>').split('</PARAM>').join('</br>');

        var container = wrs_createElement('div', {}, creator);
        container.innerHTML = objectCode;

        function recursiveParamsFix(object) {
            if (object.getAttribute && object.getAttribute('wirisObject') == 'WirisParam') {
                var attributesParsed = {};

                for (var i = 0; i < object.attributes.length; ++i) {
                    if (object.attributes[i].nodeValue !== null) {
                        attributesParsed[object.attributes[i].nodeName] = object.attributes[i].nodeValue;
                    }
                }

                var param = wrs_createElement('param', attributesParsed, creator);

                // IE fix.
                if (param.NAME) {
                    param.name = param.NAME;
                    param.value = param.VALUE;
                }

                param.removeAttribute('wirisObject');
                object.parentNode.replaceChild(param, object);
            }
            else if (object.getAttribute && object.getAttribute('wirisObject') == 'WirisApplet') {
                var attributesParsed = {};

                for (var i = 0; i < object.attributes.length; ++i) {
                    if (object.attributes[i].nodeValue !== null) {
                        attributesParsed[object.attributes[i].nodeName] = object.attributes[i].nodeValue;
                    }
                }

                var applet = wrs_createElement('applet', attributesParsed, creator);
                applet.removeAttribute('wirisObject');

                for (var i = 0; i < object.childNodes.length; ++i) {
                    recursiveParamsFix(object.childNodes[i]);

                    if (object.childNodes[i].nodeName.toLowerCase() == 'param') {
                        applet.appendChild(object.childNodes[i]);
                        --i;    // When we insert the object child into the applet, object loses one child.
                    }
                }

                object.parentNode.replaceChild(applet, object);
            }
            else {
                for (var i = 0; i < object.childNodes.length; ++i) {
                    recursiveParamsFix(object.childNodes[i]);
                }
            }
        }

        recursiveParamsFix(container);
        return container.firstChild;
    }

    /**
     * Converts an object to its HTML code.
     * @param {object} object DOM object..
     * @return {string} HTML code.
     * @ignore
     */
    function wrs_createObjectCode(object) {

        // In case that the image was not created, the object can be null or undefined.
        if (typeof object == 'undefined' || object == null) {
            return;
        }

        if (object.nodeType == 1) { // ELEMENT_NODE.
            var output = '<' + object.tagName;

            for (var i = 0; i < object.attributes.length; ++i) {
                if (object.attributes[i].specified) {
                    output += ' ' + object.attributes[i].name + '="' + wrs_htmlentities(object.attributes[i].value) + '"';
                }
            }

            if (object.childNodes.length > 0) {
                output += '>';

                for (var i = 0; i < object.childNodes.length; ++i) {
                    output += wrs_createObjectCode(object.childNodes[i]);
                }

                output += '</' + object.tagName + '>';
            }
            else if (object.nodeName == 'DIV' || object.nodeName == 'SCRIPT') {
                output += '></' + object.tagName + '>';
            }
            else {
                output += '/>';
            }

            return output;
        }

        if (object.nodeType == 3) { // TEXT_NODE.
            return wrs_htmlentities(object.nodeValue);
        }

        return '';
    }

    /**
     * Parses end HTML code. The end HTML code is HTML code with embedded images or LaTeX formulas created with the WIRIS editor. <br>
     * By default this method converts the formula images and LaTeX strings in MathML. <br>
     * If image mode is enabled the images will not be converted into MathML. For further information see {@link http://www.wiris.com/plugins/docs/full-mathml-mode}.
     * @param {string} code String to be parsed.
     * @param {object} wirisProperties Extra attributes for the formula.
     * @param {string} language Language for the formula.
     * @return {string}
     */
    function wrs_endParse(code, wirisProperties, language) {
        code = wrs_endParseEditMode(code, wirisProperties, language);
        return wrs_endParseSaveMode(code);
    }

    function wrs_regexpIndexOf(input, regexp, start) {
        var index = input.substring(start || 0).search(regexp);
        return (index >= 0) ? (index + (start || 0)) : index;
    }

    /**
     * Parses end HTML code depending on the edit mode.
     * @param {string} code HTML code to be parsed.
     * @param {object} wirisProperties Extra formula attributes.
     * @param {string} language Language for the formula.
     * @return {string}
     * @ignore
     */
    function wrs_endParseEditMode(code, wirisProperties, language) {
        // Converting LaTeX to images.

        if (window._wrs_conf_parseModes !== undefined && wrs_arrayContains(_wrs_conf_parseModes, 'latex') != -1) {
            var output = '';
            var endPosition = 0;
            var startPosition = code.indexOf('$$');

            while (startPosition != -1) {
                output += code.substring(endPosition, startPosition);
                endPosition = code.indexOf('$$', startPosition + 2);

                if (endPosition != -1) {
                    var latex = code.substring(startPosition + 2, endPosition);

                    if (latex.indexOf('<') == -1) {
                        latex = wrs_htmlentitiesDecode(latex);
                        var mathml = wrs_getMathMLFromLatex(latex, true);
                        output += mathml;
                        endPosition += 2;
                    }
                    else {
                        output += '$$';
                        endPosition = startPosition + 2;
                    }
                }
                else {
                    output += '$$';
                    endPosition = startPosition + 2;
                }

                startPosition = code.indexOf('$$', endPosition);
            }

            output += code.substring(endPosition, code.length);
            code = output;
        }

        if (window._wrs_conf_defaultEditMode && _wrs_conf_defaultEditMode == 'iframes') {
            // Converting iframes to images.
            var output = '';
            var pattern = ' class="' + _wrs_conf_imageClassName + '"';
            var formulaPosition = code.indexOf(pattern);
            var endPosition = 0;

            while (formulaPosition != -1) {
                // Looking for the actual startPosition.
                startPosition = formulaPosition;
                var i = formulaPosition;
                var startTagFound = false;

                while (i >= 0 && !startTagFound) {      // Going backwards until the start tag '<' is found.
                    var character = code.charAt(i);

                    if (character == '"' || character == '\'') {
                        var characterNextPosition = code.lastIndexOf(character, i);
                        i = (characterNextPosition == -1) ? -1 : characterNextPosition;
                    }
                    else if (character == '<') {
                        startPosition = i;
                        startTagFound = true;
                    }
                    else if (character == '>') {
                        i = -1;                 // Break: we are inside a text node.
                    }

                    --i;
                }

                // Appending the previous code.
                output += code.substring(endPosition, startPosition);

                // Looking for the endPosition.

                if (startTagFound) {
                    i = formulaPosition;
                    var counter = 1;

                    while (i < code.length && counter > 0) {
                        var character = code.charAt(i);

                        if (character == '"' || character == '\'') {
                            var characterNextPosition = code.indexOf(character, i);
                            i = (characterNextPosition == -1) ? code.length : characterNextPosition;
                        }
                        else if (character == '<') {
                            if (i + 1 < code.length && code.charAt(i + 1) == '/') {
                                --counter;

                                if (counter == 0) {
                                    endPosition = code.indexOf('>', i) + 1;

                                    if (endPosition == -1) {
                                        // End tag stripped.
                                        counter = -1;       // to be != 0 and to break the loop.
                                    }
                                }
                            }
                            else {
                                ++counter;
                            }
                        }
                        else if (character == '>' && code.charAt(i - 1) == '/') {
                            --counter;

                            if (counter == 0) {
                                endPosition = i + 1;
                            }
                        }

                        ++i;
                    }

                    if (counter == 0) {
                        var formulaTagCode = code.substring(startPosition, endPosition);
                        var formulaTagObject = wrs_createObject(formulaTagCode);
                        var mathml = formulaTagObject.getAttribute(_wrs_conf_imageMathmlAttribute);

                        if (mathml == null) {
                            mathml = formulaTagObject.getAttribute('alt');
                        }

                        var imgObject = wrs_mathmlToImgObject(document, mathml, wirisProperties, language);
                        output += wrs_createObjectCode(imgObject);
                    }
                    else {
                        // Start tag found but no end tag found. No process is done. A character is appended to avoid infinite loop in the next search.
                        output += code.charAt(formulaPosition);
                        endPosition = formulaPosition + 1;
                    }
                }
                else {
                    // No start tag is found. No process is done. A character is appended to avoid infinite loop in the next search.
                    output += code.charAt(formulaPosition);
                    endPosition = formulaPosition + 1;
                }

                formulaPosition = code.indexOf(pattern, endPosition);
            }

            output += code.substring(endPosition, code.length);
            code = output;
        }

        return code;
    }

    /**
     * Parses end HTML code depending on the save mode.
     * @param {string} code HTML code
     * @return {string}
     * @ignore
     */
    function wrs_endParseSaveMode(code) {
        var output = '';
        var convertToXml = false;
        var convertToSafeXml = false;

        if (window._wrs_conf_saveMode) {
            if (_wrs_conf_saveMode == 'safeXml') {
                convertToXml = true;
                convertToSafeXml = true;
            }
            else if (_wrs_conf_saveMode == 'xml') {
                convertToXml = true;
            }
        }

        if (_wrs_conf_saveMode == 'base64' && _wrs_conf_editMode == 'image') {
            code = wrs_codeImgTransform(code, 'img264');
        } else if (convertToXml || convertToSafeXml) {
            code = wrs_codeImgTransform(code, 'img2mathml');
        }

        return code;
    }

    /**
     * Gets the formula mathml or CAS appletCode using its image hash code.
     * @param {string} variableName Variable to send on POST query to the server.
     * @param {string} imageHashCode image hash code.
     * @return {string} Corresponding mathml code.
     * @ignore
     */
    function wrs_getCode(variableName, imageHashCode) {
        var data = {};
        data[variableName] = imageHashCode;
        return wrs_getContent(_wrs_conf_getmathmlPath, data);
    }

    /**
     * Gets the content from an URL.
     * @param {string} url target URL.
     * @param {object} postVariables post variables. Null if a GET query should be done.
     * @return {string} content of the target URL.
     * @ignore
     */
    function wrs_getContent(url, postVariables) {
        try {
            var httpRequest = wrs_createHttpRequest();

            if (httpRequest) {
                if (typeof postVariables === undefined || typeof postVariables == 'undefined') {
                    httpRequest.open('GET', url, false);
                }
                else if (url.substr(0, 1) == '/' || url.substr(0, 7) == 'http://' || url.substr(0, 8) == 'https://') {
                    httpRequest.open('POST', url, false);
                }
                else {
                    httpRequest.open('POST', _wrs_currentPath + url, false);
                }

                if (postVariables !== undefined) {
                    httpRequest.setRequestHeader('Content-type', 'application/x-www-form-urlencoded; charset=UTF-8');
                    httpRequest.send(wrs_httpBuildQuery(postVariables));
                }
                else {
                    httpRequest.send(null);
                }

                return httpRequest.responseText;
            }

            alert('Your browser is not compatible with AJAX technology. Please, use the latest version of Mozilla Firefox.');
        }
        catch (e) {
        }

        return '';
    }

    /**
     * Generates the innerHTML of an element.
     * @param {object} element target element.
     * @return {string} innertHTML of the target element.
     * @ignore
     */
    function wrs_getInnerHTML(element) {
        var innerHTML = '';

        for (var i = 0; i < element.childNodes.length; ++i) {
            innerHTML += wrs_createObjectCode(element.childNodes[i]);
        }

        return innerHTML;
    }

    /**
     * Converts MathML to LaTeX.
     * @param {string} mathml MathML String
     * @return {string} MathML corresponding LaTeX.
     * @ignore
     */
    function wrs_getLatexFromMathML(mathml) {
        var data = {
            'service': 'mathml2latex',
            'mml': mathml
        };

        var jsonResponse = JSON.parse(wrs_getContent(_wrs_conf_servicePath, data));

        var latex;

        if (jsonResponse.status == "ok") {
            latex = jsonResponse.result.text;
        }

        return latex;
    }

    /**
     * Extracts the latex of a determined position in a text.
     * @param {string} textNode test to extract LaTeX
     * @param {int} caretPosition starting position to find LaTeX.
     * @return {object} An object with 3 keys: 'latex', 'start' and 'end'. Null if latex is not found.
     * @ignore
     */
    function wrs_getLatexFromTextNode(textNode, caretPosition) {
        // Looking for the first textNode.
        var startNode = textNode;

        while (startNode.previousSibling && startNode.previousSibling.nodeType == 3) { // TEXT_NODE.
            startNode = startNode.previousSibling;
        }

        // Finding latex.

        function getNextLatexPosition(currentNode, currentPosition) {
            var position = currentNode.nodeValue.indexOf('$$', currentPosition);

            while (position == -1) {
                currentNode = currentNode.nextSibling;

                if (!currentNode || currentNode.nodeType != 3) { // TEXT_NODE.
                    return null; // Not found.
                }

                position = currentNode.nodeValue.indexOf('$$');
            }

            return {
                'node': currentNode,
                'position': position
            };
        }

        function isPrevious(node, position, endNode, endPosition) {
            if (node == endNode) {
                return (position <= endPosition);
            }

            while (node && node != endNode) {
                node = node.nextSibling;
            }

            return (node == endNode);
        }

        var start;

        var end = {
            'node': startNode,
            'position': 0
        };

        do {
            var start = getNextLatexPosition(end.node, end.position);

            if (start == null || isPrevious(textNode, caretPosition, start.node, start.position)) {
                return null;
            }

            var end = getNextLatexPosition(start.node, start.position + 2);

            if (end == null) {
                return null;
            }

            end.position += 2;
        } while (isPrevious(end.node, end.position, textNode, caretPosition));

        // Isolating latex.
        var latex;

        if (start.node == end.node) {
            latex = start.node.nodeValue.substring(start.position + 2, end.position - 2);
        }
        else {
            latex = start.node.nodeValue.substring(start.position + 2, start.node.nodeValue.length);
            var currentNode = start.node;

            do {
                currentNode = currentNode.nextSibling;

                if (currentNode == end.node) {
                    latex += end.node.nodeValue.substring(0, end.position - 2);
                }
                else {
                    latex += currentNode.nodeValue;
                }
            } while (currentNode != end.node);
        }

        return {
            'latex': latex,
            'startNode': start.node,
            'startPosition': start.position,
            'endNode': end.node,
            'endPosition': end.position
        };
    }

    /**
     * Converts LaTeX to MathML.
     * @param {string} latex String
     * @param {bool} includeLatexOnSemantics If true LaTeX would me included into MathML semantics.
     * @return {string} converted mathML
     * @ignore
     */
    function wrs_getMathMLFromLatex(latex, includeLatexOnSemantics) {
        if (_wrs_int_LatexCache.hasOwnProperty(latex)) {
            return _wrs_int_LatexCache[latex];
        }
        var data = {
            'service': 'latex2mathml',
            'latex': latex
        };

        if (includeLatexOnSemantics) {
            data['saveLatex'] = '';
        }

        var jsonResponse = JSON.parse(wrs_getContent(_wrs_conf_servicePath, data));

        var output;
        if (jsonResponse.status == "ok") {
            var output = jsonResponse.result.text;
            output = output.split("\r").join('').split("\n").join(' ');
            // Populate LatexCache.
            wrs_populateLatexCache(latex, output);
        } else {
            output = "$$" + latex + "$$";
        }

        return output;
    }

    /**
     * Gets the node length in characters.
     * @param {object} node HTML node.
     * @return {int} node length
     * @ignore
     */
    function wrs_getNodeLength(node) {
        if (node.nodeType == 3) { // TEXT_NODE.
            return node.nodeValue.length;
        }

        if (node.nodeType == 1) { // ELEMENT_NODE.
            var length = _wrs_staticNodeLengths[node.nodeName.toUpperCase()];

            if (length === undefined) {
                length = 0;
            }

            for (var i = 0; i < node.childNodes.length; ++i) {
                length += wrs_getNodeLength(node.childNodes[i]);
            }

            return length;
        }

        return 0;
    }

    /**
     * Parses the query string and returns it as a Hash table.
     * @param {object} windowObject a window object with a query string.
     * @return {object} a hash table containing the query string.
     * @ignore
     */
    function wrs_getQueryParams(windowObject) {
        var data = {};
        var start = windowObject.location.search.indexOf('?');
        start = (start == -1) ? 0 : start + 1;
        var queryStringParts = windowObject.location.search.substr(start).split('&');

        for (var i = 0; i < queryStringParts.length; ++i) {
            var paramParts = queryStringParts[i].split('=', 2);
            data[paramParts[0]] = wrs_urldecode(paramParts[1]);
        }

        return data;
    }

    /**
     * Gets the selected node or text.
     * If the caret is on a text node, concatenates it with all the previous and next text nodes.
     * @param {object} target The editable element
     * @param {boolean} isIframe Specifies if the target is an iframe or not
     * @param {forceGetSelection} If true, ignores IE system to get the current selection and uses window.getSelection()
     * @return {object} An object with the 'node' key setted if the item is an element or the keys 'node' and 'caretPosition' if the element is text
     * @ignore
     */
    function wrs_getSelectedItem(target, isIframe, forceGetSelection) {
        var windowTarget;

        if (isIframe) {
            windowTarget = target.contentWindow;
            windowTarget.focus();
        }
        else {
            windowTarget = window;
            target.focus();
        }

        if (document.selection && !forceGetSelection) {
            var range = windowTarget.document.selection.createRange();

            if (range.parentElement) {
                if (range.htmlText.length > 0) {
                    if (range.text.length == 0) {
                        return wrs_getSelectedItem(target, isIframe, true);
                    }

                    return null;
                }

                windowTarget.document.execCommand('InsertImage', false, '#');
                var temporalObject = range.parentElement();

                if (temporalObject.nodeName.toUpperCase() != 'IMG') {
                    // IE9 fix: parentElement() does not return the IMG node, returns the parent DIV node. In IE < 9, pasteHTML does not work well.
                    range.pasteHTML('<span id="wrs_openEditorWindow_temporalObject"></span>');
                    temporalObject = windowTarget.document.getElementById('wrs_openEditorWindow_temporalObject');
                }

                var node;
                var caretPosition;

                if (temporalObject.nextSibling && temporalObject.nextSibling.nodeType == 3) { // TEXT_NODE.
                    node = temporalObject.nextSibling;
                    caretPosition = 0;
                }
                else if (temporalObject.previousSibling && temporalObject.previousSibling.nodeType == 3) { // TEXT_NODE.
                    node = temporalObject.previousSibling;
                    caretPosition = node.nodeValue.length;
                }
                else {
                    node = windowTarget.document.createTextNode('');
                    temporalObject.parentNode.insertBefore(node, temporalObject);
                    caretPosition = 0;
                }

                temporalObject.parentNode.removeChild(temporalObject);

                return {
                    'node': node,
                    'caretPosition': caretPosition
                };
            }

            if (range.length > 1) {
                return null;
            }

            return {
                'node': range.item(0)
            };
        }

        if (windowTarget.getSelection) {
            var selection = windowTarget.getSelection();

            try {
                var range = selection.getRangeAt(0);
            }
            catch (e) {
                var range = windowTarget.document.createRange();
            }

            var node = range.startContainer;

            if (node.nodeType == 3) { // TEXT_NODE.
                if (range.startOffset != range.endOffset) {
                    return null;
                }

                return {
                    'node': node,
                    'caretPosition': range.startOffset
                };
            }

            if (node != range.endContainer) {
                return null;
            }

            if (node.nodeType == 1) { // ELEMENT_NODE.
                var position = range.startOffset;

                if (node.childNodes[position]) {
                    return {
                        'node': node.childNodes[position]
                    };
                }
            }
        }

        return null;
    }

    /**
     * Converts the HTML of a image into the output code that WIRIS must return.
     * By default returns the mathml stored on data-mahml attribute (if imgCode is a formula)
     * or the Wiriscas attribute of a WIRIS applet.
     * @param {string} imgCode the html code from a formula or a CAS image.
     * @param {bool} convertToXml True if the image should be converted to xml.
     * @param {bool} convertToSafeXml True if the image should be conerte to safeXmll
     * @return {string} the Xml or safeXml of a WIRIS image.
     * @ignore
     */
    function wrs_getWIRISImageOutput(imgCode, convertToXml, convertToSafeXml) {
        var imgObject = wrs_createObject(imgCode);

        if (imgObject) {
            if (imgObject.className == _wrs_conf_imageClassName || imgObject.getAttribute(_wrs_conf_imageMathmlAttribute)) {
                if (!convertToXml) {
                    return imgCode;
                }

                var xmlCode = imgObject.getAttribute(_wrs_conf_imageMathmlAttribute);

                if (xmlCode == null) {
                    xmlCode = imgObject.getAttribute('alt');
                }

                if (!convertToSafeXml) {
                    xmlCode = wrs_mathmlDecode(xmlCode);
                }

                return xmlCode;
            }
            else if (imgObject.className == _wrs_conf_CASClassName) {
                var appletCode = imgObject.getAttribute(_wrs_conf_CASMathmlAttribute);
                appletCode = wrs_mathmlDecode(appletCode);
                var appletObject = wrs_createObject(appletCode);
                appletObject.setAttribute('src', imgObject.src);
                var object = appletObject;
                var appletCodeToBeInserted = wrs_createObjectCode(appletObject);

                if (convertToSafeXml) {
                    appletCodeToBeInserted = wrs_mathmlEncode(appletCodeToBeInserted);
                }

                return appletCodeToBeInserted;
            }
        }

        return imgCode;
    }

    /**
     * Parses a text and replaces all HTML special characters by their entities.
     * @param {string} input Text to be paresed.
     * @return {string} the input text with all their special characters replaced by their entities.
     * @ignore
     */
    function wrs_htmlentities(input) {
        return input.split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;').split('"').join('&quot;');
    }

    /**
     * Parses a text and replaces all the HTML entities by their characters.
     * @param {string} input Text to be parsed
     * @return {string} The input text with all their entities replaced by characters.
     * @ignore
     */
    function wrs_htmlentitiesDecode(input) {
        return input.split('&quot;').join('"').split('&gt;').join('>').split('&lt;').join('<').split('&amp;').join('&');
    }

    /**
     * Converts a hash to a HTTP query.
     * @param {hash} properties A key-value Hash
     * @return {string} A HTTP query containing all the key value pairs with all the shpecial characters replaced by their entities.
     * @ignore
     */
    function wrs_httpBuildQuery(properties) {
        var result = '';

        for (i in properties) {
            if (properties[i] != null) {
                result += wrs_urlencode(i) + '=' + wrs_urlencode(properties[i]) + '&';
            }
        }

        // Deleting last '&' empty character.
        if (result.substring(result.length - 1) == '&') {
            result = result.substring(0, result.length - 1);
        }

        return result;
    }

    /**
     * Parses initial HTML code. If the HTML contains data generated by WIRIS, this data would be converted as following:
     * <pre>
     * MathML code: Image containing the corresponding MathML formulas.
     * MathML code with LaTeX annotation : LaTeX.
     * </pre>
     * @param {string} code HTML code with data generated by WIRIS plugin.
     * @param {string} language Language for the formula.
     * @return {string} HTML code with the WIRIS data converted into LaTeX and images.
     */
     /* Note: The code inside this function has been inverted.
        If you invert again the code then you cannot use correctly LaTeX
        in Moodle.
     */
    function wrs_initParse(code, language) {
        wrs_initSetSize();
        if (window._wrs_conf_saveMode) {
            _wrs_parseXml = _wrs_conf_saveMode == 'safeXml'|| _wrs_conf_saveMode == 'xml';
            if (window._wrs_conf_parseModes !== undefined) {
                _wrs_parseXml = _wrs_parseXml || wrs_arrayContains(_wrs_conf_parseModes, 'xml') != -1;
            }
        }
        code = wrs_initParseSaveMode(code, language);
        return wrs_initParseEditMode(code);
    }

    /**
     * Parses initial HTML code into iframes.
     * @param  {object} windowTarget Target object window.
     * @ignore
     */
    function wrs_initParseImgToIframes(windowTarget) {
        if (window._wrs_conf_defaultEditMode && _wrs_conf_defaultEditMode == 'iframes') {
            var imgList = windowTarget.document.getElementsByTagName('img');
            var i = 0;

            while (i < imgList.length) {
                if (imgList[i].className == _wrs_conf_imageClassName) {
                    var mathml = imgList[i].getAttribute(_wrs_conf_imageMathmlAttribute);

                    if (mathml == null) {
                        mathml = imgList[i].getAttribute('alt');
                    }

                    var iframe = wrs_mathmlToIframeObject(windowTarget, wrs_mathmlDecode(mathml));
                    imgList[i].parentNode.replaceChild(iframe, imgList[i]);
                }
                else {
                    ++i;
                }
            }
        }
    }

    /**
     * Parses initial HTML code depending on the edit mode.
     * @param {string} code HTML code.
     * @return {string} parsed HTML code.
     * @ignore
     */
    function wrs_initParseEditMode(code) {
        if (window._wrs_conf_parseModes !== undefined && wrs_arrayContains(_wrs_conf_parseModes, 'latex') != -1) {
            var imgList = wrs_getElementsByNameFromString(code, 'img', true);
            var token = 'encoding="LaTeX">';
            var carry = 0;          // While replacing images with latex, the indexes of the found images changes respecting the original code, so this carry is needed.

            for (var i = 0; i < imgList.length; ++i) {
                var imgCode = code.substring(imgList[i].start + carry, imgList[i].end + carry);

                if (imgCode.indexOf(' class="' + _wrs_conf_imageClassName + '"') != -1) {
                    var mathmlStartToken = ' ' + _wrs_conf_imageMathmlAttribute + '="';
                    var mathmlStart = imgCode.indexOf(mathmlStartToken);

                    if (mathmlStart == -1) {
                        mathmlStartToken = ' alt="';
                        mathmlStart = imgCode.indexOf(mathmlStartToken);
                    }

                    if (mathmlStart != -1) {
                        mathmlStart += mathmlStartToken.length;
                        var mathmlEnd = imgCode.indexOf('"', mathmlStart);
                        var mathml = wrs_mathmlDecode(imgCode.substring(mathmlStart, mathmlEnd));
                        var latexStartPosition = mathml.indexOf(token);

                        if (latexStartPosition != -1) {
                            latexStartPosition += token.length;
                            var latexEndPosition = mathml.indexOf('</annotation>', latexStartPosition);
                            var latex = mathml.substring(latexStartPosition, latexEndPosition);

                            var replaceText = '$$' + wrs_htmlentitiesDecode(latex) + '$$';
                            code = code.substring(0, imgList[i].start + carry) + replaceText + code.substring(imgList[i].end + carry);
                            carry += replaceText.length - (imgList[i].end - imgList[i].start);
                        }
                    }
                }
            }
        }

        return code;
    }

    /**
     * Parses initial HTML code depending on the save mode.
     * @param {string} code HTML code to be parsed
     * @param {string} language Language for the formula.
     * @return {string} HTML code parsed.
     * @ignore
     */
    function wrs_initParseSaveMode(code, language) {
        if (window._wrs_conf_saveMode) {

            if (_wrs_parseXml) {
                // Converting XML to tags.
                code = wrs_parseMathmlToLatex(code, _wrs_safeXmlCharacters);
                code = wrs_parseMathmlToLatex(code, _wrs_xmlCharacters);
                // Safe XML and XML must be parsed regardeless of save mode.
                // Order is important here, safeXml must be parsed first in order to avoid conflicts with data-mathml img attribute.
                code = wrs_parseSafeAppletsToObjects(code);
                code = wrs_parseMathmlToImg(code, _wrs_safeXmlCharacters, language);
                code = wrs_parseMathmlToImg(code, _wrs_xmlCharacters, language);
            }

            if (_wrs_conf_saveMode == 'base64' && _wrs_conf_editMode == 'image') {
                code = wrs_codeImgTransform(code, 'base642showimage');
            }
        }

        var appletList = wrs_getElementsByNameFromString(code, 'applet', false);
        var carry = 0;          // While replacing applets with images, the indexes of the found applets changes respecting the original code, so this carry is needed.

        for (var i = 0; i < appletList.length; ++i) {
            var appletCode = code.substring(appletList[i].start + carry, appletList[i].end + carry);

            // The second control in the if is used to find WIRIS applet which don't have Wiriscas class (as it was in old CAS applets).
            if (appletCode.indexOf(' class="' + _wrs_conf_CASClassName + '"') != -1 || appletCode.toUpperCase().indexOf('WIRIS') != -1) {
                if (appletCode.indexOf(' src="') != -1){
                    var srcStart = appletCode.indexOf(' src="') + ' src="'.length;
                    var srcEnd = appletCode.indexOf('"', srcStart);
                    var src = appletCode.substring(srcStart, srcEnd);
                } else{
                    // This should happen only with old CAS imported from Moodle 1 to Moodle 2.
                    if (typeof(_wrs_conf_pluginBasePath) != 'undefined'){
                        var src = _wrs_conf_pluginBasePath + '/integration/showcasimage.php?formula=noimage';
                    } else {
                        var src = '';
                    }
                    if (appletCode.indexOf(' class="' + _wrs_conf_CASClassName + '"') == -1){
                        var closeSymbol = appletCode.indexOf('>');
                        var appletTag = appletCode.substring(0, closeSymbol);
                        var newAppletTag = appletTag.split(' width=').join(' class="Wiriscas" width=');
                        appletCode = appletCode.split(appletTag).join(newAppletTag);
                        appletCode = appletCode.split('\'').join('"');
                    }
                }

                // Double click to edit has been removed here.
                var imgCode = '<img align="middle" class="' + _wrs_conf_CASClassName + '" ' + _wrs_conf_CASMathmlAttribute + '="' + wrs_mathmlEncode(appletCode) + '" src="' + src + '" />';

                code = code.substring(0, appletList[i].start + carry) + imgCode + code.substring(appletList[i].end + carry);
                carry += imgCode.length - (appletList[i].end - appletList[i].start);
            }
        }

        return code;
    }

    /**
     * Looks for elements that match the given name in a HTML code string.
     * Important: this function is very concrete for WIRIS code. It takes as preconditions lots of behaviors that are not the general case.
     *
     * @param {string} code HTML code
     * @param {string} name Element names
     * @param {boolean} autoClosed True if the elements are autoClosed.
     * @return {array} An array containing all HTML elements of code matching the name argument.
     * @ignore
     */
    function wrs_getElementsByNameFromString(code, name, autoClosed) {
        var elements = [];
        var code = code.toLowerCase();
        name = name.toLowerCase();
        var start = code.indexOf('<' + name + ' ');

        while (start != -1) {                       // Look for nodes.
            var endString;

            if (autoClosed) {
                endString = '>';
            }
            else {
                endString = '</' + name + '>';
            }

            var end = code.indexOf(endString, start);

            if (end != -1) {
                end += endString.length;

                elements.push({
                    'start': start,
                    'end': end
                });
            }
            else {
                end = start + 1;
            }

            start = code.indexOf('<' + name + ' ', end);
        }

        return elements;
    }

    /**
     * Replaces a selection with an element.
     * @param {object} element Element
     * @param {object} focusElement Element to be focused
     * @param {object} windowTarget Target
     * @ignore
     */
    function wrs_insertElementOnSelection(element, focusElement, windowTarget) {
        try {
            // Integration function
            // If wrs_int_insertElementOnSelection function exists on
            // integration script can call focus method from the editor instance.
            // For example, on CKEditor calls CKEditorInstance.focus() method.
            // With this method we can call proper focus methods which in some scenarios
            // help's WIRIS plugin to focus properly on the current editor window.
            if (typeof wrs_int_insertElementOnSelection != 'undefined') {
                wrs_int_insertElementOnSelection();
                if (!_wrs_range) {
                    focusElement.focus();
                }
            }
            else {
                focusElement.focus();
            }

            if (_wrs_isNewElement) {
                if (document.selection && document.getSelection == 0) {
                    var range = windowTarget.document.selection.createRange();
                    windowTarget.document.execCommand('InsertImage', false, element.src);

                    if (!('parentElement' in range)) {
                        windowTarget.document.execCommand('delete', false);
                        range = windowTarget.document.selection.createRange();
                        windowTarget.document.execCommand('InsertImage', false, element.src);
                    }

                    if ('parentElement' in range) {
                        var temporalObject = range.parentElement();

                        if (temporalObject.nodeName.toUpperCase() == 'IMG') {
                            temporalObject.parentNode.replaceChild(element, temporalObject);
                        }
                        else {
                            // IE9 fix: parentNode() does not return the IMG node, returns the parent DIV node. In IE < 9, pasteHTML does not work well.
                            range.pasteHTML(wrs_createObjectCode(element));
                        }
                    }
                }
                else {
                    var ua = navigator.userAgent.toLowerCase();
                    var isAndroid = ua.indexOf("android") > -1;
                    var isIOS = ((ua.indexOf("ipad") > -1) || (ua.indexOf("iphone") > -1));
                    var selection = windowTarget.getSelection();
                    if (_wrs_range) {
                        var range = _wrs_range;
                        _wrs_range = null;
                    }
                    else {

                        try {
                            var range = selection.getRangeAt(0);
                        }
                        catch (e) {
                            var range = windowTarget.document.createRange();
                        }
                    }
                    selection.removeAllRanges();

                    range.deleteContents();

                    var node = range.startContainer;
                    var position = range.startOffset;

                    if (node.nodeType == 3) { // TEXT_NODE.
                        node = node.splitText(position);
                        node.parentNode.insertBefore(element, node);
                        node = node.parentNode;
                    }
                    else if (node.nodeType == 1) { // ELEMENT_NODE.
                        node.insertBefore(element, node.childNodes[position]);
                    }

                    if (!isAndroid && !isIOS){
                        // Fix to set the caret after the inserted image.
                        range.selectNode(element);
                        position = range.endOffset;
                        selection.collapse(node, position);
                        // Integration function
                        // If wrs_int_setCaretPosition function exists on
                        // integration script can call caret method from the editor instance.
                        // With this method we can call proper specific editor methods which in some scenarios
                        // help's WIRIS plugin to set caret position properly on the current editor window.
                        if (typeof wrs_int_selectRange != 'undefined') {
                            wrs_int_selectRange(range);
                        }
                    }
                }
            }
            else if (_wrs_temporalRange) {
                if (document.selection && document.getSelection == 0) {
                    _wrs_isNewElement = true;
                    _wrs_temporalRange.select();
                    wrs_insertElementOnSelection(element, focusElement, windowTarget);
                }
                else {
                    var parentNode = _wrs_temporalRange.startContainer;
                    _wrs_temporalRange.deleteContents();
                    _wrs_temporalRange.insertNode(element);
                }
            }
            else {
                if (!element) { // Editor empty, formula has been erased on edit.
                    _wrs_temporalImage.parentNode.removeChild(_wrs_temporalImage);
                }
                _wrs_temporalImage.parentNode.replaceChild(element, _wrs_temporalImage);
            }
        }
        catch (e) {
        }
    }

    /**
     * Checks if the mathml at position i is inside an HTML attribute or not.
     * @param {string} content A string containing MathML code.
     * @param {string} i Search index.
     * @return {bool} True if is inside an HTML attribute. In other case, false.
     * @ignore
     */
    function wrs_isMathmlInAttribute(content, i) {
        // Regex = '^[\'"][\\s]*=[\\s]*[\\w-]+([\\s]*("[^"]*"|\'[^\']*\')[\\s]*=[\\s]*[\\w-]+[\\s]*)*[\\s]+gmi<';
        var math_att = '[\'"][\\s]*=[\\s]*[\\w-]+';                         // "=att OR '=att
        var att_content = '"[^"]*"|\'[^\']*\'';                             // "blabla" OR 'blabla'
        var att = '[\\s]*(' + att_content + ')[\\s]*=[\\s]*[\\w-]+[\\s]*';  // "blabla"=att OR 'blabla'=att
        var atts = '(' + att + ')*';                                        // "blabla"=att1 "blabla"=att2
        var regex = '^' + math_att + atts + '[\\s]+gmi<';                   // "=att "blabla"=att1 "blabla"=att2 gmi< .
        var expression = new RegExp(regex);

        var actual_content = content.substring(0, i);
        var reversed = actual_content.split('').reverse().join('');
        var exists = expression.test(reversed);

        return exists;
    }

    /**
     * WIRIS special encoding.
     * We use these entities because IE doesn't support html entities on its attributes sometimes. Yes, sometimes.
     * @param {string} input String to be decoded.
     * @return {string} Decoded string.
     * @ignore
     */
    function wrs_mathmlDecode(input) {
        // Decoding entities.
        input = input.split(_wrs_safeXmlCharactersEntities.tagOpener).join(_wrs_safeXmlCharacters.tagOpener);
        input = input.split(_wrs_safeXmlCharactersEntities.tagCloser).join(_wrs_safeXmlCharacters.tagCloser);
        input = input.split(_wrs_safeXmlCharactersEntities.doubleQuote).join(_wrs_safeXmlCharacters.doubleQuote);
        // Added to fix problem due to import from 1.9.x.
        input = input.split(_wrs_safeXmlCharactersEntities.realDoubleQuote).join(_wrs_safeXmlCharacters.realDoubleQuote);

        // Blackboard.
        if ('_wrs_blackboard' in window && window._wrs_blackboard){
            input = input.split(_wrs_safeBadBlackboardCharacters.ltElement).join(_wrs_safeGoodBlackboardCharacters.ltElement);
            input = input.split(_wrs_safeBadBlackboardCharacters.gtElement).join(_wrs_safeGoodBlackboardCharacters.gtElement);
            input = input.split(_wrs_safeBadBlackboardCharacters.ampElement).join(_wrs_safeGoodBlackboardCharacters.ampElement);

            /*var regex = /«mtext».*[<>&].*«\/mtext»/;

            var result = regex.exec(input);
            while(result){
                var changedResult = result[0].split(_wrs_xmlCharacters.tagOpener).join('§lt;');
                changedResult = changedResult.split(_wrs_xmlCharacters.tagCloser).join('§gt;');
                changedResult = changedResult.split(_wrs_xmlCharacters.ampersand).join('§amp;');
                input = input.replace(result, changedResult);
                result = regex.exec(input);
            }*/
        }

        // Decoding characters.
        input = input.split(_wrs_safeXmlCharacters.tagOpener).join(_wrs_xmlCharacters.tagOpener);
        input = input.split(_wrs_safeXmlCharacters.tagCloser).join(_wrs_xmlCharacters.tagCloser);
        input = input.split(_wrs_safeXmlCharacters.doubleQuote).join(_wrs_xmlCharacters.doubleQuote);
        input = input.split(_wrs_safeXmlCharacters.ampersand).join(_wrs_xmlCharacters.ampersand);
        input = input.split(_wrs_safeXmlCharacters.quote).join(_wrs_xmlCharacters.quote);

        // We are replacing $ by & when its part of an entity for retrocompatibility. Now, the standard is replace § by &.
        var returnValue = '';
        var currentEntity = null;

        for (var i = 0; i < input.length; ++i) {
            var character = input.charAt(i);

            if (currentEntity == null) {
                if (character == '$') {
                    currentEntity = '';
                }
                else {
                    returnValue += character;
                }
            }
            else {
                if (character == ';') {
                    returnValue += '&' + currentEntity + ';';
                    currentEntity = null;
                }
                else if (character.match(/([a-zA-Z0-9#._-] | '-')/)) {  // Character is part of an entity.
                    currentEntity += character;
                }
                else {
                    returnValue += '$' + currentEntity; // Is not an entity.
                    currentEntity = null;
                    --i; // Parse again the current character.
                }
            }
        }

        return returnValue;
    }

    /**
     * WIRIS special encoding.
     * We use these entities because IE doesn't support html entities on its attributes sometimes. Yes, sometimes.
     * @param {string} input to be encoded
     * @return {string} Encoded string.
     * @ignore
     */
    function wrs_mathmlEncode(input) {
        input = input.split(_wrs_xmlCharacters.tagOpener).join(_wrs_safeXmlCharacters.tagOpener);
        input = input.split(_wrs_xmlCharacters.tagCloser).join(_wrs_safeXmlCharacters.tagCloser);
        input = input.split(_wrs_xmlCharacters.doubleQuote).join(_wrs_safeXmlCharacters.doubleQuote);
        input = input.split(_wrs_xmlCharacters.ampersand).join(_wrs_safeXmlCharacters.ampersand);
        input = input.split(_wrs_xmlCharacters.quote).join(_wrs_safeXmlCharacters.quote);

        return input;
    }

    /**
     * Converts special symbols (> 128) to entities and replaces all textual entities by its number entities.
     * @param {string} mathml MathML string containing - or not - special symbols
     * @return {string} MathML with all textual entities replaced.
     * @ignore
     */
    function wrs_mathmlEntities(mathml) {
        var toReturn = '';

        for (var i = 0; i < mathml.length; ++i) {
            var character = mathml.charAt(i);

            // Parsing > 128 characters.
            if (mathml.charCodeAt(i) > 128) {
                toReturn += '&#' + mathml.charCodeAt(i) + ';';
            }
            else if (character == '&') {
                var end = mathml.indexOf(';', i + 1);

                if (end >= 0) {
                    var container = document.createElement('span');
                    container.innerHTML = mathml.substring(i, end + 1);
                    toReturn += '&#' + wrs_fixedCharCodeAt((container.innerText || container.textContent),0) + ';';
                    i = end;
                }
                else {
                    toReturn += character;
                }
            }
            else {
                toReturn += character;
            }
        }

        return toReturn;
    }

    /**
     * Add wrs::type attribute to mathml if the mathml has been created with a custom editor
     * for example, chemistry.
     * @param {string} mathml a MathML string created with a custom editor, like chemistry.
     * @return {string} The MathML string with his class containgin the editor toolbar string.
     * @ignore
     */
    function wrs_mathmlAddEditorAttribute(mathml) {
        var toReturn = '';

        var start = mathml.indexOf('<math');
        if (start == 0 ) {
            end = mathml.indexOf('>');
            if (mathml.indexOf("class") == -1 ) {
                // Adding custom editor type.
                toReturn = mathml.substr(start, end) + ' class="wrs_' + wrs_int_getCustomEditorEnabled().toolbar + '">';
                toReturn += mathml.substr(end + 1, mathml.length);
                return toReturn;
            }
        }
        return mathml;

    }

    /**
     * Fix charCodeAt() javascript function to handle non-Basic-Multilingual-Plane characters.
     * @param {string} str String
     * @param {int} idx An integer greater than or equal to 0 and less than the length of the string
     * @return {int} An integer representing the UTF-16 code of the string at the given index.
     * @ignore
     */

    function wrs_fixedCharCodeAt(str, idx) {
        idx = idx || 0;
        var code = str.charCodeAt(idx);
        var hi, low;

        /* High surrogate (could change last hex to 0xDB7F to treat high
        private surrogates as single characters) */

        if (0xD800 <= code && code <= 0xDBFF) {
            hi = code;
            low = str.charCodeAt(idx + 1);
            if (isNaN(low)) {
                throw 'High surrogate not followed by low surrogate in fixedCharCodeAt()';
            }
            return ((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;
        }

        if (0xDC00 <= code && code <= 0xDFFF) { // Low surrogate.
            /* We return false to allow loops to skip this iteration since should have
               already handled high surrogate above in the previous iteration. */
            return false;
        }
        return code;
    }

    /**
     * Gets the accessible text of a given MathML calling mathml2accesible service.
     * @param {string} mathml MathML to get the accesibility.
     * @param {string} language Language of the accesibility.
     * @return {string} Accessibility from mathml string on language string.
     * @ignore
     */
    function wrs_mathmlToAccessible(mathml, language, data) {
        var accessibleText;

        if (_wrs_int_AccessibleCache.hasOwnProperty(mathml)) {
            accessibleText = _wrs_int_AccessibleCache[mathml];
        }
        else {
            data['service'] = 'mathml2accessible';
            data['lang'] = _wrs_int_langCode;
            var accesibleJsonResponse = JSON.parse(wrs_getContent(_wrs_conf_servicePath, data));
            if (accesibleJsonResponse.status != 'error') {
                accessibleText = accesibleJsonResponse.result.text;
            }
            else {
                accessibleText = 'Error converting from MathML to accessible text.';
            }
        }

        return accessibleText;

    }

    /**
     * Converts mathml to an iframe object.
     * @param {object} windowTarget Window object.
     * @param {string} mathml MathML to be converted.
     * @return {object} iframe object containging parsed mathml.
     * @ignore
     */
    function wrs_mathmlToIframeObject(windowTarget, mathml) {
        if (window.navigator.userAgent.toLowerCase().indexOf('webkit') != -1) {
            // In WebKit, the formula is represented by a div instead of an iframe.
            var container = windowTarget.document.createElement('span');
            container.className = _wrs_conf_imageClassName;
            container.setAttribute(_wrs_conf_imageMathmlAttribute, mathml);
            container.setAttribute('height', '1');
            container.setAttribute('width', '1');
            container.style.display = 'inline-block';
            container.style.cursor = 'pointer';
            container.style.webkitUserModify = 'read-only';
            container.style.webkitUserSelect = 'all';

            var formulaContainer = windowTarget.document.createElement('span');
            formulaContainer.style.display = 'inline';
            container.appendChild(formulaContainer);

            function waitForViewer() {
                if (windowTarget.com && windowTarget.com.wiris) {
                    if (!('_wrs_viewer' in windowTarget)) {
                        windowTarget._wrs_viewer = new windowTarget.com.wiris.jsEditor.JsViewerMain(_wrs_conf_pluginBasePath + '/integration/editor');
                        windowTarget._wrs_viewer.insertCSS(null, windowTarget.document);
                    }

                    windowTarget._wrs_viewer.paintFormulaOnContainer(mathml, formulaContainer, null);

                    function prepareDiv() {
                        if (windowTarget._wrs_viewer.isReady()) {
                            container.style.height = formulaContainer.style.height;
                            container.style.width = formulaContainer.style.width;
                            container.style.verticalAlign = formulaContainer.style.verticalAlign;
                        }
                        else {
                            setTimeout(prepareDiv, 100);
                        }
                    };

                    prepareDiv();
                }
                else {
                    setTimeout(waitForViewer, 100);
                }
            }

            if (!('_wrs_viewerAppended' in windowTarget)) {
                var viewerScript = windowTarget.document.createElement('script');
                viewerScript.src = _wrs_conf_pluginBasePath + '/integration/editor/viewer.js';
                windowTarget.document.getElementsByTagName('head')[0].appendChild(viewerScript);
                windowTarget._wrs_viewerAppended = true;
            }

            waitForViewer();

            return container;
        }

        windowTarget.document.wrs_assignIframeEvents = function (myIframe) {
            wrs_addEvent(myIframe.contentWindow.document, 'click', function () {
                wrs_fireEvent(myIframe, 'dblclick');
            });
        };

        var iframe = windowTarget.document.createElement('iframe');
        iframe.className = _wrs_conf_imageClassName;
        iframe.setAttribute(_wrs_conf_imageMathmlAttribute, mathml);
        iframe.style.display = 'inline';
        iframe.style.border = 'none';
        iframe.setAttribute('height', '1');
        iframe.setAttribute('width', '1');
        iframe.setAttribute('scrolling', 'no');
        iframe.setAttribute('frameBorder', '0');
        iframe.src = _wrs_conf_pluginBasePath + '/core/iframe.html#' + _wrs_conf_imageMathmlAttribute;
        return iframe;
    }

    /**
     * Converts mathml to img object.
     * @param {object} creator Object with the "createElement" method
     * @param {string} mathml MathML code
     * @param {object} wirisProperties object containing WIRIS custom properties
     * @param {language} language Custom language for accesibility.
     * @return {object} And image containing the formula image corresponding to mathml string.
     * @ignore
     */
    function wrs_mathmlToImgObject(creator, mathml, wirisProperties, language) {
        var width;
        var height;
        var baseline;
        var imgObject = creator.createElement('img');
        imgObject.align = 'middle';
        imgObject.style.maxWidth = 'none';
        var data = (wirisProperties) ? wirisProperties : {};

        if (window._wrs_conf_useDigestInsteadOfMathml && _wrs_conf_useDigestInsteadOfMathml) {
            data['returnDigest'] = 'true';
        }

        data['mml'] = mathml;
        data['lang'] = language;

        if (_wrs_conf_setSize) {
            // Request metrics of the generated image.
            data['metrics'] = 'true';
            data['centerbaseline'] = 'false';
        }

        // Full base64 method (edit & save).
        if (_wrs_conf_saveMode == 'base64' && _wrs_conf_editMode == 'default') {
            data['base64'] = true;
        }

        // Render js params: _wrs_int_wirisProperties contains some js render params. Since mathml can support render params, js params should be send only to editor, not to render.

        imgObject.className = _wrs_conf_imageClassName;

        // TODO Custom Editors: class="wrs_toolbar" should be given by the editor
        // so the first condition shouldn't be longer necessary.
        if (customEditor = wrs_int_getCustomEditorEnabled()) {
            imgObject.setAttribute('data-custom-editor', customEditor.toolbar);
        } else if (mathml.indexOf('class="') != -1) { // We check here if the mathmnl has been created from a customEditor (such chemistry)
            // to add data-custom-editor attribute to img object (if necessary).
            mathmlSubstring = mathml.substring(mathml.indexOf('class="') + 'class="'.length, mathml.length);
            mathmlSubstring = mathmlSubstring.substring(0, mathmlSubstring.indexOf('"'));
            mathmlSubstring = mathmlSubstring.substring(4,mathmlSubstring.length);
            imgObject.setAttribute('data-custom-editor', mathmlSubstring);
        }

        // Performance enabled.
        if (_wrs_conf_wirisPluginPerformance && (_wrs_conf_saveMode == 'xml' || _wrs_conf_saveMode == 'safeXml')) {

            var result = JSON.parse(wrs_createShowImageSrc(mathml, data, language));
            if (result["status"] == 'warning') {
                // POST call.
                // if the mathml is malformed, this function will throw an exception.
                try {
                    result = JSON.parse(wrs_getContent(_wrs_conf_showimagePath, data));
                }
                catch (e) {
                    return;
                }
            }
            result = result.result;
            if (result['format'] == 'png') {
                imgObject.src = 'data:image/png;base64,' + result['content'];
            } else {
                imgObject.src = 'data:image/svg+xml;charset=utf8,' + wrs_urlencode(result['content']);
            }
            imgObject.setAttribute(_wrs_conf_imageMathmlAttribute, wrs_mathmlEncode(mathml));
            if (_wrs_conf_setSize) {
                wrs_setImgSize(imgObject, result['content'], true);
            }

            if (window._wrs_conf_enableAccessibility && _wrs_conf_enableAccessibility) {
                if (typeof result.alt == 'undefined') {
                    imgObject.alt = wrs_mathmlToAccessible(mathml, language, data);
                    wrs_populateAccessibleCache(mathml, imgObject.alt);
                }
                else {
                    imgObject.alt = result.alt;
                }
            }
        }
        else {
            var result = wrs_createImageSrc(mathml, data);
            if (window._wrs_conf_useDigestInsteadOfMathml && _wrs_conf_useDigestInsteadOfMathml) {
                var parts = result.split(':', 2);
                imgObject.setAttribute(_wrs_conf_imageMathmlAttribute, parts[0]);
                imgObject.src = parts[1];
            }
            else {
                imgObject.setAttribute(_wrs_conf_imageMathmlAttribute, wrs_mathmlEncode(mathml));
                imgObject.src = result;
                if (_wrs_conf_setSize) {
                    wrs_setImgSize(imgObject,result, (_wrs_conf_saveMode == 'base64' && _wrs_conf_editMode == 'default') ? true : false);
                }
            }
            if (window._wrs_conf_enableAccessibility && _wrs_conf_enableAccessibility) {
                imgObject.alt = wrs_mathmlToAccessible(mathml, language, data);
                wrs_populateAccessibleCache(mathml, imgObject.alt);
            }
        }
        /* if (_wrs_conf_setSize) {
            var ar = wrs_urlToAssArray(result);
            width = ar['cw'];
            height = ar['ch'];
            baseline = ar['cb'];
            dpi = ar['dpi'];
            if (dpi) {
                width = width * 96/dpi;
                height = height * 96/dpi;
                baseline = baseline * 96/dpi;
            }
            // result = wrs_assArrayToUrl(ar);
        }*/

        if (typeof wrs_observer != 'undefined') {
            wrs_observer.observe(imgObject, wrs_observer_config);
        }

        // Role math https://www.w3.org/TR/wai-aria/roles#math.
        imgObject.setAttribute('role', 'math');
        return imgObject;
    }

    /**
     * Opens a new CAS window.
     * @param {object} target The editable element
     * @param {boolean} isIframe Specifies if target is an iframe or not
     * @param {string} language CAS language.
     * @return {object} The opened window
     * @ignore
     */
    function wrs_openCASWindow(target, isIframe, language) {
        if (isIframe === undefined) {
            isIframe = true;
        }

        _wrs_temporalRange = null;

        if (target) {
            var selectedItem = wrs_getSelectedItem(target, isIframe);

            if (selectedItem != null && selectedItem.caretPosition === undefined && selectedItem.node.nodeName.toUpperCase() == 'IMG' && selectedItem.node.className == _wrs_conf_CASClassName) {
                _wrs_temporalImage = selectedItem.node;
                _wrs_isNewElement = false;
            }
        }

        var path = _wrs_conf_CASPath;

        if (language) {
            path += '?lang=' + language;
        }

        return window.open(path, 'WIRISCAS', _wrs_conf_CASAttributes);
    }

    /**
     * Opens a new editor window.
     * @param {string} language Language code for the editor
     * @param {object} target The editable element
     * @param {boolean} isIframe Specifies if the target is an iframe or not
     * @param {boolean} isModal Specifies if the target is a modal window or not
     * @return {object} The opened window
     * @ignore
     */
    function wrs_openEditorWindow(language, target, isIframe) {
        var ua = navigator.userAgent.toLowerCase();
        var isAndroid = ua.indexOf("android") > -1;
        var isIOS = ((ua.indexOf("ipad") > -1) || (ua.indexOf("iphone") > -1));

        if(isAndroid || isIOS) {
            _wrs_conf_modalWindow = true; // Conf property must be overrided on tablet/phone devices.
        }

        try {
            if (isIframe) {
                var selection = target.contentWindow.getSelection();
                _wrs_range = selection.getRangeAt(0);
            }
            else {
                var selection = getSelection();
                _wrs_range = selection.getRangeAt(0);
            }
        }
        catch (e) {
            _wrs_range = null;
        }

        if (isIframe === undefined) {
            isIframe = true;
        }

        // Avoid double slashes.
        var path = _wrs_conf_path.lastIndexOf('/') == _wrs_conf_path.length - 1 ? _wrs_conf_path + "core/editor.html" : _wrs_conf_path + "/core/editor.html";

        if (language) {
            path = wrs_addArgument(path, "lang", language);
        }

        if (location.protocol == 'https:') {
            path = wrs_addArgument(path, "secure", "true");
        }

        var availableDirs = new Array('rtl', 'ltr');
        if (typeof _wrs_int_directionality != 'undefined' && wrs_arrayContains(availableDirs, _wrs_int_directionality) != -1){
            path = wrs_addArgument(path,"dir",_wrs_int_directionality);
        }

        // Cross Domain Policy.
        wrs_addArgument(path, 'host', 'localhost');

        _wrs_editMode = (window._wrs_conf_defaultEditMode) ? _wrs_conf_defaultEditMode : 'images';
        _wrs_temporalRange = null;

        if (target) {
            var selectedItem;
            if (typeof wrs_int_getSelectedItem != 'undefined') {
                selectedItem = wrs_int_getSelectedItem(target, isIframe);
            } else {
                selectedItem = wrs_getSelectedItem(target, isIframe);
            }

            if (selectedItem != null) {
                if (selectedItem.caretPosition === undefined) {
                    if (wrs_containsClass(selectedItem.node, _wrs_conf_imageClassName)) {
                        if (selectedItem.node.nodeName.toUpperCase() == 'IMG') {
                            _wrs_editMode = 'images';
                        }
                        else if (selectedItem.node.nodeName.toUpperCase() == 'IFRAME') {
                            _wrs_editMode = 'iframes';
                        }

                        _wrs_temporalImage = selectedItem.node;
                        _wrs_isNewElement = false;
                    }
                }
                else {
                    var latexResult = wrs_getLatexFromTextNode(selectedItem.node, selectedItem.caretPosition);

                    if (latexResult != null) {
                        _wrs_editMode = 'latex';

                        var mathml = wrs_getMathMLFromLatex(latexResult.latex);
                        _wrs_isNewElement = false;

                        _wrs_temporalImage = document.createElement('img');
                        _wrs_temporalImage.setAttribute(_wrs_conf_imageMathmlAttribute, wrs_mathmlEncode(mathml));
                        var windowTarget = (isIframe) ? target.contentWindow : window;

                        if (document.selection) {
                            var leftOffset = 0;
                            var previousNode = latexResult.startNode.previousSibling;

                            while (previousNode) {
                                leftOffset += wrs_getNodeLength(previousNode);
                                previousNode = previousNode.previousSibling;
                            }

                            _wrs_temporalRange = windowTarget.document.selection.createRange();
                            _wrs_temporalRange.moveToElementText(latexResult.startNode.parentNode);
                            _wrs_temporalRange.move('character', leftOffset + latexResult.startPosition);
                            _wrs_temporalRange.moveEnd('character', latexResult.latex.length + 4); // Plus 4 for the '$$' characters.
                        }
                        else {
                            _wrs_temporalRange = windowTarget.document.createRange();
                            _wrs_temporalRange.setStart(latexResult.startNode, latexResult.startPosition);
                            _wrs_temporalRange.setEnd(latexResult.endNode, latexResult.endPosition);
                        }
                    }
                }
            }
        }

        var title = wrs_int_getCustomEditorEnabled() != null ? wrs_int_getCustomEditorEnabled().title : 'WIRIS EDITOR math';
        if (typeof _wrs_conf_modalWindow != 'undefined' && _wrs_conf_modalWindow === false) {
            _wrs_popupWindow = window.open(path, title, _wrs_conf_editorAttributes);
            return _wrs_popupWindow;
        }
        else {
            if (_wrs_modalWindow == null) {
                _wrs_modalWindow = new ModalWindow(path, _wrs_conf_editorAttributes);
            }
            if (!_wrs_css_loaded) {
                var fileref = document.createElement("link");
                fileref.setAttribute("rel", "stylesheet");
                fileref.setAttribute("type", "text/css");
                fileref.setAttribute("href", wrs_concatenateUrl(_wrs_conf_path, '/core/modal.css'));
                document.getElementsByTagName("head")[0].appendChild(fileref);
                _wrs_css_loaded = true;
            }
            _wrs_modalWindow.open();
        }
    }

    /**
     * Converts all occurrences of mathml code to LATEX. The MathML code should containg <annotation encoding="LaTeX"/> to be converted.
     * @param {string} content A string containing MathML valid code.
     * @return {string} String with all MathML annotated occurrences replaced by the corresponding LaTeX code.
     * @ignore
     */
    function wrs_parseMathmlToLatex(content, characters){
        var output = '';
        var mathTagBegin = characters.tagOpener + 'math';
        var mathTagEnd = characters.tagOpener + '/math' + characters.tagCloser;
        var openTarget = characters.tagOpener + 'annotation encoding=' + characters.doubleQuote + 'LaTeX' + characters.doubleQuote + characters.tagCloser;
        var closeTarget = characters.tagOpener + '/annotation' + characters.tagCloser;
        var start = content.indexOf(mathTagBegin);
        var end = 0;
        var mathml, startAnnotation, closeAnnotation;

        while (start != -1) {
            output += content.substring(end, start);
            end = content.indexOf(mathTagEnd, start);

            if (end == -1) {
                end = content.length - 1;
            }
            else {
                end += mathTagEnd.length;
            }

            mathml = content.substring(start, end);

            startAnnotation = mathml.indexOf(openTarget);
            if (startAnnotation != -1){
                startAnnotation += openTarget.length;
                closeAnnotation = mathml.indexOf(closeTarget);
                var latex = mathml.substring(startAnnotation, closeAnnotation);
                if (characters == _wrs_safeXmlCharacters) {
                    latex = wrs_mathmlDecode(latex);
                }
                output += '$$' + latex + '$$';
                // Populate latex into cache.
                wrs_populateLatexCache(latex, mathml);
            }else{
                output += mathml;
            }

            start = content.indexOf(mathTagBegin, end);
        }

        output += content.substring(end, content.length);
        return output;
    }

    /**
     * Converts all occurrences of mathml code to the corresponding image.
     * @param {string} content An string with valid MathML code.
     * @param {object} characters An object containing xmlCharacters or safeXmlCharacters relation.
     * @param {string} language String containging a valid language code in order to generate formula accesibilty.
     * @return {string} The input string with all the MathML ocurrences replaced by the corresponding image.
     * @ignore
     */
    function wrs_parseMathmlToImg(content, characters, language) {
        var output = '';
        var mathTagBegin = characters.tagOpener + 'math';
        var mathTagEnd = characters.tagOpener + '/math' + characters.tagCloser;
        var start = content.indexOf(mathTagBegin);
        var end = 0;

        while (start != -1) {
            output += content.substring(end, start);
            // Avoid WIRIS images to be parsed.
            imageMathmlAtrribute = content.indexOf(_wrs_conf_imageMathmlAttribute);
            end = content.indexOf(mathTagEnd, start);

            if (end == -1) {
                end = content.length - 1;
            } else if (imageMathmlAtrribute != -1) {
                // First close tag of img attribute
                // If a mathmlAttribute exists should be inside a img tag.
                end += content.indexOf("/>", start);
            }
            else {
                end += mathTagEnd.length;
            }

            if (!wrs_isMathmlInAttribute(content, start) && imageMathmlAtrribute == -1){
                var mathml = content.substring(start, end);
                mathml = (characters == _wrs_safeXmlCharacters) ? wrs_mathmlDecode(mathml) : wrs_mathmlEntities(mathml);
                output += wrs_createObjectCode(wrs_mathmlToImgObject(document, mathml, null, language));
            }
            else {
                output += content.substring(start, end);
            }

            start = content.indexOf(mathTagBegin, end);
        }

        output += content.substring(end, content.length);
        return output;
    }

    /**
     * Converts all occurrences of safe applet code to the corresponding code.
     * @param {string} content String containging valid applet code <APPLET>...</APPLET>
     * @return {string} String with all the applet code conerted to safe tags.
     * @ignore
     */
    function wrs_parseSafeAppletsToObjects(content) {
        var output = '';
        var appletTagBegin = _wrs_safeXmlCharacters.tagOpener + 'APPLET';
        var appletTagEnd = _wrs_safeXmlCharacters.tagOpener + '/APPLET' + _wrs_safeXmlCharacters.tagCloser;
        var upperCaseContent = content.toUpperCase();
        var start = upperCaseContent.indexOf(appletTagBegin);
        var end = 0;
        var applet;

        while (start != -1) {
            output += content.substring(end, start);
            end = upperCaseContent.indexOf(appletTagEnd, start);

            if (end == -1) {
                end = content.length - 1;
            }
            else {
                end += appletTagEnd.length;
            }

            applet = wrs_convertOldXmlinitialtextAttribute(content.substring(start, end));

            output += wrs_mathmlDecode(applet);
            start = upperCaseContent.indexOf(appletTagBegin, end);
        }

        output += content.substring(end, content.length);
        return output;
    }

    /**
     * Cross-browser removeEventListener/detachEvent function.
     * @param {object} element Element target
     * @param {event} event Event
     * @param {function} func Function to run
     * @ignore
     */
    function wrs_removeEvent(element, event, func) {
        if (element.removeEventListener) {
            element.removeEventListener(event, func, true);
        }
        else if (element.detachEvent) {
            element.detachEvent('on' + event, func);
        }
    }

    /**
     * Splits an HTML content in three parts: the code before <body>, the code between <body> and </body> and the code after </body>.
     * @param {string} code HTML code to be splited.
     * @return {objet} An object with the structure {'prefix': xxx, 'code': yyy, 'sufix': zzz}
     * @ignore
     */
    function wrs_splitBody(code) {
        var prefix = '';
        var sufix = '';
        var bodyPosition = code.indexOf('<body');

        if (bodyPosition != -1) {
            bodyPosition = code.indexOf('>', bodyPosition);

            if (bodyPosition != -1) {
                ++bodyPosition;
                var endBodyPosition = code.indexOf('</body>', bodyPosition);

                if (endBodyPosition == -1) {
                    endBodyPosition = code.length;
                }

                prefix = code.substring(0, bodyPosition);
                sufix = code.substring(endBodyPosition, code.length);
                code = code.substring(bodyPosition, endBodyPosition);
            }
        }

        return {
            'prefix': prefix,
            'code': code,
            'sufix': sufix
        };
    }

    /**
     * Inserts or modifies CAS.
     * @param {object} focusElement Element to be focused
     * @param {object} windowTarget Window where the editable content is
     * @param {string} appletCode Applet code
     * @param {string} image Base 64 image stream
     * @param {int} imageWidth Image width
     * @param {int} imageHeight Image height
     * @ignore
     */
    function wrs_updateCAS(focusElement, windowTarget, appletCode, image, imageWidth, imageHeight) {
        var imgObject = wrs_appletCodeToImgObject(windowTarget.document, appletCode, image, imageWidth, imageHeight);
        wrs_insertElementOnSelection(imgObject, focusElement, windowTarget);
    }

    var wrs_PluginEvent = function () {
        this.cancelled = false;
        this.defaultPrevented = false;
    }

    wrs_PluginEvent.prototype.cancel = function () {
        this.cancelled = true;
    }

    wrs_PluginEvent.prototype.preventDefault = function () {
        this.defaultPrevented = true;
    }

    /**
     * Fires WIRIS plugin event listeners
     * @param  {String} eventName event name
     * @param  {Object} e         event properties
     * @return {bool}             false if event has been prevented.
     * @ignore
     */
    function wrs_fireEventListeners(eventName, e) {
        for (var i = 0; i < wrs_pluginListeners.length && !e.cancelled; ++i) {
            if (wrs_pluginListeners[i][eventName]) {
                // Calling listener.
                wrs_pluginListeners[i][eventName](e);
            }
        }

        return e.defaultPrevented;
    }

    /**
     * Inserts or modifies formulas.
     * @param {object} focusElement Element to be focused
     * @param {object} windowTarget Window where the editable content is
     * @param {string} mathml Mathml code
     * @param {object} wirisProperties Extra attributes for the formula (like background color or font size).
     * @param {string} editMode Current edit mode.
     * @param {string} language Language for the formula.
     * @ignore
     */
    function wrs_updateFormula(focusElement, windowTarget, mathml, wirisProperties, editMode, language) {
        // Before update listener.

        // Params on beforeUpdate listener
        // - mathml
        // - editMode (read only)
        // - wirisProperties
        // - language (read only).

        editMode = editMode !== null ? editMode : _wrs_editMode;
        var e = new wrs_PluginEvent();

        e.mathml = mathml;

        // Cloning wirisProperties object
        // We don't want wirisProperties object modified.
        e.wirisProperties = {};

        for (var attr in wirisProperties) {
            e.wirisProperties[attr] = wirisProperties[attr];
        }

        // Read only.
        e.language = language;
        e.editMode = editMode;

        if (wrs_fireEventListeners('onBeforeFormulaInsertion', e)) {
            return;
        }

        mathml = e.mathml;
        wirisProperties = e.wirisProperties;

        // Setting empty params for after.
        e = new wrs_PluginEvent();
        e.editMode = editMode;
        e.windowTarget = windowTarget;
        e.focusElement = focusElement;

        if (mathml.length == 0) {
            wrs_insertElementOnSelection(null, focusElement, windowTarget);
        }
        else if (editMode == 'latex') {
            e.latex = wrs_getLatexFromMathML(mathml);
            e.node = windowTarget.document.createTextNode('$$' + e.latex + '$$');
            wrs_populateLatexCache(e.latex, mathml);
            wrs_insertElementOnSelection(e.node, focusElement, windowTarget);
        }
        else if (editMode == 'iframes') {
            var iframe = wrs_mathmlToIframeObject(windowTarget, mathml);
            wrs_insertElementOnSelection(iframe, focusElement, windowTarget);
        }
        else {
            e.node = wrs_mathmlToImgObject(windowTarget.document, mathml, wirisProperties, language);
            wrs_insertElementOnSelection(e.node, focusElement, windowTarget);
        }

        if (wrs_fireEventListeners('onAfterFormulaInsertion', e)) {
            return;
        }
    }

    /**
     * Inserts or modifies formulas or CAS on a textarea.
     * @param {object} textarea Target
     * @param {string} text Text to add in the textarea. For example, if you want to add the link to the image, you can call this function as wrs_updateTextarea(textarea, wrs_createImageSrc(mathml));
     * @ignore
     */
    function wrs_updateTextarea(textarea, text) {
        if (textarea && text) {
            textarea.focus();

            if (textarea.selectionStart != null) {
                textarea.value = textarea.value.substring(0, textarea.selectionStart) + text + textarea.value.substring(textarea.selectionEnd, textarea.value.length);
            }
            else {
                var selection = document.selection.createRange();
                selection.text = text;
            }
        }
    }

    /**
     * URL decode function.
     * @param {string} input String to be decoded
     * @return {string} decode string.
     * @ignore
     */
    function wrs_urldecode(input) {
        return decodeURIComponent(input);
    }

    /**
     * URL encode function.
     * @param {string} clearString Input string to be encoded
     * @return {string} encoded string.
     * @ignore
     */
    function wrs_urlencode(clearString) {
        var output = '';
        // Method encodeURIComponent doesn't encode !'()*~ .
        output = encodeURIComponent(clearString);
        return output;
    }

    function wrs_addArgument(path,key,value) {
        if (path.indexOf("?") > 0) {
            sep = "&";
        } else {
            sep = "?";
        }
        return path + sep + key + "=" + value;
    }

    function wrs_urlToAssArray(url) {
        var i;
        i = url.indexOf("?");
        if (i > 0) {
            var query = url.substring(i + 1);
            var ss  = query.split("&");
            var h = new Object();
            for (i = 0; i < ss.length; i++) {
                var s = ss[i];
                var kv = s.split("=");
                if (kv.length > 1) {
                    h[kv[0]] = decodeURIComponent(kv[1].replace(/\+/g, ' '));
                }
            }
            return h;
        } else {
            return new Object();
        }
    }

    function wrs_setImgSize(img, url, json) {

        if (json) {
            // Cleaning data:image/png;base64.
            if (_wrs_conf_imageFormat == 'svg') {
                // SVG format.
                // If SVG is encoded in base64 we need to convert the base64 bytes into a SVG string.
                if (_wrs_conf_saveMode != 'base64') {
                    var ar = getMetricsFromSvgString(url);
                } else {
                    var base64String = img.src.substr( img.src.indexOf('base64,') + 7, img.src.length);
                    var svgString = '';
                    var bytes = wrs_b64ToByteArray(base64String, base64String.length);
                    for (var i = 0; i < bytes.length; i++) {
                        svgString += String.fromCharCode(bytes[i]);
                    }
                    var ar = getMetricsFromSvgString(svgString);
                }
                // PNG format: we store all metrics information in the first 88 bytes.
            } else {
                var base64String = img.src.substr( img.src.indexOf('base64,') + 7, img.src.length);
                var bytes = wrs_b64ToByteArray(base64String, 88);
                var ar = wrs_getMetricsFromBytes(bytes);
            }
            // Backwards compatibility: we store the metrics into createimage response.
        } else {
            var ar = wrs_urlToAssArray(url);
        }
        var width = ar['cw'];
        if (!width) {
            return;
        }
        var height = ar['ch'];
        var baseline = ar['cb'];
        var dpi = ar['dpi'];
        if (dpi) {
            width = width * 96 / dpi;
            height = height * 96 / dpi;
            baseline = baseline * 96 / dpi;
        }
        img.width = width;
        img.height = height;
        img.style.verticalAlign = "-" + (height - baseline) + "px";
    }

    function wrs_fixAfterResize(img) {
        img.removeAttribute('style');
        img.removeAttribute('width');
        img.removeAttribute('height');
        // In order to avoid resize with max-width css property.
        img.style.maxWidth = 'none';
        if (_wrs_conf_setSize) {
            if (img.src.indexOf("data:image") != -1) {
                if (_wrs_conf_imageFormat == 'svg') {
                    // ...data:image/svg+xml;charset=utf8, = 32.
                    var svg = wrs_urldecode(img.src.substring(32, img.src.length))
                    wrs_setImgSize(img, svg, true);
                } else {
                    // ...data:image/png;base64, == 22.
                    var base64 = img.src.substring(22,img.src.length);
                    wrs_setImgSize(img, base64, true);
                }
            } else {
                wrs_setImgSize(img,img.src);
            }
        }
    }

    function wrs_initSetSize() {
        // Override _wrs_conf_setSize to align formulas when xml or safeXml mode are enabled.
        _wrs_conf_setSize = _wrs_conf_setSize || _wrs_conf_saveMode == 'xml' || _wrs_conf_saveMode == 'safeXml' || (_wrs_conf_saveMode == 'base64' && _wrs_conf_editMode == 'default');
    }

    /**
     * Loads a set of global variables containing server configuration.
     * This method calls to configurationjs service, converting the response
     * JSON into javascript variables
     * @ignore
     */
    function wrs_loadConfiguration() {
        if (typeof _wrs_conf_path == 'undefined') {
            _wrs_conf_path = wrs_getCorePath();
        }

        var httpRequest = typeof XMLHttpRequest != 'undefined' ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
        var configUrl = _wrs_int_conf_file.indexOf("/") == 0 || _wrs_int_conf_file.indexOf("http") == 0 ? _wrs_int_conf_file : wrs_concatenateUrl(_wrs_conf_path, _wrs_int_conf_file);
        httpRequest.open('GET', configUrl, false);
        httpRequest.send(null);

        var jsonConfiguration = JSON.parse(httpRequest.responseText);

        // JSON structure: {{jsVariableName, jsVariableValue}}.

        var variables = Object.keys(jsonConfiguration);

        for (var variable in variables) {
            window[variables[variable]] = jsonConfiguration[variables[variable]];
        }

        // Services path (tech dependant).
        wrs_loadServicePaths(configUrl);

        // End configuration.
        _wrs_conf_configuration_loaded = true;
        if (typeof _wrs_conf_core_loaded != 'undefined') {
            _wrs_conf_plugin_loaded = true;
        }
    }

    function wrs_loadServicePaths(url) {
        // Services path (tech dependant).
        _wrs_conf_createimagePath = url.replace('configurationjs', 'createimage');
        _wrs_conf_showimagePath = url.replace('configurationjs', 'showimage');
        _wrs_conf_editorPath = url.replace('configurationjs', 'editor');
        _wrs_conf_CASPath = url.replace('configurationjs', 'cas');
        _wrs_conf_createcasimagePath = url.replace('configurationjs', 'createcasimage');
        _wrs_conf_getmathmlPath = url.replace('configurationjs', 'getmathml');
        _wrs_conf_servicePath = url.replace('configurationjs', 'service');
    }

    _wrs_conf_plugin_loaded = true;

    function wrs_getCorePath() {
        var scriptName = "core/core.js";
        var col = document.getElementsByTagName("script");
        for (var i = 0; i < col.length; i++) {
            var d;
            var src;
            d = col[i];
            src = d.src;
            var j = src.lastIndexOf(scriptName);
            if (j >= 0) {
                // That's my script!
                return src.substr(0, j - 1);
            }
        }
    }

    function wrs_loadLangFile() {
        // When a language is not defined, put english (en) as default.
        if (typeof _wrs_int_langCode == 'undefined' || _wrs_int_langCode == null) {
            _wrs_int_langCode = 'en';
        }

        langArray = _wrs_languages.split(',');

        if (langArray.indexOf(_wrs_int_langCode) == -1) {
            _wrs_int_langCode = _wrs_int_langCode.substr(0,2);
        }

        if (langArray.indexOf(_wrs_int_langCode) == -1) {
            _wrs_int_langCode = 'en';
        }

        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = wrs_getCorePath() + "/languages/" + _wrs_int_langCode + "/strings.js";
        document.getElementsByTagName('head')[0].appendChild(script);
    }

    function wrs_concatenateUrl(path1, path2) {
        var separator = "";
        if ((path1.indexOf("/") != path1.length) && (path2.indexOf("/") != 0)) {
            separator = "/";
        }
        return (path1 + separator + path2).replace(/([^:]\/)\/+/g, "$1");
    }

    // Loading javascript configuration.
    if (typeof _wrs_conf_configuration_loaded == 'undefined') {
        wrs_loadConfiguration();
    } else {
        var configUrl = _wrs_int_conf_file.indexOf("/") == 0 || _wrs_int_conf_file.indexOf("http") == 0 ? _wrs_int_conf_file : wrs_concatenateUrl(_wrs_conf_path, _wrs_int_conf_file);
        // If javascript configuration is loaded we need to load service paths manually.
        wrs_loadServicePaths(configUrl);
        _wrs_conf_plugin_loaded = true;
    }

    wrs_loadLangFile()

    /**
     * Create modal window with embebbed iframe
     *
     * @param {string} title Modal window title
     * @param {object} iframeParams iframe attributes
     * @param {object} deviceProperties device properties like orientation, OS..
     * @param {object} modalProperites modal properties (like draggable).
     * @ignore
     */

    function wrs_createModalWindow() {
        // Adding css stylesheet.
        if (!_wrs_css_loaded) {
            var fileref = document.createElement("link");
            fileref.setAttribute("rel", "stylesheet");
            fileref.setAttribute("type", "text/css");
            fileref.setAttribute("href", wrs_concatenateUrl(_wrs_conf_path, '/core/modal.css'));
            document.getElementsByTagName("head")[0].appendChild(fileref);
            _wrs_css_loaded = true;
        }

        _wrs_modalWindow.open();
    }

    /**
     * Closes modal window
     */
    function wrs_closeModalWindow() {
        // We avoid to close window when it's closed
        if(_wrs_modalWindow.isOpen()) {
            wrs_int_disableCustomEditors();
            wrs_int_notifyWindowClosed();
            _wrs_editMode = (window._wrs_conf_defaultEditMode) ? _wrs_conf_defaultEditMode : 'images';
            _wrs_modalWindow.close();
        }
    }

    /**
     * Create modal dialog for non mobile android devices.
     * @param  {modalDiv} modal overlay div.
     * @param  {containerDiv} modal window div.
     * @param  {iframe} embedded iframe.
     * @param  {iframeParams}  embedded iframe params (height, width).
     * @ignore
     */

    function wrs_createModalWindowAndroid() {
        _wrs_modalWindowProperties.device = 'android';
        wrs_addClass(_wrs_modalWindow.iframeContainer, 'wrs_modal_android');
        _wrs_modalWindow.overlayDiv.className = _wrs_modalWindow.overlayDiv.className + " wrs_modal_android";
        _wrs_modalWindow.containerDiv.className = _wrs_modalWindow.containerDiv.className + " wrs_modal_android";
        _wrs_modalWindow.iframe.className = _wrs_modalWindow.iframe.className + " wrs_modal_android";
    }

    /**
     * Create modal dialog for non mobile iOS devices.
     * @param  {modalDiv} modal overlay div.
     * @param  {containerDiv} modal window div.
     * @param  {iframe} embedded iframe.
     * @param  {iframeParams}  embedded iframe params (height, width).
     * @ignore
     */
    function wrs_createModalWindowIos() {
        wrs_addClass(iframeContainer, 'wrs_modal_ios');
        _wrs_modalWindow.overlayDiv.className = _wrs_modalWindow.overlayDiv.className + " wrs_modal_ios";
        if (typeof _wrs_isMoodle24 != 'undefined') {
            _wrs_modalWindow.overlayDiv.className = _wrs_modalWindow.overlayDiv.className + " moodle";
        }

        _wrs_modalWindow.containerDiv.className = _wrs_modalWindow.containerDiv.className + " wrs_modal_ios";
        _wrs_modalWindow.iframe.className = _wrs_modalWindow.iframe.className + " wrs_modal_ios";
    }

    /**
     * Create modal dialog for mobile devices.
     *
     * @param  {modalDiv} modal overlay div.
     * @param  {containerDiv} modal window div.
     * @param  {iframe} embedded iframe.
     * @param  {iframeParams}  embedded iframe params (height, width).
     * @ignore
     */

    function wrs_createModalWindowMobile(modalDiv, containerDiv, iframe, iframeParams, iframeContainer) {

        wrs_addClass(_wrs_modalWindow.iframeContainer, 'wrs_modal_mobile');
        _wrs_modalWindow.overlayDiv.className = _wrs_modalWindow.overlayDiv.className + " wrs_modal_mobile";
        _wrs_modalWindow.containerDiv.className = _wrs_modalWindow.containerDiv.className + " wrs_modal_mobile";
        _wrs_modalWindow.iframe.className = _wrs_modalWindow.iframe.className + " wrs_modal_mobile";

        wrs_addMetaViewport("device-width", 1.0, 1.0, 1.0);

        var modalTitleBar = document.getElementsByClassName('wrs_modal_title')[0]

        if (modalTitleBar) {
            document.removeChild(modalTitleBar);
        }
    }

    /**
     * Create modal dialog for Androir mobile devices with an old stock browser (<=4.3).
     *
     * @param  {modalDiv} modal overlay div.
     * @param  {containerDiv} modal window div.
     * @param  {iframe} embedded iframe.
     * @param  {iframeParams}  embedded iframe params (height, width).
     * @ignore
     */
    function wrs_createModalWindowBadStockAndroid(modalDiv, containerDiv, iframe, iframeParams) {
        _wrs_modalWindow.overlayDiv.className = _wrs_modalWindow.overlayDiv.className + " wrs_modal_badStock";
        _wrs_modalWindow.containerDiv.className = _wrs_modalWindow.containerDiv.className + " wrs_modal_badStock";
        _wrs_modalWindow.iframe.className = _wrs_modalWindow.iframe.className + " wrs_modal_badStock";

        if (window.outerWidth < parseInt(_wrs_modalWindowProperties.iframeAttributes['width'])) {
            var modalWidth = parseInt(_wrs_modalWindowProperties.iframeAttributes['width']) + 10;
            _wrs_modalWindow.containerDiv.style.width = _wrs_modalWindowProperties.iframeAttributes['width'] + 'px';
            _wrs_modalWindow.iframe.style.width = _wrs_modalWindowProperties.iframeAttributes['width'] + 'px';
        }

        window.addEventListener('orientationchange', function() {
            if (window.outerWidth > parseInt(_wrs_modalWindowProperties.iframeAttributes['width']) + 10) {
                var modalWidth = parseInt(_wrs_modalWindowProperties.iframeAttributes['width']) + 10;
                _wrs_modalWindow.containerDiv.style.width = modalWidth + 'px';
                _wrs_modalWindow.iframe.style.width = _wrs_modalWindowProperties.iframeAttributes['width'] + 'px';
            } else {
                _wrs_modalWindow.containerDiv.style.width = null;
                _wrs_modalWindow.iframe.style.width = null;
            }
        });

        wrs_addMetaViewport("device-width", 1.0, 1.0, 1.0);
    }

    /**
     * Add viewport header for scale control.
     *
     * @param {int} width  width of the layout viewport.
     * @param {int} initialScale Sets the initial zoom of the page and the width of the layout viewport.
     * @param {int} minimumScale Sets the minimum zoom level (i.e. how much the user can zoom out).
     * @param {int} maximumScale Sets the maximum zoom level (i.e. how much the user can zoom in).
     * @ignore
     */
    function wrs_addMetaViewport(width, initialScale, minimumScale, maximumScale) {
        _wrs_originalMetaViewport = document.querySelector('meta[name=viewport]') ? document.querySelector('meta[name=viewport]').content : null;
        if (_wrs_originalMetaViewport) {
            document.querySelector('meta[name=viewport]').content = "width=" + width + ", initial-scale=" + initialScale + ", minimum-scale=" + minimumScale + ", maximum-scale=" + maximumScale;
        } else {
            var attributes = {};
            attributes['name'] = 'viewport';
            attributes['content'] = "width=" + width + ", initial-scale=" + initialScale + ", minimum-scale=" + minimumScale + ", maximum-scale=" + maximumScale;
            var meta = wrs_createElement('meta', attributes);
            document.getElementsByTagName("head")[0].appendChild(meta);
        }
    }

    /**
     * Android stock browser test
     * http://stackoverflow.com/questions/24926221/distinguish-android-chrome-from-stock-browser-stock-browsers-user-agent-contai
     *
     * @return {Boolean} true if user agent is from an Android stock browser (<= 4.3)
     * @ignore
     */
    function wrs_isBadStockAndroid () {
        var userAgent = window.navigator.userAgent;
        // Android stock browser test derived from
        // http://stackoverflow.com/questions/24926221/distinguish-android-chrome-from-stock-browser-stock-browsers-user-agent-contai.
        var isAndroid = userAgent.indexOf(' Android ') > -1;
        if (!isAndroid) {
            return false;
        }

        var isStockAndroid = userAgent.indexOf('Version/') > -1;
        if (!isStockAndroid) {
            return false;
        }

        var versionNumber = parseFloat((userAgent.match('Android ([0-9.]+)') || [])[1]);
        // Anything below 4.4 uses WebKit without *any* viewport support.
        return versionNumber <= 4.3;
    }

    /**
     * Populates LaTeX cache into _wrs_int_LatexCache global variable.
     *
     * @param {string}latex LaTeX code (with $$ separators)
     * @param {string} mathml matml LaTeX translation.
     * @ignore
     */
    function wrs_populateLatexCache(latex, mathml) {
        if (mathml.indexOf('semantics') == -1 && mathml.indexOf('annotation') == -1 ) {
            mathml = wrs_insertSemanticsMathml(mathml, latex);
        }
        if (!_wrs_int_LatexCache.hasOwnProperty(latex)) {
            _wrs_int_LatexCache[latex] = mathml;
        }
    }

    /**
     * Puts into _wrs_int_AccessibleCache global variable dictionary the pair mathml=>accessibleText.
     *
     * @param {string} mathml MatML text.
     * @param {string} accessibleText Image accessible text
     * @ignore
     */
    function wrs_populateAccessibleCache(mathml, accessibleText) {
        if (!_wrs_int_AccessibleCache.hasOwnProperty(mathml)) {
            _wrs_int_AccessibleCache[mathml] = accessibleText;
        }
    }

    /**
     * Add annotation tag to mathml without it (mathml comes from LaTeX string)
     * @param  {string} mathml MathML code generated by a LaTeX string.
     * @param  {string} latex Original LaTeX string
     * @return {string} new mathml containing LaTeX code on annotation tag.
     * @ignore
     */
    function wrs_insertSemanticsMathml(mathml, latex) {

        var firstEndTag = '>';
        var mathTagEnd = '<' + '/math' + '>';
        var openSemantics = '<' + 'semantics' + '>';
        var closeSemantics = '<' + '/semantics' + '>';
        var openTarget = '<annotation encoding="LaTeX">';
        var closeTarget = '<' + '/annotation' + '>';
        var mrowOpen = '<mrow>';
        var mrowClose = '</mrow>';

        var indexMathBegin = mathml.indexOf(firstEndTag);
        var indexMathEnd = mathml.indexOf(mathTagEnd);
        var mathBeginExists = mathml.substring(mathml.indexOf('<'), mathml.indexOf('>')).indexOf('math');

        if (indexMathBegin != -1 && indexMathEnd != -1 && mathBeginExists)  {
            var mathmlContent = mathml.substring(indexMathBegin + 1, indexMathEnd);
            if (mathmlContent.indexOf(mrowOpen) != 0) {
                var mathmlContentSemantics = openSemantics + mrowOpen + mathmlContent + mrowClose + openTarget + latex + closeTarget + closeSemantics;
            } else {
                var mathmlContentSemantics = openSemantics + mathmlContent + openTarget + latex + closeTarget + closeSemantics;
            }
            return mathml.replace(mathmlContent, mathmlContentSemantics);
        } else {
            return mathml;
        }

    }

    /**
     * Transform html img tags inside a html code to mathml, base64 img tags (i.e with base64 on src) or showimage img tags (i.e with showimage.php on src)
     *
     * @param  {String} code html code
     * @param  {String} mode base642showimage or img2mathml or img264 transform.
     * @return {String} html code transformed.
     * @ignore
     */
    function wrs_codeImgTransform(code, mode) {
        output = '';

        var endPosition = 0;
        var pattern = /<img/gi;
        var patternLength = pattern.source.length;

        while (pattern.test(code)) {
            var startPosition = pattern.lastIndex - patternLength;
            output += code.substring(endPosition, startPosition);

            var i = startPosition + 1;

            while (i < code.length && endPosition <= startPosition) {
                var character = code.charAt(i);

                if (character == '"' || character == '\'') {
                    var characterNextPosition = code.indexOf(character, i + 1);

                    if (characterNextPosition == -1) {
                        i = code.length;        // End while.
                    }
                    else {
                        i = characterNextPosition;
                    }
                }
                else if (character == '>') {
                    endPosition = i + 1;
                }

                ++i;
            }

            if (endPosition < startPosition) {      // The img tag is stripped.
                output += code.substring(startPosition, code.length);
                return output;
            }
            var imgCode = code.substring(startPosition, endPosition);
            var imgObject = wrs_createObject(imgCode);
            var xmlCode = imgObject.getAttribute(_wrs_conf_imageMathmlAttribute);

            if (mode == 'base642showimage') {
                if (xmlCode == null) {
                    xmlCode = imgObject.getAttribute('alt');
                }
                xmlCode = wrs_mathmlDecode(xmlCode);
                imgCode = wrs_mathmlToImgObject(document, xmlCode, null, null);
                output += wrs_createObjectCode(imgCode);
            } else if (mode == 'img2mathml') {
                if (window._wrs_conf_saveMode) {
                    if (_wrs_conf_saveMode == 'safeXml') {
                        convertToXml = true;
                        convertToSafeXml = true;
                    }
                    else if (_wrs_conf_saveMode == 'xml') {
                        convertToXml = true;
                        convertToSafeXml = false;
                    }
                }
                output += wrs_getWIRISImageOutput(imgCode, convertToXml, convertToSafeXml);
            } else if (mode == 'img264') {

                if (xmlCode == null) {
                    xmlCode = imgObject.getAttribute('alt');
                }
                xmlCode = wrs_mathmlDecode(xmlCode);

                var properties = {};
                properties['base64'] = 'true';
                imgCode = wrs_mathmlToImgObject(document, xmlCode, properties, null)
                // Metrics.
                wrs_setImgSize(imgCode, imgCode.src, true);

                output += wrs_createObjectCode(imgCode);
            }
        }
        output += code.substring(endPosition, code.length);
        return output;
    }

    /**
     * Decode a base64 to its numeric value
     *
     * @param  {String} el base64 character.
     * @return {int} base64 char numeric value.
     * @ignore
     */
    function wrs_decode64(el) {

        var PLUS = '+'.charCodeAt(0);
        var SLASH = '/'.charCodeAt(0);
        var NUMBER = '0'.charCodeAt(0);
        var LOWER = 'a'.charCodeAt(0);
        var UPPER = 'A'.charCodeAt(0);
        var PLUS_URL_SAFE = '-'.charCodeAt(0);
        var SLASH_URL_SAFE = '_'.charCodeAt(0);
        var code = el.charCodeAt(0);

        if (code === PLUS || code === PLUS_URL_SAFE) {
            return 62; // Char '+'.
        }
        if (code === SLASH || code === SLASH_URL_SAFE){
            return 63 // Char '/'.
        }
        if (code < NUMBER){
            return -1 // No match.
        }
        if (code < NUMBER + 10){
            return code - NUMBER + 26 + 26
        }
        if (code < UPPER + 26){
            return code - UPPER
        }
        if (code < LOWER + 26){
            return code - LOWER + 26
        }
    }

    /**
     * Converts a base64 string to a array of bytes.
     * @param  {String} b64String base64 string.
     * @param  {int} len dimension of byte array (by default whole string).
     * @return {Array} Byte array.
     * @ignore
     */
    function wrs_b64ToByteArray(b64String, len) {

        var tmp;

        if (b64String.length % 4 > 0) {
            throw new Error('Invalid string. Length must be a multiple of 4'); // Tipped base64. Length is fixed.
        }

        var arr = new Array()

        if (!len) { // All b64String string.
            var placeHolders = b64String.charAt(b64String.length - 2) === '=' ? 2 : b64String.charAt(b64String.length - 1) === '=' ? 1 : 0
            var l = placeHolders > 0 ? b64String.length - 4 : b64String.length;
        } else {
            var l = len;
        }

        for (var i = 0; i < l; i += 4) {
            // Ignoring code checker standards (bitewise operators).
            // See https://tracker.moodle.org/browse/CONTRIB-5862 for further information.
            // @codingStandardsIgnoreStart
            tmp = (wrs_decode64(b64String.charAt(i)) << 18) | (wrs_decode64(b64String.charAt(i + 1)) << 12) | (wrs_decode64(b64String.charAt(i + 2)) << 6) | wrs_decode64(b64String.charAt(i + 3));

            arr.push((tmp  >> 16) & 0xFF);
            arr.push((tmp >> 8) & 0xFF);
            arr.push(tmp & 0xFF);
            // @codingStandardsIgnoreEnd
        }

        if (placeHolders) {
            if (placeHolders === 2) {
                // Ignoring code checker standards (bitewise operators).
                // @codingStandardsIgnoreStart
                tmp = (wrs_decode64(b64String.charAt(i)) << 2) | (wrs_decode64(b64String.charAt(i + 1)) >> 4);
                arr.push(tmp & 0xFF)
            } else if (placeHolders === 1) {
                tmp = (wrs_decode64(b64String.charAt(i)) << 10) | (wrs_decode64(b64String.charAt(i + 1)) << 4) | (wrs_decode64(b64String.charAt(i + 2)) >> 2)
                arr.push((tmp >> 8) & 0xFF);
                arr.push(tmp & 0xFF);
                // @codingStandardsIgnoreEnd
            }
        }

        return arr
    }

    /**
     * Returns the first 32-bit signed integer from a byte array.
     * @param  {Array} bytes array of bytes.
     * @return {int} 32-bit signed integer.
     * @ignore
     */
    function wrs_readInt32(bytes) {
        if (bytes.length < 4) {
            return false;
        }
        var int32 = bytes.splice(0,4);
        // @codingStandardsIgnoreStart
        return (int32[0] << 24 | int32[1] << 16 | int32[2] <<  8 | int32[3] << 0);
        // @codingStandardsIgnoreEnd
    }

    /**
     * Read the first byte from a byte array.
     * @param  {array} bytes byte array.
     * @return {int} first byte of the byte array.
     * @ignore
     */
    function wrs_readByte(bytes) {
        // @codingStandardsIgnoreStart
        return bytes.shift() << 0;
        // @codingStandardsIgnoreEnd

    }

    /**
     * Read an arbitrary number of bytes, from a fixed position on a byte array.
     * @param  {array} bytes byte array.
     * @param  {int} post start position.
     * @param  {int} len number of bytes to read.
     * @return {array} byte array.
     * @ignore
     */
    function wrs_readBytes(bytes, pos, len) {
        return bytes.splice(pos, len);
    }

    /**
     * Get metrics (width, height, baseline and dpi) from a png's byte array.
     * @param  {array} bytes png byte array.
     * @return {array} An array containging the png's metrics.
     * @ignore
     */
    function wrs_getMetricsFromBytes(bytes) {
        wrs_readBytes(bytes, 0, 8);
        alloc = 10;
        i = 0;
        while (bytes.length >= 4) {
            len = wrs_readInt32(bytes);
            typ = wrs_readInt32(bytes);
            if (typ == 0x49484452) {
                width = wrs_readInt32(bytes);
                height = wrs_readInt32(bytes);
                // Read 5 bytes.
                wrs_readInt32(bytes);
                wrs_readByte(bytes);
            } else if (typ == 0x62615345) { // Baseline: 'baSE'.
                baseline = wrs_readInt32(bytes);
            } else if (typ == 0x70485973) { // Dpis: 'pHYs'.
                dpi = wrs_readInt32(bytes);
                dpi = (Math.round(dpi / 39.37));
                wrs_readInt32(bytes);
                wrs_readByte(bytes);
            }
            wrs_readInt32(bytes);
        }

        if (typeof width != 'undefined') {
            var arr = new Array();
            arr['cw'] = width;
            arr['ch'] = height;
            arr['dpi'] = dpi;
            if (baseline) {
                arr['cb'] = baseline;
            }

            return arr;
        }
    }

    function getMetricsFromSvgString(svgString) {
        var first = svgString.indexOf('height="');
        var last = svgString.indexOf('"',first + 8, svgString.length);
        var height = svgString.substring(first + 8, last);

        first = svgString.indexOf('width="');
        last = svgString.indexOf('"',first + 7, svgString.length);
        var width = svgString.substring(first + 7, last);

        first = svgString.indexOf('wrs:baseline="');
        last = svgString.indexOf('"',first + 14, svgString.length);
        var baseline = svgString.substring(first + 14, last);

        if (typeof(width != 'undefined')) {
            var arr = new Array();
            arr['cw'] = width;
            arr['ch'] = height;
            if (typeof baseline != 'undefined') {
                arr['cb'] = baseline
            }

            return arr;
        }

    }

    /**
     * Get custom active editor
     * @ignore
     */
    function wrs_int_getCustomEditorEnabled() {
        var customEditorEnabled = null;
        for (var key in _wrs_int_customEditors) {
            if (_wrs_int_customEditors[key].enabled) {
                customEditorEnabled = _wrs_int_customEditors[key];
                break;
            }
        }

        return customEditorEnabled;
    }

    /**
     * Disable all custom editors
     * @ignore
     */
    function wrs_int_disableCustomEditors(){
        Object.keys(_wrs_int_customEditors).forEach(function(key) {
                _wrs_int_customEditors[key].enabled = false;
        });
    }

    /**
     * Enable a custom editor
     * @param {string} editor a custom editor to be enabled
     * @ignore
     */
    function wrs_int_enableCustomEditor(editor) {
        // Only one custom editor enabled at the same time.
        wrs_int_disableCustomEditors();
        if (_wrs_int_customEditors[editor]) {
            _wrs_int_customEditors[editor].enabled = true;
        }
    }

    /**
     * Convert a hash to string  sorting keys to get a deterministic output
     * @param {hash} h a key-value hash
     * @return{string} A string with the form key1=value1...keyn=valuen
     * @ignore
     */
    function wrs_propertiesToString(h) {
        // 1. Sort keys. We sort the keys because we want a deterministic output.
        var keys = []
        for (var key in h) {
            if (h.hasOwnProperty(key)) {
                keys.push(key);
            }
        }

        var n = keys.length;
        for (var i = 0; i < n; i++) {
            for (var j = i + 1; j < n; j++) {
                var s1 = keys[i];
                var s2 = keys[j];
                if (wrs_compareStrings(s1,s2) > 0) {
                    // Swap.
                    keys[i] = s2;
                    keys[j] = s1;
                }
            }
        }

        // 2. Generate output.
        var output = '';
        for (var i = 0; i < n; i++) {
            var key = keys[i];
            output += key;
            output += "=";
            var value = h[key];
            value = value.replace("\\", "\\\\");
            value = value.replace("\n", "\\n");
            value = value.replace("\r", "\\r");
            value = value.replace("\t", "\\t");

            output += value;
            output += "\n";
        }
        return output;
    }

    /**
     * Compare two strings using charCodeAt method
     * @param {string} a first string to compare.
     * @param {string} b second string to compare
     * @return {int} the int difference between a and b
     * @ignore
     */
    function wrs_compareStrings(a, b){
        var i;
        var an = a.length;
        var bn = b.length;
        var n = (an > bn) ? bn : an;
        for(i = 0; i < n; i++){
            var c = wrs_fixedCharCodeAt(a,i) - wrs_fixedCharCodeAt(b,i);
            if(c != 0) {
                return c;
            }
        }
            return a.length - b.length;
    }

    // Polyfills.

    if (!Object.keys) {
        Object.keys = (function () {
            'use strict';
            var hasOwnProperty = Object.prototype.hasOwnProperty,
            hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
            dontEnums = [
              'toString',
              'toLocaleString',
              'valueOf',
              'hasOwnProperty',
              'isPrototypeOf',
              'propertyIsEnumerable',
              'constructor'
            ],
            dontEnumsLength = dontEnums.length;

            return function (obj) {
                if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
                    throw new TypeError('Object.keys called on non-object');
                }

                var result = [], prop, i;

                for (prop in obj) {
                    if (hasOwnProperty.call(obj, prop)) {
                        result.push(prop);
                    }
                }

                if (hasDontEnumBug) {
                    for (i = 0; i < dontEnumsLength; i++) {
                        if (hasOwnProperty.call(obj, dontEnums[i])) {
                            result.push(dontEnums[i]);
                        }
                    }
                }
                return result;
            };
        }());
    }

    /**
     * Add a new callback to a WIRIS plugins listener.
     * @param {object} listener an Object containing listener name and a callback.
     * @ignore
     */
    function wrs_addPluginListener(listener) {
        wrs_pluginListeners.push(listener);
    }

    /**
     * For now its not possible comunicate directly between editor.js and ModalWindow object.
     * We need to use this method to call ModalWindow prototype from editor.js
     * @param  {object} editor WIRIS Editor
     * @ignore
     */
    function wrs_setModalWindowEditor(editor) {
        if (_wrs_conf_modalWindow) {
            _wrs_modalWindow.setEditor(editor);
        }
    }

    /**
     * Get the base URL (i.e the URL on core.js lives).
     */
    function wrs_getServerPath() {
        url = wrs_getCorePath();
        var hostNameIndex = url.indexOf("/", url.indexOf("/") + 2);
        return url.substr(0, hostNameIndex);
    }

    /**
     * This method updates all services paths if there are relative with the absolute
     * URL path.
     * @ignore
     */
    function wrs_updateContextPath() {
        if (typeof _wrs_conf_plugin_loaded == 'undefined') {
                setTimeout(wrs_updateContextPath, 100);
        } else {
            if (_wrs_conf_showimagePath.indexOf("/") == 0) {
                serverPath = wrs_getServerPath()
                _wrs_conf_showimagePath = serverPath + _wrs_conf_showimagePath;
                _wrs_conf_editorPath = serverPath + _wrs_conf_editorPath;
                _wrs_conf_CASPath = serverPath + _wrs_conf_CASPath;
                _wrs_conf_createimagePath = serverPath + _wrs_conf_createimagePath;
                _wrs_conf_createcasimagePath = serverPath + _wrs_conf_createcasimagePath;
                _wrs_conf_getmathmlPath = serverPath + _wrs_conf_getmathmlPath;
                _wrs_conf_servicePath = serverPath + _wrs_conf_servicePath;
            }
        }
    }

    wrs_updateContextPath();

    // Production steps of ECMA-262, Edition 5, 15.4.4.18
    // Reference: http://es5.github.io/#x15.4.4.18.
    if (!Array.prototype.forEach) {

        Array.prototype.forEach = function(callback, thisArg) {

            var T, k;

            if (this == null) {
                throw new TypeError(' this is null or not defined');
            }

            // 1. Let O be the result of calling ToObject passing the |this| value as the argument.
            var O = Object(this);

            // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
            // 3. Let len be ToUint32(lenValue).

            // @codingStandardsIgnoreStart
            var len = O.length >>> 0;
            // @codingStandardsIgnoreEnd

            // 4. If IsCallable(callback) is false, throw a TypeError exception.
            // See: http://es5.github.com/#x9.11 .
            if (typeof callback !== "function") {
                throw new TypeError(callback + ' is not a function');
            }

            // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
            if (arguments.length > 1) {
                T = thisArg;
            }

            // 6. Let k be 0.
            k = 0;

            // 7. Repeat, while k < len.
            while (k < len) {

                var kValue;

                // A. Let Pk be ToString(k).
                    // This is implicit for LHS operands of the in operator
                // B. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk
                    // This step can be combined with c
                // C. If kPresent is true.
                if (k in O) {

                    // I. Let kValue be the result of calling the Get internal method of O with argument Pk.
                    kValue = O[k];

                    // II. Call the Call internal method of callback with T as the this value and
                    // argument list containing kValue, k, and O.
                    callback.call(T, kValue, k, O);
                }
                // D. Increase k by 1.
                k++;
            }
            // 8. return undefined.
        };
    }
    /**
     * EditorListener constructor
     * @ignore
     */
    function EditorListener(){
        this.isContentChanged = false;
        this.waitingForChanges = false;
    }
    /**
     * EditorListener method set if content is changed
     * @ignore
     */
    EditorListener.prototype.setIsContentChanged = function(value){
        this.isContentChanged = value;
    }
    /**
     * EditorListener method to get if content is changed
     * @ignore
     */
    EditorListener.prototype.getIsContentChanged = function(value){
        return this.isContentChanged;
    }
    /**
     * EditorListener method to wait changes
     * @ignore
     */
    EditorListener.prototype.setWaitingForChanges = function(value){
        this.waitingForChanges = value;
    }
    /**
     * EditorListener method to overwrite
     * @ignore
     */
    EditorListener.prototype.caretPositionChanged = function(editor){};
    /**
     * EditorListener method to overwrite
     * @ignore
     */
    EditorListener.prototype.clipboardChanged = function(editor){};
    /**
     * EditorListener method to set if content is changed
     * @ignore
     */
    EditorListener.prototype.contentChanged = function(editor){
        if(this.waitingForChanges === true && this.isContentChanged === false){
            this.isContentChanged = true;
        }
    }
    /**
     * EditorListener method to overwrite
     * @ignore
     */
    EditorListener.prototype.styleChanged = function(editor){}
    /**
     * EditorListener method to overwrite
     * @ignore
     */
    EditorListener.prototype.transformationReceived = function(editor){}
    // @codingStandardsIgnoreStart
    (function(){
    var HxOverrides = function() { }
    HxOverrides.__name__ = true;
    HxOverrides.dateStr = function(date) {
    	var m = date.getMonth() + 1;
    	var d = date.getDate();
    	var h = date.getHours();
    	var mi = date.getMinutes();
    	var s = date.getSeconds();
    	return date.getFullYear() + "-" + (m < 10?"0" + m:"" + m) + "-" + (d < 10?"0" + d:"" + d) + " " + (h < 10?"0" + h:"" + h) + ":" + (mi < 10?"0" + mi:"" + mi) + ":" + (s < 10?"0" + s:"" + s);
    }
    HxOverrides.strDate = function(s) {
    	switch(s.length) {
    	case 8:
    		var k = s.split(":");
    		var d = new Date();
    		d.setTime(0);
    		d.setUTCHours(k[0]);
    		d.setUTCMinutes(k[1]);
    		d.setUTCSeconds(k[2]);
    		return d;
    	case 10:
    		var k = s.split("-");
    		return new Date(k[0],k[1] - 1,k[2],0,0,0);
    	case 19:
    		var k = s.split(" ");
    		var y = k[0].split("-");
    		var t = k[1].split(":");
    		return new Date(y[0],y[1] - 1,y[2],t[0],t[1],t[2]);
    	default:
    		throw "Invalid date format : " + s;
    	}
    }
    HxOverrides.cca = function(s,index) {
    	var x = s.charCodeAt(index);
    	if(x != x) return undefined;
    	return x;
    }
    HxOverrides.substr = function(s,pos,len) {
    	if(pos != null && pos != 0 && len != null && len < 0) return "";
    	if(len == null) len = s.length;
    	if(pos < 0) {
    		pos = s.length + pos;
    		if(pos < 0) pos = 0;
    	} else if(len < 0) len = s.length + len - pos;
    	return s.substr(pos,len);
    }
    HxOverrides.remove = function(a,obj) {
    	var i = 0;
    	var l = a.length;
    	while(i < l) {
    		if(a[i] == obj) {
    			a.splice(i,1);
    			return true;
    		}
    		i++;
    	}
    	return false;
    }
    HxOverrides.iter = function(a) {
    	return { cur : 0, arr : a, hasNext : function() {
    		return this.cur < this.arr.length;
    	}, next : function() {
    		return this.arr[this.cur++];
    	}};
    }
    var IntIter = function(min,max) {
    	this.min = min;
    	this.max = max;
    };
    IntIter.__name__ = true;
    IntIter.prototype = {
    	next: function() {
    		return this.min++;
    	}
    	,hasNext: function() {
    		return this.min < this.max;
    	}
    	,__class__: IntIter
    }
    var Std = function() { }
    Std.__name__ = true;
    Std["is"] = function(v,t) {
    	return js.Boot.__instanceof(v,t);
    }
    Std.string = function(s) {
    	return js.Boot.__string_rec(s,"");
    }
    Std["int"] = function(x) {
    	return x | 0;
    }
    Std.parseInt = function(x) {
    	var v = parseInt(x,10);
    	if(v == 0 && (HxOverrides.cca(x,1) == 120 || HxOverrides.cca(x,1) == 88)) v = parseInt(x);
    	if(isNaN(v)) return null;
    	return v;
    }
    Std.parseFloat = function(x) {
    	return parseFloat(x);
    }
    Std.random = function(x) {
    	return Math.floor(Math.random() * x);
    }
    var com = com || {}
    if(!com.wiris) com.wiris = {}
    if(!com.wiris.js) com.wiris.js = {}
    com.wiris.js.JsPluginTools = function() {
    	this.tryReady();
    };
    com.wiris.js.JsPluginTools.__name__ = true;
    com.wiris.js.JsPluginTools.main = function() {
    	var ev;
    	ev = com.wiris.js.JsPluginTools.getInstance();
    	haxe.Timer.delay($bind(ev,ev.tryReady),100);
    }
    com.wiris.js.JsPluginTools.getInstance = function() {
    	if(com.wiris.js.JsPluginTools.instance == null) com.wiris.js.JsPluginTools.instance = new com.wiris.js.JsPluginTools();
    	return com.wiris.js.JsPluginTools.instance;
    }
    com.wiris.js.JsPluginTools.bypassEncapsulation = function() {
    	if(window.com == null) window.com = { };
    	if(window.com.wiris == null) window.com.wiris = { };
    	if(window.com.wiris.js == null) window.com.wiris.js = { };
    	if(window.com.wiris.js.JsPluginTools == null) window.com.wiris.js.JsPluginTools = com.wiris.js.JsPluginTools.getInstance();
    }
    com.wiris.js.JsPluginTools.prototype = {
    	md5encode: function(content) {
    		return haxe.Md5.encode(content);
    	}
    	,doLoad: function() {
    		this.ready = true;
    		com.wiris.js.JsPluginTools.instance = this;
    		com.wiris.js.JsPluginTools.bypassEncapsulation();
    	}
    	,tryReady: function() {
    		this.ready = false;
    		if(js.Lib.document.readyState) {
    			this.doLoad();
    			this.ready = true;
    		}
    		if(!this.ready) haxe.Timer.delay($bind(this,this.tryReady),100);
    	}
    	,__class__: com.wiris.js.JsPluginTools
    }
    var haxe = haxe || {}
    haxe.Log = function() { }
    haxe.Log.__name__ = true;
    haxe.Log.trace = function(v,infos) {
    	js.Boot.__trace(v,infos);
    }
    haxe.Log.clear = function() {
    	js.Boot.__clear_trace();
    }
    haxe.Md5 = function() {
    };
    haxe.Md5.__name__ = true;
    haxe.Md5.encode = function(s) {
    	return new haxe.Md5().doEncode(s);
    }
    haxe.Md5.prototype = {
    	doEncode: function(str) {
    		var x = this.str2blks(str);
    		var a = 1732584193;
    		var b = -271733879;
    		var c = -1732584194;
    		var d = 271733878;
    		var step;
    		var i = 0;
    		while(i < x.length) {
    			var olda = a;
    			var oldb = b;
    			var oldc = c;
    			var oldd = d;
    			step = 0;
    			a = this.ff(a,b,c,d,x[i],7,-680876936);
    			d = this.ff(d,a,b,c,x[i + 1],12,-389564586);
    			c = this.ff(c,d,a,b,x[i + 2],17,606105819);
    			b = this.ff(b,c,d,a,x[i + 3],22,-1044525330);
    			a = this.ff(a,b,c,d,x[i + 4],7,-176418897);
    			d = this.ff(d,a,b,c,x[i + 5],12,1200080426);
    			c = this.ff(c,d,a,b,x[i + 6],17,-1473231341);
    			b = this.ff(b,c,d,a,x[i + 7],22,-45705983);
    			a = this.ff(a,b,c,d,x[i + 8],7,1770035416);
    			d = this.ff(d,a,b,c,x[i + 9],12,-1958414417);
    			c = this.ff(c,d,a,b,x[i + 10],17,-42063);
    			b = this.ff(b,c,d,a,x[i + 11],22,-1990404162);
    			a = this.ff(a,b,c,d,x[i + 12],7,1804603682);
    			d = this.ff(d,a,b,c,x[i + 13],12,-40341101);
    			c = this.ff(c,d,a,b,x[i + 14],17,-1502002290);
    			b = this.ff(b,c,d,a,x[i + 15],22,1236535329);
    			a = this.gg(a,b,c,d,x[i + 1],5,-165796510);
    			d = this.gg(d,a,b,c,x[i + 6],9,-1069501632);
    			c = this.gg(c,d,a,b,x[i + 11],14,643717713);
    			b = this.gg(b,c,d,a,x[i],20,-373897302);
    			a = this.gg(a,b,c,d,x[i + 5],5,-701558691);
    			d = this.gg(d,a,b,c,x[i + 10],9,38016083);
    			c = this.gg(c,d,a,b,x[i + 15],14,-660478335);
    			b = this.gg(b,c,d,a,x[i + 4],20,-405537848);
    			a = this.gg(a,b,c,d,x[i + 9],5,568446438);
    			d = this.gg(d,a,b,c,x[i + 14],9,-1019803690);
    			c = this.gg(c,d,a,b,x[i + 3],14,-187363961);
    			b = this.gg(b,c,d,a,x[i + 8],20,1163531501);
    			a = this.gg(a,b,c,d,x[i + 13],5,-1444681467);
    			d = this.gg(d,a,b,c,x[i + 2],9,-51403784);
    			c = this.gg(c,d,a,b,x[i + 7],14,1735328473);
    			b = this.gg(b,c,d,a,x[i + 12],20,-1926607734);
    			a = this.hh(a,b,c,d,x[i + 5],4,-378558);
    			d = this.hh(d,a,b,c,x[i + 8],11,-2022574463);
    			c = this.hh(c,d,a,b,x[i + 11],16,1839030562);
    			b = this.hh(b,c,d,a,x[i + 14],23,-35309556);
    			a = this.hh(a,b,c,d,x[i + 1],4,-1530992060);
    			d = this.hh(d,a,b,c,x[i + 4],11,1272893353);
    			c = this.hh(c,d,a,b,x[i + 7],16,-155497632);
    			b = this.hh(b,c,d,a,x[i + 10],23,-1094730640);
    			a = this.hh(a,b,c,d,x[i + 13],4,681279174);
    			d = this.hh(d,a,b,c,x[i],11,-358537222);
    			c = this.hh(c,d,a,b,x[i + 3],16,-722521979);
    			b = this.hh(b,c,d,a,x[i + 6],23,76029189);
    			a = this.hh(a,b,c,d,x[i + 9],4,-640364487);
    			d = this.hh(d,a,b,c,x[i + 12],11,-421815835);
    			c = this.hh(c,d,a,b,x[i + 15],16,530742520);
    			b = this.hh(b,c,d,a,x[i + 2],23,-995338651);
    			a = this.ii(a,b,c,d,x[i],6,-198630844);
    			d = this.ii(d,a,b,c,x[i + 7],10,1126891415);
    			c = this.ii(c,d,a,b,x[i + 14],15,-1416354905);
    			b = this.ii(b,c,d,a,x[i + 5],21,-57434055);
    			a = this.ii(a,b,c,d,x[i + 12],6,1700485571);
    			d = this.ii(d,a,b,c,x[i + 3],10,-1894986606);
    			c = this.ii(c,d,a,b,x[i + 10],15,-1051523);
    			b = this.ii(b,c,d,a,x[i + 1],21,-2054922799);
    			a = this.ii(a,b,c,d,x[i + 8],6,1873313359);
    			d = this.ii(d,a,b,c,x[i + 15],10,-30611744);
    			c = this.ii(c,d,a,b,x[i + 6],15,-1560198380);
    			b = this.ii(b,c,d,a,x[i + 13],21,1309151649);
    			a = this.ii(a,b,c,d,x[i + 4],6,-145523070);
    			d = this.ii(d,a,b,c,x[i + 11],10,-1120210379);
    			c = this.ii(c,d,a,b,x[i + 2],15,718787259);
    			b = this.ii(b,c,d,a,x[i + 9],21,-343485551);
    			a = this.addme(a,olda);
    			b = this.addme(b,oldb);
    			c = this.addme(c,oldc);
    			d = this.addme(d,oldd);
    			i += 16;
    		}
    		return this.rhex(a) + this.rhex(b) + this.rhex(c) + this.rhex(d);
    	}
    	,ii: function(a,b,c,d,x,s,t) {
    		return this.cmn(this.bitXOR(c,this.bitOR(b,~d)),a,b,x,s,t);
    	}
    	,hh: function(a,b,c,d,x,s,t) {
    		return this.cmn(this.bitXOR(this.bitXOR(b,c),d),a,b,x,s,t);
    	}
    	,gg: function(a,b,c,d,x,s,t) {
    		return this.cmn(this.bitOR(this.bitAND(b,d),this.bitAND(c,~d)),a,b,x,s,t);
    	}
    	,ff: function(a,b,c,d,x,s,t) {
    		return this.cmn(this.bitOR(this.bitAND(b,c),this.bitAND(~b,d)),a,b,x,s,t);
    	}
    	,cmn: function(q,a,b,x,s,t) {
    		return this.addme(this.rol(this.addme(this.addme(a,q),this.addme(x,t)),s),b);
    	}
    	,rol: function(num,cnt) {
    		return num << cnt | num >>> 32 - cnt;
    	}
    	,str2blks: function(str) {
    		var nblk = (str.length + 8 >> 6) + 1;
    		var blks = new Array();
    		var _g1 = 0, _g = nblk * 16;
    		while(_g1 < _g) {
    			var i = _g1++;
    			blks[i] = 0;
    		}
    		var i = 0;
    		while(i < str.length) {
    			blks[i >> 2] |= HxOverrides.cca(str,i) << (str.length * 8 + i) % 4 * 8;
    			i++;
    		}
    		blks[i >> 2] |= 128 << (str.length * 8 + i) % 4 * 8;
    		var l = str.length * 8;
    		var k = nblk * 16 - 2;
    		blks[k] = l & 255;
    		blks[k] |= (l >>> 8 & 255) << 8;
    		blks[k] |= (l >>> 16 & 255) << 16;
    		blks[k] |= (l >>> 24 & 255) << 24;
    		return blks;
    	}
    	,rhex: function(num) {
    		var str = "";
    		var hex_chr = "0123456789abcdef";
    		var _g = 0;
    		while(_g < 4) {
    			var j = _g++;
    			str += hex_chr.charAt(num >> j * 8 + 4 & 15) + hex_chr.charAt(num >> j * 8 & 15);
    		}
    		return str;
    	}
    	,addme: function(x,y) {
    		var lsw = (x & 65535) + (y & 65535);
    		var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
    		return msw << 16 | lsw & 65535;
    	}
    	,bitAND: function(a,b) {
    		var lsb = a & 1 & (b & 1);
    		var msb31 = a >>> 1 & b >>> 1;
    		return msb31 << 1 | lsb;
    	}
    	,bitXOR: function(a,b) {
    		var lsb = a & 1 ^ b & 1;
    		var msb31 = a >>> 1 ^ b >>> 1;
    		return msb31 << 1 | lsb;
    	}
    	,bitOR: function(a,b) {
    		var lsb = a & 1 | b & 1;
    		var msb31 = a >>> 1 | b >>> 1;
    		return msb31 << 1 | lsb;
    	}
    	,__class__: haxe.Md5
    }
    haxe.Timer = function(time_ms) {
    	var me = this;
    	this.id = window.setInterval(function() {
    		me.run();
    	},time_ms);
    };
    haxe.Timer.__name__ = true;
    haxe.Timer.delay = function(f,time_ms) {
    	var t = new haxe.Timer(time_ms);
    	t.run = function() {
    		t.stop();
    		f();
    	};
    	return t;
    }
    haxe.Timer.measure = function(f,pos) {
    	var t0 = haxe.Timer.stamp();
    	var r = f();
    	haxe.Log.trace(haxe.Timer.stamp() - t0 + "s",pos);
    	return r;
    }
    haxe.Timer.stamp = function() {
    	return new Date().getTime() / 1000;
    }
    haxe.Timer.prototype = {
    	run: function() {
    	}
    	,stop: function() {
    		if(this.id == null) return;
    		window.clearInterval(this.id);
    		this.id = null;
    	}
    	,__class__: haxe.Timer
    }
    var js = js || {}
    js.Boot = function() { }
    js.Boot.__name__ = true;
    js.Boot.__unhtml = function(s) {
    	return s.split("&").join("&amp;").split("<").join("&lt;").split(">").join("&gt;");
    }
    js.Boot.__trace = function(v,i) {
    	var msg = i != null?i.fileName + ":" + i.lineNumber + ": ":"";
    	msg += js.Boot.__string_rec(v,"");
    	var d;
    	if(typeof(document) != "undefined" && (d = document.getElementById("haxe:trace")) != null) d.innerHTML += js.Boot.__unhtml(msg) + "<br/>"; else if(typeof(console) != "undefined" && console.log != null) console.log(msg);
    }
    js.Boot.__clear_trace = function() {
    	var d = document.getElementById("haxe:trace");
    	if(d != null) d.innerHTML = "";
    }
    js.Boot.isClass = function(o) {
    	return o.__name__;
    }
    js.Boot.isEnum = function(e) {
    	return e.__ename__;
    }
    js.Boot.getClass = function(o) {
    	return o.__class__;
    }
    js.Boot.__string_rec = function(o,s) {
    	if(o == null) return "null";
    	if(s.length >= 5) return "<...>";
    	var t = typeof(o);
    	if(t == "function" && (o.__name__ || o.__ename__)) t = "object";
    	switch(t) {
    	case "object":
    		if(o instanceof Array) {
    			if(o.__enum__) {
    				if(o.length == 2) return o[0];
    				var str = o[0] + "(";
    				s += "\t";
    				var _g1 = 2, _g = o.length;
    				while(_g1 < _g) {
    					var i = _g1++;
    					if(i != 2) str += "," + js.Boot.__string_rec(o[i],s); else str += js.Boot.__string_rec(o[i],s);
    				}
    				return str + ")";
    			}
    			var l = o.length;
    			var i;
    			var str = "[";
    			s += "\t";
    			var _g = 0;
    			while(_g < l) {
    				var i1 = _g++;
    				str += (i1 > 0?",":"") + js.Boot.__string_rec(o[i1],s);
    			}
    			str += "]";
    			return str;
    		}
    		var tostr;
    		try {
    			tostr = o.toString;
    		} catch( e ) {
    			return "???";
    		}
    		if(tostr != null && tostr != Object.toString) {
    			var s2 = o.toString();
    			if(s2 != "[object Object]") return s2;
    		}
    		var k = null;
    		var str = "{\n";
    		s += "\t";
    		var hasp = o.hasOwnProperty != null;
    		for( var k in o ) { ;
    		if(hasp && !o.hasOwnProperty(k)) {
    			continue;
    		}
    		if(k == "prototype" || k == "__class__" || k == "__super__" || k == "__interfaces__" || k == "__properties__") {
    			continue;
    		}
    		if(str.length != 2) str += ", \n";
    		str += s + k + " : " + js.Boot.__string_rec(o[k],s);
    		}
    		s = s.substring(1);
    		str += "\n" + s + "}";
    		return str;
    	case "function":
    		return "<function>";
    	case "string":
    		return o;
    	default:
    		return String(o);
    	}
    }
    js.Boot.__interfLoop = function(cc,cl) {
    	if(cc == null) return false;
    	if(cc == cl) return true;
    	var intf = cc.__interfaces__;
    	if(intf != null) {
    		var _g1 = 0, _g = intf.length;
    		while(_g1 < _g) {
    			var i = _g1++;
    			var i1 = intf[i];
    			if(i1 == cl || js.Boot.__interfLoop(i1,cl)) return true;
    		}
    	}
    	return js.Boot.__interfLoop(cc.__super__,cl);
    }
    js.Boot.__instanceof = function(o,cl) {
    	try {
    		if(o instanceof cl) {
    			if(cl == Array) return o.__enum__ == null;
    			return true;
    		}
    		if(js.Boot.__interfLoop(o.__class__,cl)) return true;
    	} catch( e ) {
    		if(cl == null) return false;
    	}
    	switch(cl) {
    	case Int:
    		return Math.ceil(o%2147483648.0) === o;
    	case Float:
    		return typeof(o) == "number";
    	case Bool:
    		return o === true || o === false;
    	case String:
    		return typeof(o) == "string";
    	case Dynamic:
    		return true;
    	default:
    		if(o == null) return false;
    		if(cl == Class && o.__name__ != null) return true; else null;
    		if(cl == Enum && o.__ename__ != null) return true; else null;
    		return o.__enum__ == cl;
    	}
    }
    js.Boot.__cast = function(o,t) {
    	if(js.Boot.__instanceof(o,t)) return o; else throw "Cannot cast " + Std.string(o) + " to " + Std.string(t);
    }
    js.Lib = function() { }
    js.Lib.__name__ = true;
    js.Lib.debug = function() {
    	debugger;
    }
    js.Lib.alert = function(v) {
    	alert(js.Boot.__string_rec(v,""));
    }
    js.Lib.eval = function(code) {
    	return eval(code);
    }
    js.Lib.setErrorHandler = function(f) {
    	js.Lib.onerror = f;
    }
    var $_;
    function $bind(o,m) { var f = function(){ return f.method.apply(f.scope, arguments); }; f.scope = o; f.method = m; return f; };
    if(Array.prototype.indexOf) HxOverrides.remove = function(a,o) {
    	var i = a.indexOf(o);
    	if(i == -1) return false;
    	a.splice(i,1);
    	return true;
    }; else null;
    Math.__name__ = ["Math"];
    Math.NaN = Number.NaN;
    Math.NEGATIVE_INFINITY = Number.NEGATIVE_INFINITY;
    Math.POSITIVE_INFINITY = Number.POSITIVE_INFINITY;
    Math.isFinite = function(i) {
    	return isFinite(i);
    };
    Math.isNaN = function(i) {
    	return isNaN(i);
    };
    String.prototype.__class__ = String;
    String.__name__ = true;
    Array.prototype.__class__ = Array;
    Array.__name__ = true;
    Date.prototype.__class__ = Date;
    Date.__name__ = ["Date"];
    var Int = { __name__ : ["Int"]};
    var Dynamic = { __name__ : ["Dynamic"]};
    var Float = Number;
    Float.__name__ = ["Float"];
    var Bool = Boolean;
    Bool.__ename__ = ["Bool"];
    var Class = { __name__ : ["Class"]};
    var Enum = { };
    var Void = { __ename__ : ["Void"]};
    if(typeof document != "undefined") js.Lib.document = document;
    if(typeof window != "undefined") {
    	js.Lib.window = window;
    	js.Lib.window.onerror = function(msg,url,line) {
    		var f = js.Lib.onerror;
    		if(f == null) return false;
    		return f(msg,[url + ":" + line]);
    	};
    }
    com.wiris.js.JsPluginTools.main();
    delete Array.prototype.__class__; }());
    // @codingStandardsIgnoreEnd
    /**
     * Modal window constructor
     * @param {string} path             Iframe src
     * @param {string} title            Modal window title
     * @param {Object} editorAttributes Editor attributes (width, height)...
     * @ignore
     */
    function ModalWindow(path, editorAttributes) {

        var ua = navigator.userAgent.toLowerCase();
        var isAndroid = ua.indexOf("android") > -1;
        var isIOS = ((ua.indexOf("ipad") > -1) || (ua.indexOf("iphone") > -1));

        var deviceWidth = window.outerWidth;
        var deviceHeight = window.outerHeight;

        var landscape = deviceWidth > deviceHeight;
        var portrait = deviceWidth < deviceHeight;

        var iframeAttributes  = {};
        iframeAttributes['width'] = editorAttributes.split(' ').join('').split(',')[0].split("=")[1];
        iframeAttributes['height'] = editorAttributes.split(' ').join('').split(',')[1].split("=")[1];
        iframeAttributes['src'] = path;

        var isMobile = (landscape && iframeAttributes['height'] > deviceHeight) || (portrait && iframeAttributes['width'] > deviceWidth) ? true : false;

        // Device object properties.

        var deviceProperties = {};
        deviceProperties['orientation'] = landscape ? 'landscape' : 'portait';
        deviceProperties['isAndroid'] = isAndroid ? true : false;
        deviceProperties['isIOS'] = isIOS ? true : false;
        deviceProperties['isMobile'] = isMobile;

        this.deviceProperties = deviceProperties;
        this.properties = {
            created : false,
            state : '',
            previousState : '',
            deviceProperties: deviceProperties
        }

        this.properties.iframeAttributes = iframeAttributes;
        this.modalHeight = parseInt(this.properties.iframeAttributes['height']);
        this.modalWidth = parseInt(this.properties.iframeAttributes['width']);

        this.title = '';

        var attributes = {};

        attributes['class'] = 'wrs_modal_overlay';
        var modalOverlayDiv = wrs_createElement('div', attributes);
        this.overlayDiv = modalOverlayDiv;

        attributes['class'] = 'wrs_modal_title_bar';
        var barModalDiv = wrs_createElement('div', attributes);
        this.titleBardDiv = barModalDiv;

        attributes = {};
        attributes['class'] = 'wrs_modal_title';
        var titleModalDiv = wrs_createElement('div', attributes);
        titleModalDiv.innerHTML = this.title;
        this.titleDiv = titleModalDiv;

        attributes = {};
        attributes['class'] = 'wrs_modal_close_button';
        attributes['title'] = strings['close'];
        var closeModalDiv = wrs_createElement('a', attributes);
        closeModalDiv.setAttribute('role','button');
        this.closeDiv = closeModalDiv;

        attributes = {};
        attributes['class'] = 'wrs_modal_stack_button';
        attributes['title'] = strings['fullscreen'];
        var stackModalDiv = wrs_createElement('a', attributes);
        stackModalDiv.setAttribute('role','button');
        this.stackDiv = stackModalDiv;

        attributes = {};
        attributes['class'] = 'wrs_modal_minimize_button';
        attributes['title'] = strings['minimise'];
        var minimizeModalDiv = wrs_createElement('a', attributes);
        minimizeModalDiv.setAttribute('role','button');
        this.minimizeDiv = minimizeModalDiv;

        attributes = {};
        attributes['class'] = 'wrs_modal_dialogContainer';
        var containerDiv = wrs_createElement('div', attributes);
        containerDiv.style.overflow = 'hidden';
        this.containerDiv = containerDiv;

        attributes = {};
        attributes['id'] = 'wrs_modal_iframe_id';
        attributes['class'] = 'wrs_modal_iframe';
        attributes['title'] = 'WIRIS Editor Modal Window';
        attributes['src'] = iframeAttributes['src'];
        attributes['frameBorder'] = "0";
        var iframeModal = wrs_createElement('iframe', attributes);
        this.iframe = iframeModal;

        attributes = {};
        attributes['class'] = 'wrs_modal_iframeContainer';
        var iframeModalContainer = wrs_createElement('div', attributes);
        this.iframeContainer = iframeModalContainer;

        // We create iframe inside _wrs_conf_path origin.
        this.iframeOrigin = this.getOriginFromUrl(_wrs_conf_path);

        this.lastImageWasNew = true;

        this.toolbar = null;
    }

    ModalWindow.prototype.create = function() {
        this.titleBardDiv.appendChild(this.closeDiv);
        this.titleBardDiv.appendChild(this.stackDiv);
        this.titleBardDiv.appendChild(this.minimizeDiv);
        this.titleBardDiv.appendChild(this.titleDiv);
        this.iframeContainer.appendChild(this.iframe);

        wrs_addEvent(this.overlayDiv, 'mouseup', function (e) {
            if (typeof(_wrs_modalWindow) !== 'undefined' && _wrs_modalWindow != null) {
                _wrs_modalWindow.fireEditorEvent('mouseup');
            }
        });

        if (!this.deviceProperties['isMobile'] && !this.deviceProperties['isAndroid'] && !this.deviceProperties['isIOS']) {
            this.containerDiv.appendChild(this.titleBardDiv);
        }
        this.containerDiv.appendChild(this.iframeContainer);

        document.body.appendChild(this.containerDiv);
        document.body.appendChild(this.overlayDiv);

        wrs_addEvent(this.closeDiv, 'click', this.close.bind(this));

        if (!this.deviceProperties['isMobile'] && !this.deviceProperties['isIOS'] && !this.deviceProperties['isAndroid']) { // Desktop.
            this.stackDiv.addEventListener('click', this.stackModalWindow.bind(this), true);
            this.minimizeDiv.addEventListener('click', this.minimizeModalWindow.bind(this), true);
            this.createModalWindowDesktop();
        }
        else if (this.deviceProperties['isAndroid']) {
            this.createModalWindowAndroid();
        }
        else if (this.deviceProperties['isIOS'] && !this.deviceProperties['isMobile']) {
            this.createModalWindowIos();
        }
        this.addListeners();
        _wrs_popupWindow = this.iframe.contentWindow;
        this.properties.open = true;
        this.properties.created = true;

        if (typeof _wrs_conf_modalWindow != "undefined" && _wrs_conf_modalWindow && _wrs_conf_modalWindowFullScreen) {
            this.maximizeModalWindow();
        }
    }

    ModalWindow.prototype.open = function() {

        if (this.deviceProperties['isIOS'] || this.deviceProperties['isAndroid'] || this.deviceProperties['isMobile']) {
            // Due to editor wait we need to wait until editor focus.
            setTimeout(function() { _wrs_modalWindow.hideKeyboard() }, 300);
        }

        if (this.properties.open == true || this.properties.created) {
            var updateToolbar = function(object) {
                if (customEditor = wrs_int_getCustomEditorEnabled()) {
                    var toolbar = customEditor.toolbar ? customEditor.toolbar : _wrs_int_wirisProperties['toolbar'];
                    _wrs_modalWindow.setTitle(customEditor.title);
                    if (object.toolbar == null || object.toolbar != toolbar) {
                        object.setToolbar(toolbar);
                    }
                } else {
                    var toolbar = (typeof _wrs_int_wirisProperties == 'undefined' || typeof _wrs_int_wirisProperties['toolbar'] == 'undefined') ? 'general' : _wrs_int_wirisProperties['toolbar'];
                    _wrs_modalWindow.setTitle('WIRIS EDITOR math');
                    if (object.toolbar == null || object.toolbar != toolbar) {
                        object.setToolbar(toolbar);
                        wrs_int_disableCustomEditors();
                    }
                }
            };

            var self = this;

            // It controls cases where is needed to set an empty mathml or copy the current mathml value.
            var updateMathMLContent = function () {
                if (self.properties.deviceProperties.isAndroid || self.properties.deviceProperties.isIOS) {
                    self.setMathMLWithCallback('<math><semantics><annotation encoding="application/json">[]</annotation></semantics></math>"');
                } else {
                    self.setMathMLWithCallback('<math/>');
                }
            };

            if (this.properties.open == true) {
                updateToolbar(self);
                if (_wrs_isNewElement) {
                    updateMathMLContent();
                    self.lastImageWasNew = true;
                }
                else {
                    this.setMathMLWithCallback(wrs_mathmlDecode(_wrs_temporalImage.getAttribute(_wrs_conf_imageMathmlAttribute)));
                    this.lastImageWasNew = false;
                }
            }
            else {
                this.containerDiv.style.visibility = '';
                this.containerDiv.style.opacity = '';
                this.containerDiv.style.display = '';
                this.overlayDiv.style.visibility = '';
                this.overlayDiv.style.display = '';

                this.properties.open = true;

                updateToolbar(self);

                if (_wrs_isNewElement) {
                    updateMathMLContent();
                    self.lastImageWasNew = true;
                } else {
                    this.setMathMLWithCallback(wrs_mathmlDecode(_wrs_temporalImage.getAttribute(_wrs_conf_imageMathmlAttribute)));
                    this.lastImageWasNew = false;
                }

                if (!this.properties.deviceProperties.isAndroid && !this.properties.deviceProperties.isIOS) {
                    this.stackModalWindow();
                }
            }

            if (typeof _wrs_conf_modalWindow != "undefined" && _wrs_conf_modalWindow && _wrs_conf_modalWindowFullScreen) {
                this.maximizeModalWindow();
            }
        } else {
            var title = wrs_int_getCustomEditorEnabled() != null ? wrs_int_getCustomEditorEnabled().title : 'WIRIS EDITOR math';
            _wrs_modalWindow.setTitle(title);
            this.create();
        }

    }

    ModalWindow.prototype.isOpen = function() {
        return this.properties.open;
    }

    /**
     * Closes modal window and restores viewport header.
     * @ignore
     */
    ModalWindow.prototype.close = function() {
        this.setMathML('<math/>');
        this.overlayDiv.style.visibility = 'hidden';
        this.containerDiv.style.visibility = 'hidden';
        this.containerDiv.style.display = 'none';
        this.containerDiv.style.opacity = '0';
        this.overlayDiv.style.display = 'none';
        this.properties.open = false;
        wrs_int_disableCustomEditors();
        // Properties to initial state.
        this.properties.state = '';
        this.properties.previousState = '';
        setTimeout(
            function() {
                if (typeof _wrs_currentEditor != 'undefined' && _wrs_currentEditor) {
                    _wrs_currentEditor.focus();
                }
            }, 100);
        _wrs_popupWindow.postMessage({'objectName' : 'editorClose'}, this.iframeOrigin);
    }

    ModalWindow.prototype.addClass = function(cls) {
        wrs_addClass(this.overlayDiv, cls);
        wrs_addClass(this.titleBardDiv, cls);
        wrs_addClass(this.overlayDiv, cls);
        wrs_addClass(this.containerDiv, cls);
        wrs_addClass(this.iframeContainer, cls);
        wrs_addClass(this.iframe, cls);
        wrs_addClass(this.stackDiv, cls);
        wrs_addClass(this.minimizeDiv, cls);
    }

    ModalWindow.prototype.removeClass = function(cls) {
        wrs_removeClass(this.overlayDiv, cls);
        wrs_removeClass(this.titleBardDiv, cls);
        wrs_removeClass(this.overlayDiv, cls);
        wrs_removeClass(this.containerDiv, cls);
        wrs_removeClass(this.iframeContainer, cls);
        wrs_removeClass(this.iframe, cls);
        wrs_removeClass(this.stackDiv, cls);
        wrs_removeClass(this.minimizeDiv, cls);
    }

    ModalWindow.prototype.setTitle = function(title) {
        this.titleDiv.innerHTML = title;
        this.title = title;

    }
    /**
     * Create modal dialog for desktop OS.
     * @param  {modalDiv} modal overlay div.
     * @param  {containerDiv} modal window div.
     * @param  {iframe} embedded iframe.
     * @param  {iframeParams}  embedded iframe params (height, width).
     * @ignore
     */
    ModalWindow.prototype.createModalWindowDesktop = function() {
        this.addClass('wrs_modal_desktop');
        this.stackModalWindow();
    }

    /**
     * Create modal dialog for non mobile android devices.
     * @param  {modalDiv} modal overlay div.
     * @param  {containerDiv} modal window div.
     * @param  {iframe} embedded iframe.
     * @param  {iframeParams}  embedded iframe params (height, width).
     * @ignore
     */

    ModalWindow.prototype.createModalWindowAndroid = function() {
        this.addClass('wrs_modal_android');
    }

    /**
     * Create modal dialog for iOS devices.
     * @ignore
     */

    ModalWindow.prototype.createModalWindowIos = function() {

        this.addClass('wrs_modal_ios');
    }

    ModalWindow.prototype.stackModalWindow = function () {
        if (this.properties.state == 'stack' || (this.properties.state == 'minimized') && !this.properties.previousState == 'stack') {
            this.maximizeModalWindow();
        } else {
            this.properties.previousState = this.properties.state;
            this.properties.state = 'stack';

            this.containerDiv.style.top = null;
            this.containerDiv.style.rifgh = null;
            this.containerDiv.style.left = null;
            this.containerDiv.style.position = null;

            this.overlayDiv.style.background = "rgba(0,0,0,0)";

            this.stackDiv.title = "Full-screen";

            var modalWidth = parseInt(this.properties.iframeAttributes['width']);
            this.iframeContainer.style.width = modalWidth + 'px';
            this.iframeContainer.style.height = 300 + 'px';
            this.containerDiv.style.width = (modalWidth + 12) + 'px';
            this.iframe.style.width = this.properties.iframeAttributes['width'] + 'px';
            this.iframe.style.height = (parseInt(300) + 3) + 'px';
            this.iframe.style.margin = '6px';
            this.removeClass('wrs_maximized');
            this.minimizeDiv.title = "Minimise";
            this.removeClass('wrs_minimized');
            this.addClass('wrs_stack');
        }
    }

    ModalWindow.prototype.minimizeModalWindow = function() {
        if (this.properties.state == 'minimized' && this.properties.previousState == 'stack') {
            this.stackModalWindow();
        }
        else if (this.properties.state == 'minimized' && this.properties.previousState == 'maximized') {
            this.maximizeModalWindow();
        }
        else {
            this.removeListeners();
            this.properties.previousState = this.properties.state;
            this.properties.state = "minimized";
            this.containerDiv.style.width = null;
            this.containerDiv.style.left = null;
            this.containerDiv.style.top = null;
            this.containerDiv.style.position = null;
            this.overlayDiv.style.background = "rgba(0,0,0,0)";
            this.minimizeDiv.title = "Maximise";

            if (wrs_containsClass(this.overlayDiv, 'wrs_stack')) {
                this.removeClass('wrs_stack');
            }
            else {
                this.removeClass('wrs_maximized');
            }

            this.addClass('wrs_minimized');
        }
    }

    /**
     * Minimizes modal window.
     * @ignore
     */
    ModalWindow.prototype.maximizeModalWindow = function() {
        this.properties.previousState = this.properties.state;
        this.properties.state = 'maximized';

        this.iframeContainer.style.width = this.modalWidth + 'px';
        this.iframeContainer.style.height = this.modalHeight + 'px';
        this.containerDiv.style.width = (this.modalWidth + 12) + 'px';
        this.iframe.style.width = this.properties.iframeAttributes['width'] + 'px';
        this.iframe.style.height = (parseInt(this.properties.iframeAttributes['height']) + 3) + 'px';
        this.iframe.style.margin = '6px';
        this.removeClass('wrs_drag');
        if (wrs_containsClass(this.overlayDiv, 'wrs_minimized')) {
            this.minimizeDiv.title = "Minimise";
            this.removeClass('wrs_minimized');
        } else if (wrs_containsClass(this.overlayDiv, 'wrs_stack')) {
            this.containerDiv.style.left = null;
            this.containerDiv.style.top = null;
            this.removeClass('wrs_stack');
        }
        this.stackDiv.title = "Exit full-screen";
        this.overlayDiv.style.background = "rgba(0,0,0,0.8)";
        this.overlayDiv.style.display = '';
        this.addClass('wrs_maximized');
    }

    /**
     * Makes an object draggable adding mouse and touch events.
     *
     * @param  {object} draggable object (for example modal dialog).
     * @param  {target} target to add the events (for example de titlebar of a modal dialog)
     * @ignore
     */
    ModalWindow.prototype.addListeners = function() {
        // Mouse events.
        wrs_addEvent(document.body, 'mousedown', this.startDrag.bind(this));
        wrs_addEvent(window, 'mouseup', this.stopDrag.bind(this));
        wrs_addEvent(document, 'mouseup', this.stopDrag.bind(this));
        wrs_addEvent(document.body, 'mousemove', this.drag.bind(this));
    }

    /**
     * Removes draggable events from an object.
     *
     * @param  {object} draggable object (for example modal dialog).
     * @param  {target} target to add the events (for example de titlebar of a modal dialog)
     * @ignore
     */
    ModalWindow.prototype.removeListeners = function() {
        // Mouse events.
        wrs_removeEvent(document.body, 'mousedown', this.startDrag);
        wrs_removeEvent(window, 'mouseup', this.stopDrag);
        wrs_removeEvent(document, 'mouseup', this.stopDrag);
        wrs_removeEvent(document.getElementsByClassName("wrs_modal_iframe")[0], 'mouseup', this.stopDrag);
        wrs_removeEvent(document.body, 'mousemove', this.drag);
    }

    /**
     * Returns mouse or touch coordinates (on touch events ev.ClientX doesn't exists)
     * @param {event} ev mnouse or touch event
     * @return {object} with the X and Y coordinates.
     * @ignore
     */
    ModalWindow.prototype.eventClient = function(ev) {
        if (typeof(ev.clientX) == 'undefined' && ev.changedTouches) {
            var client = {
                X : ev.changedTouches[0].clientX,
                Y : ev.changedTouches[0].clientY
            };
            return client;
        } else {
            client = {
                X : ev.clientX,
                Y : ev.clientY
            };
            return client;
        }
    }

    /**
     * Set the overlay div display
     *
     * @param {event} ev touchstart or mousedown event.
     * @ignore
     */
    ModalWindow.prototype.setOverlayDiv = function(ev) {
        this.overlayDiv.style.display = '';
    }

    /**
     * Start drag function: set the object _wrs_dragDataObject with the draggable object offsets coordinates.
     * when drag starts (on touchstart or mousedown events).
     *
     * @param {event} ev touchstart or mousedown event.
     * @ignore
     */
    ModalWindow.prototype.startDrag = function(ev) {
        if (this.properties.state == 'minimized') {
            return;
        }
        if (ev.target.className == 'wrs_modal_title') {
            if(!this.dragDataobject) {
                ev = ev || event;
                this.dragDataObject = {
                    x: this.eventClient(ev).X - (isNaN(parseInt(window.getComputedStyle(this.containerDiv)).left && parseInt(window.getComputedStyle(this.containerDiv).left > 0 )) ? this.containerDiv.offsetLeft : parseInt(window.getComputedStyle(this.containerDiv).left)),
                    y: this.eventClient(ev).Y - (isNaN(parseInt(window.getComputedStyle(this.containerDiv).top)) ? this.containerDiv.offsetTop : parseInt(window.getComputedStyle(this.containerDiv).top))
                };
            };
        }

    }

    /**
     * Updates_wrs_dragDataObject with the draggable object coordinates when the draggable object is being moved.
     *
     * @param {event} ev touchmouve or mousemove events.
     * @ignore
     */
    ModalWindow.prototype.drag = function(ev) {
        if(this.dragDataObject) {
            ev.preventDefault();
            ev = ev || event;
            this.containerDiv.style.left = this.eventClient(ev).X - this.dragDataObject.x + window.pageXOffset + "px";
            this.containerDiv.style.top = this.eventClient(ev).Y - this.dragDataObject.y + window.pageYOffset + "px";
            this.containerDiv.style.position = 'absolute';
            this.containerDiv.style.bottom = null;
            wrs_removeClass(this.containerDiv, 'wrs_stack');
        }
    }

    /**
     * Set the _wrs_dragDataObject to null when the drag finish (touchend or mouseup events).
     *
     * @param {event} ev touchend or mouseup event.
     * @ignore
     */
    ModalWindow.prototype.stopDrag = function(ev) {
        this.containerDiv.style.position = 'fixed';
        // Due to we have multiple events that call this function, we need only to execute the next modifiers one time,
        // when the user stops to drag and dragDataObject is not null (the object to drag is attached).
        if (this.dragDataObject) {
            // Fixed position makes the coords relative to the main window. So that, we need to transform
            // the absolute coords to relative removing the scroll.
            this.containerDiv.style.left = parseInt(this.containerDiv.style.left) - window.pageXOffset + "px";
            this.containerDiv.style.top = parseInt(this.containerDiv.style.top) - window.pageYOffset + "px";
        }
        this.containerDiv.style.bottom = null;
        this.dragDataObject = null;
    }

    /**
     * Hide soft keyboards on IOS systems.
     * @ignore
     */
    ModalWindow.prototype.hideKeyboard = function() {
        document.activeElement.blur();
    }

    /**
     * Returns the origin (i.e protocol + hostname + port) from an url string.
     * This method is used to get the iframe window origin to allow postMessages.
     * @param  {string} url url string
     * @return {string}     origin string
     * @ignore
     */
    ModalWindow.prototype.getOriginFromUrl = function(url) {
        var parser = document.createElement('a');
        parser.href = url;
        return parser.protocol.indexOf('//') == -1 ? parser.protocol + '//' + parser.host : parser.protocol + parser.host;
    }

    /**
     * Enable safe cross-origin comunication betweenWIRIS Plugin and WIRIS Editor. We can't call directly
     * WIRIS Editor methods because the content iframe could be in a different domain.
     * We use postMessage method to create a wrapper between modal window and editor.
     *
     */

    /**
     * Set a MathML into editor.
     * @param {string} mathml MathML string.
     * @ignore
     */
    ModalWindow.prototype.setMathML = function(mathml) {
        _wrs_popupWindow.postMessage({'objectName' : 'editor', 'methodName' : 'setMathML', 'arguments': [mathml]}, this.iframeOrigin);
        this.focus();
    }
    /**
     * Set a MathML into editor and call function in back.
     * @param {string} mathml MathML string.
     * @ignore
     */
    ModalWindow.prototype.setMathMLWithCallback = function(mathml) {
        _wrs_popupWindow.postMessage({'objectName' : 'editorCallback', 'arguments': [mathml]}, this.iframeOrigin);
        this.focus();
    }

    /**
     * Set a toolbar into editor.
     * @param {string} toolbar toolbar name.
     * @ignore
     */
    ModalWindow.prototype.setToolbar = function(toolbar) {
        this.toolbar = toolbar;
        _wrs_popupWindow.postMessage({'objectName' : 'editor', 'methodName' : 'setParams', 'arguments': [{'toolbar' : toolbar}]}, this.iframeOrigin);
    }

    /**
     * Set focus on editor.
     * @ignore
     */
    ModalWindow.prototype.focus = function() {
        // Focus on iframe explicit
        // We add this focus in iframe beacuse tiny3 have a problem with focus in chrome and it can't focus iframe automaticly
        this.iframe.focus();
        _wrs_popupWindow.postMessage({'objectName' : 'editor', 'methodName' : 'focus', 'arguments': null}, this.iframeOrigin);
    }

    /**
     * Fires editor event
     * @param  {string} eventName event name
     * @ignore
     */
    ModalWindow.prototype.fireEditorEvent = function(eventName) {
        _wrs_popupWindow.postMessage({'objectName' : 'editorEvent', 'eventName' : eventName, 'arguments': null}, this.iframeOrigin);
    }

    /**
     * Add classes to show well the layout when there is a soft keyboard.
     * @ignore
     */
    ModalWindow.prototype.addClassVirtualKeyboard = function () {
        this.containerDiv.classList.remove('wrs_modal_ios');
        // this.iframeContainer.classList.remove('wrs_modal_ios');
        // this.overlayDiv.classList.remove('wrs_modal_ios');

        this.containerDiv.classList.add('wrs_virtual_keyboard_opened');
        // this.iframeContainer.classList.add('wrs_virtual_keyboard_opened');
        // this.overlayDiv.classList.add('wrs_virtual_keyboard_opened');
    }

    /**
     * Remove classes to show well the layout when there is a soft keyboard.
     * @ignore
     */
    ModalWindow.prototype.removeClassVirtualKeyboard = function () {
        this.containerDiv.classList.remove('wrs_virtual_keyboard_opened');
        // this.iframeContainer.classList.remove('wrs_virtual_keyboard_opened');
        // this.overlayDiv.classList.remove('wrs_virtual_keyboard_opened');

        this.containerDiv.classList.add('wrs_modal_ios');
        // this.iframeContainer.classList.add('wrs_modal_ios');
        // this.overlayDiv.classList.add('wrs_modal_ios');
    }

    var _wrs_conf_core_loaded = true;

    var _wrs_conf_core_loaded = true;