text: /**
   * formatInline scenarios for tag "B" (| = caret, |foo| = selected text)
   *
   *   #1 caret in unformatted text:
   *      abcdefg|
   *   output:
   *      abcdefg<b>|</b>
   *
   *   #2 unformatted text selected:
   *      abc|deg|h
   *   output:
   *      abc<b>|deg|</b>h
   *
   *   #3 unformatted text selected across boundaries:
   *      ab|c <span>defg|h</span>
   *   output:
   *      ab<b>|c </b><span><b>defg</b>|h</span>
   *
   *   #4 formatted text entirely selected
   *      <b>|abc|</b>
   *   output:
   *      |abc|
   *
   *   #5 formatted text partially selected
   *      <b>ab|c|</b>
   *   output:
   *      <b>ab</b>|c|
   *
   *   #6 formatted text selected across boundaries
   *      <span>ab|c</span> <b>de|fgh</b>
   *   output:
   *      <span>ab|c</span> de|<b>fgh</b>
   */
  (function(wysihtml5) {
    var // Treat <b> as <strong> and vice versa
        ALIAS_MAPPING = {
          "strong": "b",
          "em":     "i",
          "b":      "strong",
          "i":      "em"
        },
        htmlApplier = {};
  
    function _getTagNames(tagName) {
      var alias = ALIAS_MAPPING[tagName];
      return alias ? [tagName.toLowerCase(), alias.toLowerCase()] : [tagName.toLowerCase()];
    }
  
    function _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp, container) {
      var identifier = tagName;
  
      if (className) {
        identifier += ":" + className;
      }
      if (cssStyle) {
        identifier += ":" + cssStyle;
      }
  
      if (!htmlApplier[identifier]) {
        htmlApplier[identifier] = new wysihtml5.selection.HTMLApplier(_getTagNames(tagName), className, classRegExp, true, cssStyle, styleRegExp, container);
      }
  
      return htmlApplier[identifier];
    }
  
    wysihtml5.commands.formatInline = {
      exec: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, dontRestoreSelect, noCleanup) {
        var range = composer.selection.createRange(),
            ownRanges = composer.selection.getOwnRanges();
  
        if (!ownRanges || ownRanges.length == 0) {
          return false;
        }
        composer.selection.getSelection().removeAllRanges();
  
        _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp, composer.element).toggleRange(ownRanges);
  
        if (!dontRestoreSelect) {
          range.setStart(ownRanges[0].startContainer,  ownRanges[0].startOffset);
          range.setEnd(
            ownRanges[ownRanges.length - 1].endContainer,
            ownRanges[ownRanges.length - 1].endOffset
          );
          composer.selection.setSelection(range);
          composer.selection.executeAndRestore(function() {
            if (!noCleanup) {
              composer.cleanUp();
            }
          }, true, true);
        } else if (!noCleanup) {
          composer.cleanUp();
        }
      },
  
      // Executes so that if collapsed caret is in a state and executing that state it should unformat that state
      // It is achieved by selecting the entire state element before executing.
      // This works on built in contenteditable inline format commands
      execWithToggle: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) {
        var that = this;
        var range = null;
  
        if (this.state(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) &&
          composer.selection.isCollapsed() &&
          !composer.selection.caretIsLastInSelection() &&
          !composer.selection.caretIsFirstInSelection()
        ) {
          var state_element = that.state(composer, command, tagName, className, classRegExp)[0];
          composer.selection.executeAndRestoreRangy(function() {
            var parent = state_element.parentNode;
            composer.selection.selectNode(state_element, true);
            wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, true, true);
          });
        } else {
          if (this.state(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) && !composer.selection.isCollapsed()) {
            composer.selection.executeAndRestoreRangy(function() {
              wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, true, true);
            });
          } else {
            wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp);
          }
        }
        range = composer.selection.getRange()
        range.collapse();
        composer.selection.setSelection(range);
      },
  
      state: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) {
        var doc           = composer.doc,
            aliasTagName  = ALIAS_MAPPING[tagName] || tagName,
            ownRanges, isApplied;
  
        // Check whether the document contains a node with the desired tagName
        if (!wysihtml5.dom.hasElementWithTagName(doc, tagName) &&
            !wysihtml5.dom.hasElementWithTagName(doc, aliasTagName)) {
          return false;
        }
  
         // Check whether the document contains a node with the desired className
        if (className && !wysihtml5.dom.hasElementWithClassName(doc, className)) {
           return false;
        }
  
        ownRanges = composer.selection.getOwnRanges();
  
        if (!ownRanges || ownRanges.length === 0) {
          return false;
        }
  
        isApplied = _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp, composer.element).isAppliedToRange(ownRanges);
  
        return (isApplied && isApplied.elements) ? isApplied.elements : false;
      }
    };
  })(wysihtml5);
  