(function () {
  'use strict';

  function _defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
      var descriptor = props[i];
      descriptor.enumerable = descriptor.enumerable || false;
      descriptor.configurable = true;
      if ("value" in descriptor) descriptor.writable = true;
      Object.defineProperty(target, descriptor.key, descriptor);
    }
  }

  function _createClass(Constructor, protoProps, staticProps) {
    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
    if (staticProps) _defineProperties(Constructor, staticProps);
    Object.defineProperty(Constructor, "prototype", {
      writable: false
    });
    return Constructor;
  }

  /**
   * A NodeIterator with iframes support and a method to check if an element is
   * matching a specified selector
   * @example
   * const iterator = new DOMIterator(
   *     document.querySelector("#context"), true
   * );
   * iterator.forEachNode(NodeFilter.SHOW_TEXT, node => {
   *     console.log(node);
   * }, node => {
   *     if(DOMIterator.matches(node.parentNode, ".ignore")){
   *         return NodeFilter.FILTER_REJECT;
   *     } else {
   *         return NodeFilter.FILTER_ACCEPT;
   *     }
   * }, () => {
   *     console.log("DONE");
   * });
   * @todo Outsource into separate repository
   */
  var DOMIterator = /*#__PURE__*/function () {
    /**
     * @param {HTMLElement|HTMLElement[]|NodeList|string} ctx - The context DOM
     * element, an array of DOM elements, a NodeList or a selector
     * @param {boolean} [iframes=true] - A boolean indicating if iframes should
     * be handled
     * @param {string[]} [exclude=[]] - An array containing exclusion selectors
     * for iframes
     * @param {number} [iframesTimeout=5000] - A number indicating the ms to
     * wait before an iframe should be skipped, in case the load event isn't
     * fired. This also applies if the user is offline and the resource of the
     * iframe is online (either by the browsers "offline" mode or because
     * there's no internet connection)
     */
    function DOMIterator(ctx, iframes, exclude, iframesTimeout) {
      if (iframes === void 0) {
        iframes = true;
      }

      if (exclude === void 0) {
        exclude = [];
      }

      if (iframesTimeout === void 0) {
        iframesTimeout = 5000;
      }

      /**
       * The context of the instance. Either a DOM element, an array of DOM
       * elements, a NodeList or a selector
       * @type {HTMLElement|HTMLElement[]|NodeList|string}
       * @access protected
       */
      this.ctx = ctx;
      /**
       * Boolean indicating if iframe support is enabled
       * @type {boolean}
       * @access protected
       */

      this.iframes = iframes;
      /**
       * An array containing exclusion selectors for iframes
       * @type {string[]}
       */

      this.exclude = exclude;
      /**
       * The maximum ms to wait for a load event before skipping an iframe
       * @type {number}
       */

      this.iframesTimeout = iframesTimeout;
    }
    /**
     * Checks if the specified DOM element matches the selector
     * @param  {HTMLElement} element - The DOM element
     * @param  {string|string[]} selector - The selector or an array with
     * selectors
     * @return {boolean}
     * @access public
     */


    DOMIterator.matches = function matches(element, selector) {
      var selectors = typeof selector === 'string' ? [selector] : selector,
          fn = element.matches || element.matchesSelector || element.msMatchesSelector || element.mozMatchesSelector || element.oMatchesSelector || element.webkitMatchesSelector;

      if (fn) {
        var match = false;
        selectors.every(function (sel) {
          if (fn.call(element, sel)) {
            match = true;
            return false;
          }

          return true;
        });
        return match;
      } else {
        // may be false e.g. when el is a textNode
        return false;
      }
    }
    /**
     * Returns all contexts filtered by duplicates (even nested)
     * @return {HTMLElement[]} - An array containing DOM contexts
     * @access protected
     */
    ;

    var _proto = DOMIterator.prototype;

    _proto.getContexts = function getContexts() {
      var ctx,
          filteredCtx = [];

      if (typeof this.ctx === 'undefined' || !this.ctx) {
        // e.g. null
        ctx = [];
      } else if (NodeList.prototype.isPrototypeOf(this.ctx)) {
        ctx = Array.prototype.slice.call(this.ctx);
      } else if (Array.isArray(this.ctx)) {
        ctx = this.ctx;
      } else if (typeof this.ctx === 'string') {
        ctx = Array.prototype.slice.call(document.querySelectorAll(this.ctx));
      } else {
        // e.g. HTMLElement or element inside iframe
        ctx = [this.ctx];
      } // filter duplicate text nodes


      ctx.forEach(function (ctx) {
        var isDescendant = filteredCtx.filter(function (contexts) {
          return contexts.contains(ctx);
        }).length > 0;

        if (filteredCtx.indexOf(ctx) === -1 && !isDescendant) {
          filteredCtx.push(ctx);
        }
      });
      return filteredCtx;
    }
    /**
     * @callback DOMIterator~getIframeContentsSuccessCallback
     * @param {HTMLDocument} contents - The contentDocument of the iframe
     */

    /**
     * Calls the success callback function with the iframe document. If it can't
     * be accessed it calls the error callback function
     * @param {HTMLElement} ifr - The iframe DOM element
     * @param {DOMIterator~getIframeContentsSuccessCallback} successFn
     * @param {function} [errorFn]
     * @access protected
     */
    ;

    _proto.getIframeContents = function getIframeContents(ifr, successFn, errorFn) {
      if (errorFn === void 0) {
        errorFn = function errorFn() {};
      }

      var doc;

      try {
        var ifrWin = ifr.contentWindow;
        doc = ifrWin.document;

        if (!ifrWin || !doc) {
          // no permission = null. Undefined in Phantom
          throw new Error('iframe inaccessible');
        }
      } catch (e) {
        errorFn();
      }

      if (doc) {
        successFn(doc);
      }
    }
    /**
     * Checks if an iframe is empty (if about:blank is the shown page)
     * @param {HTMLElement} ifr - The iframe DOM element
     * @return {boolean}
     * @access protected
     */
    ;

    _proto.isIframeBlank = function isIframeBlank(ifr) {
      var bl = 'about:blank',
          src = ifr.getAttribute('src').trim(),
          href = ifr.contentWindow.location.href;
      return href === bl && src !== bl && src;
    }
    /**
     * Observes the onload event of an iframe and calls the success callback or
     * the error callback if the iframe is inaccessible. If the event isn't
     * fired within the specified {@link DOMIterator#iframesTimeout}, then it'll
     * call the error callback too
     * @param {HTMLElement} ifr - The iframe DOM element
     * @param {DOMIterator~getIframeContentsSuccessCallback} successFn
     * @param {function} errorFn
     * @access protected
     */
    ;

    _proto.observeIframeLoad = function observeIframeLoad(ifr, successFn, errorFn) {
      var _this = this;

      var called = false,
          tout = null;

      var listener = function listener() {
        if (called) {
          return;
        }

        called = true;
        clearTimeout(tout);

        try {
          if (!_this.isIframeBlank(ifr)) {
            ifr.removeEventListener('load', listener);

            _this.getIframeContents(ifr, successFn, errorFn);
          }
        } catch (e) {
          // isIframeBlank maybe throws throws an error
          errorFn();
        }
      };

      ifr.addEventListener('load', listener);
      tout = setTimeout(listener, this.iframesTimeout);
    }
    /**
     * Callback when the iframe is ready
     * @callback DOMIterator~onIframeReadySuccessCallback
     * @param {HTMLDocument} contents - The contentDocument of the iframe
     */

    /**
     * Callback if the iframe can't be accessed
     * @callback DOMIterator~onIframeReadyErrorCallback
     */

    /**
     * Calls the callback if the specified iframe is ready for DOM access
     * @param  {HTMLElement} ifr - The iframe DOM element
     * @param  {DOMIterator~onIframeReadySuccessCallback} successFn - Success
     * callback
     * @param {DOMIterator~onIframeReadyErrorCallback} errorFn - Error callback
     * @see {@link http://stackoverflow.com/a/36155560/3894981} for
     * background information
     * @access protected
     */
    ;

    _proto.onIframeReady = function onIframeReady(ifr, successFn, errorFn) {
      try {
        if (ifr.contentWindow.document.readyState === 'complete') {
          if (this.isIframeBlank(ifr)) {
            this.observeIframeLoad(ifr, successFn, errorFn);
          } else {
            this.getIframeContents(ifr, successFn, errorFn);
          }
        } else {
          this.observeIframeLoad(ifr, successFn, errorFn);
        }
      } catch (e) {
        // accessing document failed
        errorFn();
      }
    }
    /**
     * Callback when all iframes are ready for DOM access
     * @callback DOMIterator~waitForIframesDoneCallback
     */

    /**
     * Iterates over all iframes and calls the done callback when all of them
     * are ready for DOM access (including nested ones)
     * @param {HTMLElement} ctx - The context DOM element
     * @param {DOMIterator~waitForIframesDoneCallback} done - Done callback
     */
    ;

    _proto.waitForIframes = function waitForIframes(ctx, done) {
      var _this2 = this;

      var eachCalled = 0;
      this.forEachIframe(ctx, function () {
        return true;
      }, function (ifr) {
        eachCalled++;

        _this2.waitForIframes(ifr.querySelector('html'), function () {
          if (! --eachCalled) {
            done();
          }
        });
      }, function (handled) {
        if (!handled) {
          done();
        }
      });
    }
    /**
     * Callback allowing to filter an iframe. Must return true when the element
     * should remain, otherwise false
     * @callback DOMIterator~forEachIframeFilterCallback
     * @param {HTMLElement} iframe - The iframe DOM element
     */

    /**
     * Callback for each iframe content
     * @callback DOMIterator~forEachIframeEachCallback
     * @param {HTMLElement} content - The iframe document
     */

    /**
     * Callback if all iframes inside the context were handled
     * @callback DOMIterator~forEachIframeEndCallback
     * @param {number} handled - The number of handled iframes (those who
     * wheren't filtered)
     */

    /**
     * Iterates over all iframes inside the specified context and calls the
     * callbacks when they're ready. Filters iframes based on the instance
     * exclusion selectors
     * @param {HTMLElement} ctx - The context DOM element
     * @param {DOMIterator~forEachIframeFilterCallback} filter - Filter callback
     * @param {DOMIterator~forEachIframeEachCallback} each - Each callback
     * @param {DOMIterator~forEachIframeEndCallback} [end] - End callback
     * @access protected
     */
    ;

    _proto.forEachIframe = function forEachIframe(ctx, filter, each, end) {
      var _this3 = this;

      if (end === void 0) {
        end = function end() {};
      }

      var ifr = ctx.querySelectorAll('iframe'),
          open = ifr.length,
          handled = 0;
      ifr = Array.prototype.slice.call(ifr);

      var checkEnd = function checkEnd() {
        if (--open <= 0) {
          end(handled);
        }
      };

      if (!open) {
        checkEnd();
      }

      ifr.forEach(function (ifr) {
        if (DOMIterator.matches(ifr, _this3.exclude)) {
          checkEnd();
        } else {
          _this3.onIframeReady(ifr, function (con) {
            if (filter(ifr)) {
              handled++;
              each(con);
            }

            checkEnd();
          }, checkEnd);
        }
      });
    }
    /**
     * Creates a NodeIterator on the specified context
     * @see {@link https://developer.mozilla.org/en/docs/Web/API/NodeIterator}
     * @param {HTMLElement} ctx - The context DOM element
     * @param {DOMIterator~whatToShow} whatToShow
     * @param {DOMIterator~filterCb} filter
     * @return {NodeIterator}
     * @access protected
     */
    ;

    _proto.createIterator = function createIterator(ctx, whatToShow, filter) {
      return document.createNodeIterator(ctx, whatToShow, filter, false);
    }
    /**
     * Creates an instance of DOMIterator in an iframe
     * @param {HTMLDocument} contents - Iframe document
     * @return {DOMIterator}
     * @access protected
     */
    ;

    _proto.createInstanceOnIframe = function createInstanceOnIframe(contents) {
      return new DOMIterator(contents.querySelector('html'), this.iframes);
    }
    /**
     * Checks if an iframe occurs between two nodes, more specifically if an
     * iframe occurs before the specified node and after the specified prevNode
     * @param {HTMLElement} node - The node that should occur after the iframe
     * @param {HTMLElement} prevNode - The node that should occur before the
     * iframe
     * @param {HTMLElement} ifr - The iframe to check against
     * @return {boolean}
     * @access protected
     */
    ;

    _proto.compareNodeIframe = function compareNodeIframe(node, prevNode, ifr) {
      var compCurr = node.compareDocumentPosition(ifr),
          prev = Node.DOCUMENT_POSITION_PRECEDING;

      if (compCurr & prev) {
        if (prevNode !== null) {
          var compPrev = prevNode.compareDocumentPosition(ifr),
              after = Node.DOCUMENT_POSITION_FOLLOWING;

          if (compPrev & after) {
            return true;
          }
        } else {
          return true;
        }
      }

      return false;
    }
    /**
     * @typedef {DOMIterator~getIteratorNodeReturn}
     * @type {object.<string>}
     * @property {HTMLElement} prevNode - The previous node or null if there is
     * no
     * @property {HTMLElement} node - The current node
     */

    /**
     * Returns the previous and current node of the specified iterator
     * @param {NodeIterator} itr - The iterator
     * @return {DOMIterator~getIteratorNodeReturn}
     * @access protected
     */
    ;

    _proto.getIteratorNode = function getIteratorNode(itr) {
      var prevNode = itr.previousNode();
      var node;

      if (prevNode === null) {
        node = itr.nextNode();
      } else {
        node = itr.nextNode() && itr.nextNode();
      }

      return {
        prevNode: prevNode,
        node: node
      };
    }
    /**
     * An array containing objects. The object key "val" contains an iframe
     * DOM element. The object key "handled" contains a boolean indicating if
     * the iframe was handled already.
     * It wouldn't be enough to save all open or all already handled iframes.
     * The information of open iframes is necessary because they may occur after
     * all other text nodes (and compareNodeIframe would never be true). The
     * information of already handled iframes is necessary as otherwise they may
     * be handled multiple times
     * @typedef DOMIterator~checkIframeFilterIfr
     * @type {object[]}
     */

    /**
     * Checks if an iframe wasn't handled already and if so, calls
     * {@link DOMIterator#compareNodeIframe} to check if it should be handled.
     * Information wheter an iframe was or wasn't handled is given within the
     * <code>ifr</code> dictionary
     * @param {HTMLElement} node - The node that should occur after the iframe
     * @param {HTMLElement} prevNode - The node that should occur before the
     * iframe
     * @param {HTMLElement} currIfr - The iframe to check
     * @param {DOMIterator~checkIframeFilterIfr} ifr - The iframe dictionary.
     * Will be manipulated (by reference)
     * @return {boolean} Returns true when it should be handled, otherwise false
     * @access protected
     */
    ;

    _proto.checkIframeFilter = function checkIframeFilter(node, prevNode, currIfr, ifr) {
      var key = false,
          // false === doesn't exist
      handled = false;
      ifr.forEach(function (ifrDict, i) {
        if (ifrDict.val === currIfr) {
          key = i;
          handled = ifrDict.handled;
        }
      });

      if (this.compareNodeIframe(node, prevNode, currIfr)) {
        if (key === false && !handled) {
          ifr.push({
            val: currIfr,
            handled: true
          });
        } else if (key !== false && !handled) {
          ifr[key].handled = true;
        }

        return true;
      }

      if (key === false) {
        ifr.push({
          val: currIfr,
          handled: false
        });
      }

      return false;
    }
    /**
     * Creates an iterator on all open iframes in the specified array and calls
     * the end callback when finished
     * @param {DOMIterator~checkIframeFilterIfr} ifr
     * @param {DOMIterator~whatToShow} whatToShow
     * @param  {DOMIterator~forEachNodeCallback} eCb - Each callback
     * @param {DOMIterator~filterCb} fCb
     * @access protected
     */
    ;

    _proto.handleOpenIframes = function handleOpenIframes(ifr, whatToShow, eCb, fCb) {
      var _this4 = this;

      ifr.forEach(function (ifrDict) {
        if (!ifrDict.handled) {
          _this4.getIframeContents(ifrDict.val, function (con) {
            _this4.createInstanceOnIframe(con).forEachNode(whatToShow, eCb, fCb);
          });
        }
      });
    }
    /**
     * Iterates through all nodes in the specified context and handles iframe
     * nodes at the correct position
     * @param {DOMIterator~whatToShow} whatToShow
     * @param {HTMLElement} ctx - The context
     * @param  {DOMIterator~forEachNodeCallback} eachCb - Each callback
     * @param {DOMIterator~filterCb} filterCb - Filter callback
     * @param {DOMIterator~forEachNodeEndCallback} doneCb - End callback
     * @access protected
     */
    ;

    _proto.iterateThroughNodes = function iterateThroughNodes(whatToShow, ctx, eachCb, filterCb, doneCb) {
      var _this5 = this;

      var itr = this.createIterator(ctx, whatToShow, filterCb);

      var ifr = [],
          elements = [],
          node,
          prevNode,
          retrieveNodes = function retrieveNodes() {
        var _this5$getIteratorNod = _this5.getIteratorNode(itr);

        prevNode = _this5$getIteratorNod.prevNode;
        node = _this5$getIteratorNod.node;
        return node;
      };

      while (retrieveNodes()) {
        if (this.iframes) {
          this.forEachIframe(ctx, function (currIfr) {
            // note that ifr will be manipulated here
            return _this5.checkIframeFilter(node, prevNode, currIfr, ifr);
          }, function (con) {
            _this5.createInstanceOnIframe(con).forEachNode(whatToShow, function (ifrNode) {
              return elements.push(ifrNode);
            }, filterCb);
          });
        } // it's faster to call the each callback in an array loop
        // than in this while loop


        elements.push(node);
      }

      elements.forEach(function (node) {
        eachCb(node);
      });

      if (this.iframes) {
        this.handleOpenIframes(ifr, whatToShow, eachCb, filterCb);
      }

      doneCb();
    }
    /**
     * Callback for each node
     * @callback DOMIterator~forEachNodeCallback
     * @param {HTMLElement} node - The DOM text node element
     */

    /**
     * Callback if all contexts were handled
     * @callback DOMIterator~forEachNodeEndCallback
     */

    /**
     * Iterates over all contexts and initializes
     * {@link DOMIterator#iterateThroughNodes iterateThroughNodes} on them
     * @param {DOMIterator~whatToShow} whatToShow
     * @param  {DOMIterator~forEachNodeCallback} each - Each callback
     * @param {DOMIterator~filterCb} filter - Filter callback
     * @param {DOMIterator~forEachNodeEndCallback} done - End callback
     * @access public
     */
    ;

    _proto.forEachNode = function forEachNode(whatToShow, each, filter, done) {
      var _this6 = this;

      if (done === void 0) {
        done = function done() {};
      }

      var contexts = this.getContexts();
      var open = contexts.length;

      if (!open) {
        done();
      }

      contexts.forEach(function (ctx) {
        var ready = function ready() {
          _this6.iterateThroughNodes(whatToShow, ctx, each, filter, function () {
            if (--open <= 0) {
              // call end all contexts were handled
              done();
            }
          });
        }; // wait for iframes to avoid recursive calls, otherwise this would
        // perhaps reach the recursive function call limit with many nodes


        if (_this6.iframes) {
          _this6.waitForIframes(ctx, ready);
        } else {
          ready();
        }
      });
    }
    /**
     * Callback to filter nodes. Can return e.g. NodeFilter.FILTER_ACCEPT or
     * NodeFilter.FILTER_REJECT
     * @see {@link http://tinyurl.com/zdczmm2}
     * @callback DOMIterator~filterCb
     * @param {HTMLElement} node - The node to filter
     */

    /**
     * @typedef DOMIterator~whatToShow
     * @see {@link http://tinyurl.com/zfqqkx2}
     * @type {number}
     */
    ;

    return DOMIterator;
  }();
  /**
   * Marks search terms in DOM elements
   * @example
   * new Mark(document.querySelector(".context")).mark("lorem ipsum");
   * @example
   * new Mark(document.querySelector(".context")).markRegExp(/lorem/gmi);
   */


  var Mark$1 = /*#__PURE__*/function () {
    // eslint-disable-line no-unused-vars

    /**
     * @param {HTMLElement|HTMLElement[]|NodeList|string} ctx - The context DOM
     * element, an array of DOM elements, a NodeList or a selector
     */
    function Mark$1(ctx) {
      /**
       * The context of the instance. Either a DOM element, an array of DOM
       * elements, a NodeList or a selector
       * @type {HTMLElement|HTMLElement[]|NodeList|string}
       * @access protected
       */
      this.ctx = ctx;
      /**
       * Specifies if the current browser is a IE (necessary for the node
       * normalization bug workaround). See {@link Mark#unwrapMatches}
       * @type {boolean}
       * @access protected
       */

      this.ie = false;
      var ua = window.navigator.userAgent;

      if (ua.indexOf('MSIE') > -1 || ua.indexOf('Trident') > -1) {
        this.ie = true;
      }
    }
    /**
     * Options defined by the user. They will be initialized from one of the
     * public methods. See {@link Mark#mark}, {@link Mark#markRegExp},
     * {@link Mark#markRanges} and {@link Mark#unmark} for option properties.
     * @type {object}
     * @param {object} [val] - An object that will be merged with defaults
     * @access protected
     */


    var _proto2 = Mark$1.prototype;

    /**
     * Logs a message if log is enabled
     * @param {string} msg - The message to log
     * @param {string} [level="debug"] - The log level, e.g. <code>warn</code>
     * <code>error</code>, <code>debug</code>
     * @access protected
     */
    _proto2.log = function log(msg, level) {
      if (level === void 0) {
        level = 'debug';
      }

      var log = this.opt.log;

      if (!this.opt.debug) {
        return;
      }

      if (typeof log === 'object' && typeof log[level] === 'function') {
        log[level]("mark.js: " + msg);
      }
    }
    /**
     * Escapes a string for usage within a regular expression
     * @param {string} str - The string to escape
     * @return {string}
     * @access protected
     */
    ;

    _proto2.escapeStr = function escapeStr(str) {
      // eslint-disable-next-line no-useless-escape
      return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
    }
    /**
     * Creates a regular expression string to match the specified search
     * term including synonyms, diacritics and accuracy if defined
     * @param  {string} str - The search term to be used
     * @return {string}
     * @access protected
     */
    ;

    _proto2.createRegExp = function createRegExp(str) {
      if (this.opt.wildcards !== 'disabled') {
        str = this.setupWildcardsRegExp(str);
      }

      str = this.escapeStr(str);

      if (Object.keys(this.opt.synonyms).length) {
        str = this.createSynonymsRegExp(str);
      }

      if (this.opt.ignoreJoiners || this.opt.ignorePunctuation.length) {
        str = this.setupIgnoreJoinersRegExp(str);
      }

      if (this.opt.diacritics) {
        str = this.createDiacriticsRegExp(str);
      }

      str = this.createMergedBlanksRegExp(str);

      if (this.opt.ignoreJoiners || this.opt.ignorePunctuation.length) {
        str = this.createJoinersRegExp(str);
      }

      if (this.opt.wildcards !== 'disabled') {
        str = this.createWildcardsRegExp(str);
      }

      str = this.createAccuracyRegExp(str);
      return str;
    }
    /**
     * Creates a regular expression string to match the defined synonyms
     * @param  {string} str - The search term to be used
     * @return {string}
     * @access protected
     */
    ;

    _proto2.createSynonymsRegExp = function createSynonymsRegExp(str) {
      var syn = this.opt.synonyms,
          sens = this.opt.caseSensitive ? '' : 'i',
          // add replacement character placeholder before and after the
      // synonym group
      joinerPlaceholder = this.opt.ignoreJoiners || this.opt.ignorePunctuation.length ? "\0" : '';

      for (var index in syn) {
        if (syn.hasOwnProperty(index)) {
          var value = syn[index],
              k1 = this.opt.wildcards !== 'disabled' ? this.setupWildcardsRegExp(index) : this.escapeStr(index),
              k2 = this.opt.wildcards !== 'disabled' ? this.setupWildcardsRegExp(value) : this.escapeStr(value);

          if (k1 !== '' && k2 !== '') {
            str = str.replace(new RegExp("(" + this.escapeStr(k1) + "|" + this.escapeStr(k2) + ")", "gm" + sens), joinerPlaceholder + ("(" + this.processSynomyms(k1) + "|") + (this.processSynomyms(k2) + ")") + joinerPlaceholder);
          }
        }
      }

      return str;
    }
    /**
     * Setup synonyms to work with ignoreJoiners and or ignorePunctuation
     * @param {string} str - synonym key or value to process
     * @return {string} - processed synonym string
     */
    ;

    _proto2.processSynomyms = function processSynomyms(str) {
      if (this.opt.ignoreJoiners || this.opt.ignorePunctuation.length) {
        str = this.setupIgnoreJoinersRegExp(str);
      }

      return str;
    }
    /**
     * Sets up the regular expression string to allow later insertion of
     * wildcard regular expression matches
     * @param  {string} str - The search term to be used
     * @return {string}
     * @access protected
     */
    ;

    _proto2.setupWildcardsRegExp = function setupWildcardsRegExp(str) {
      // replace single character wildcard with unicode 0001
      str = str.replace(/(?:\\)*\?/g, function (val) {
        return val.charAt(0) === '\\' ? '?' : "\x01";
      }); // replace multiple character wildcard with unicode 0002

      return str.replace(/(?:\\)*\*/g, function (val) {
        return val.charAt(0) === '\\' ? '*' : "\x02";
      });
    }
    /**
     * Sets up the regular expression string to allow later insertion of
     * wildcard regular expression matches
     * @param  {string} str - The search term to be used
     * @return {string}
     * @access protected
     */
    ;

    _proto2.createWildcardsRegExp = function createWildcardsRegExp(str) {
      // default to "enable" (i.e. to not include spaces)
      // "withSpaces" uses `[\\S\\s]` instead of `.` because the latter
      // does not match new line characters
      var spaces = this.opt.wildcards === 'withSpaces';
      return str // replace unicode 0001 with a RegExp class to match any single
      // character, or any single non-whitespace character depending
      // on the setting
      .replace(/\u0001/g, spaces ? '[\\S\\s]?' : '\\S?') // replace unicode 0002 with a RegExp class to match zero or
      // more characters, or zero or more non-whitespace characters
      // depending on the setting
      .replace(/\u0002/g, spaces ? '[\\S\\s]*?' : '\\S*');
    }
    /**
     * Sets up the regular expression string to allow later insertion of
     * designated characters (soft hyphens & zero width characters)
     * @param  {string} str - The search term to be used
     * @return {string}
     * @access protected
     */
    ;

    _proto2.setupIgnoreJoinersRegExp = function setupIgnoreJoinersRegExp(str) {
      // adding a "null" unicode character as it will not be modified by the
      // other "create" regular expression functions
      return str.replace(/[^(|)\\]/g, function (val, indx, original) {
        // don't add a null after an opening "(", around a "|" or before
        // a closing "(", or between an escapement (e.g. \+)
        var nextChar = original.charAt(indx + 1);

        if (/[(|)\\]/.test(nextChar) || nextChar === '') {
          return val;
        } else {
          return val + "\0";
        }
      });
    }
    /**
     * Creates a regular expression string to allow ignoring of designated
     * characters (soft hyphens, zero width characters & punctuation) based on
     * the specified option values of <code>ignorePunctuation</code> and
     * <code>ignoreJoiners</code>
     * @param  {string} str - The search term to be used
     * @return {string}
     * @access protected
     */
    ;

    _proto2.createJoinersRegExp = function createJoinersRegExp(str) {
      var joiner = [];
      var ignorePunctuation = this.opt.ignorePunctuation;

      if (Array.isArray(ignorePunctuation) && ignorePunctuation.length) {
        joiner.push(this.escapeStr(ignorePunctuation.join('')));
      }

      if (this.opt.ignoreJoiners) {
        // u+00ad = soft hyphen
        // u+200b = zero-width space
        // u+200c = zero-width non-joiner
        // u+200d = zero-width joiner
        joiner.push("\\u00ad\\u200b\\u200c\\u200d");
      }

      return joiner.length ? str.split(/\u0000+/).join("[" + joiner.join('') + "]*") : str;
    }
    /**
     * Creates a regular expression string to match diacritics
     * @param  {string} str - The search term to be used
     * @return {string}
     * @access protected
     */
    ;

    _proto2.createDiacriticsRegExp = function createDiacriticsRegExp(str) {
      var sens = this.opt.caseSensitive ? '' : 'i',
          dct = this.opt.caseSensitive ? ['aàáảãạăằắẳẵặâầấẩẫậäåāą', 'AÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ', 'cçćč', 'CÇĆČ', 'dđď', 'DĐĎ', 'eèéẻẽẹêềếểễệëěēę', 'EÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ', 'iìíỉĩịîïī', 'IÌÍỈĨỊÎÏĪ', 'lł', 'LŁ', 'nñňń', 'NÑŇŃ', 'oòóỏõọôồốổỗộơởỡớờợöøō', 'OÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ', 'rř', 'RŘ', 'sšśșş', 'SŠŚȘŞ', 'tťțţ', 'TŤȚŢ', 'uùúủũụưừứửữựûüůū', 'UÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ', 'yýỳỷỹỵÿ', 'YÝỲỶỸỴŸ', 'zžżź', 'ZŽŻŹ'] : ['aàáảãạăằắẳẵặâầấẩẫậäåāąAÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ', 'cçćčCÇĆČ', 'dđďDĐĎ', 'eèéẻẽẹêềếểễệëěēęEÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ', 'iìíỉĩịîïīIÌÍỈĨỊÎÏĪ', 'lłLŁ', 'nñňńNÑŇŃ', 'oòóỏõọôồốổỗộơởỡớờợöøōOÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ', 'rřRŘ', 'sšśșşSŠŚȘŞ', 'tťțţTŤȚŢ', 'uùúủũụưừứửữựûüůūUÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ', 'yýỳỷỹỵÿYÝỲỶỸỴŸ', 'zžżźZŽŻŹ'];
      var handled = [];
      str.split('').forEach(function (ch) {
        dct.every(function (dct) {
          // Check if the character is inside a diacritics list
          if (dct.indexOf(ch) !== -1) {
            // Check if the related diacritics list was not
            // handled yet
            if (handled.indexOf(dct) > -1) {
              return false;
            } // Make sure that the character OR any other
            // character in the diacritics list will be matched


            str = str.replace(new RegExp("[" + dct + "]", "gm" + sens), "[" + dct + "]");
            handled.push(dct);
          }

          return true;
        });
      });
      return str;
    }
    /**
     * Creates a regular expression string that merges whitespace characters
     * including subsequent ones into a single pattern, one or multiple
     * whitespaces
     * @param  {string} str - The search term to be used
     * @return {string}
     * @access protected
     */
    ;

    _proto2.createMergedBlanksRegExp = function createMergedBlanksRegExp(str) {
      return str.replace(/[\s]+/gmi, '[\\s]+');
    }
    /**
     * Creates a regular expression string to match the specified string with
     * the defined accuracy. As in the regular expression of "exactly" can be
     * a group containing a blank at the beginning, all regular expressions will
     * be created with two groups. The first group can be ignored (may contain
     * the said blank), the second contains the actual match
     * @param  {string} str - The searm term to be used
     * @return {str}
     * @access protected
     */
    ;

    _proto2.createAccuracyRegExp = function createAccuracyRegExp(str) {
      var _this7 = this;

      var chars = '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~¡¿';
      var acc = this.opt.accuracy,
          val = typeof acc === 'string' ? acc : acc.value,
          ls = typeof acc === 'string' ? [] : acc.limiters,
          lsJoin = '';
      ls.forEach(function (limiter) {
        lsJoin += "|" + _this7.escapeStr(limiter);
      });

      switch (val) {
        case 'partially':
        default:
          return "()(" + str + ")";

        case 'complementary':
          lsJoin = '\\s' + (lsJoin ? lsJoin : this.escapeStr(chars));
          return "()([^" + lsJoin + "]*" + str + "[^" + lsJoin + "]*)";

        case 'exactly':
          return "(^|\\s" + lsJoin + ")(" + str + ")(?=$|\\s" + lsJoin + ")";
      }
    }
    /**
     * @typedef Mark~separatedKeywords
     * @type {object.<string>}
     * @property {array.<string>} keywords - The list of keywords
     * @property {number} length - The length
     */

    /**
     * Returns a list of keywords dependent on whether separate word search
     * was defined. Also it filters empty keywords
     * @param {array} sv - The array of keywords
     * @return {Mark~separatedKeywords}
     * @access protected
     */
    ;

    _proto2.getSeparatedKeywords = function getSeparatedKeywords(sv) {
      var _this8 = this;

      var stack = [];
      sv.forEach(function (kw) {
        if (!_this8.opt.separateWordSearch) {
          if (kw.trim() && stack.indexOf(kw) === -1) {
            stack.push(kw);
          }
        } else {
          kw.split(' ').forEach(function (kwSplitted) {
            if (kwSplitted.trim() && stack.indexOf(kwSplitted) === -1) {
              stack.push(kwSplitted);
            }
          });
        }
      });
      return {
        // sort because of https://git.io/v6USg
        'keywords': stack.sort(function (a, b) {
          return b.length - a.length;
        }),
        'length': stack.length
      };
    }
    /**
     * Check if a value is a number
     * @param {number|string} value - the value to check;
     * numeric strings allowed
     * @return {boolean}
     * @access protected
     */
    ;

    _proto2.isNumeric = function isNumeric(value) {
      // http://stackoverflow.com/a/16655847/145346
      // eslint-disable-next-line eqeqeq
      return Number(parseFloat(value)) == value;
    }
    /**
     * @typedef Mark~rangeObject
     * @type {object}
     * @property {number} start - The start position within the composite value
     * @property {number} length - The length of the string to mark within the
     * composite value.
     */

    /**
     * @typedef Mark~setOfRanges
     * @type {object[]}
     * @property {Mark~rangeObject}
     */

    /**
     * Returns a processed list of integer offset indexes that do not overlap
     * each other, and remove any string values or additional elements
     * @param {Mark~setOfRanges} array - unprocessed raw array
     * @return {Mark~setOfRanges} - processed array with any invalid entries
     * removed
     * @throws Will throw an error if an array of objects is not passed
     * @access protected
     */
    ;

    _proto2.checkRanges = function checkRanges(array) {
      var _this9 = this;

      // start and length indexes are included in an array of objects
      // [{start: 0, length: 1}, {start: 4, length: 5}]
      // quick validity check of the first entry only
      if (!Array.isArray(array) || Object.prototype.toString.call(array[0]) !== '[object Object]') {
        this.log('markRanges() will only accept an array of objects');
        this.opt.noMatch(array);
        return [];
      }

      var stack = [];
      var last = 0;
      array // acending sort to ensure there is no overlap in start & end
      // offsets
      .sort(function (a, b) {
        return a.start - b.start;
      }).forEach(function (item) {
        var _this9$callNoMatchOnI = _this9.callNoMatchOnInvalidRanges(item, last),
            start = _this9$callNoMatchOnI.start,
            end = _this9$callNoMatchOnI.end,
            valid = _this9$callNoMatchOnI.valid;

        if (valid) {
          // preserve item in case there are extra key:values within
          item.start = start;
          item.length = end - start;
          stack.push(item);
          last = end;
        }
      });
      return stack;
    }
    /**
     * @typedef Mark~validObject
     * @type {object}
     * @property {number} start - The start position within the composite value
     * @property {number} end - The calculated end position within the composite
     * value.
     * @property {boolean} valid - boolean value indicating that the start and
     * calculated end range is valid
     */

    /**
      * Initial validation of ranges for markRanges. Preliminary checks are done
      * to ensure the start and length values exist and are not zero or non-
      * numeric
      * @param {Mark~rangeObject} range - the current range object
      * @param {number} last - last index of range
      * @return {Mark~validObject}
      * @access protected
      */
    ;

    _proto2.callNoMatchOnInvalidRanges = function callNoMatchOnInvalidRanges(range, last) {
      var start,
          end,
          valid = false;

      if (range && typeof range.start !== 'undefined') {
        start = parseInt(range.start, 10);
        end = start + parseInt(range.length, 10); // ignore overlapping values & non-numeric entries

        if (this.isNumeric(range.start) && this.isNumeric(range.length) && end - last > 0 && end - start > 0) {
          valid = true;
        } else {
          this.log('Ignoring invalid or overlapping range: ' + ("" + JSON.stringify(range)));
          this.opt.noMatch(range);
        }
      } else {
        this.log("Ignoring invalid range: " + JSON.stringify(range));
        this.opt.noMatch(range);
      }

      return {
        start: start,
        end: end,
        valid: valid
      };
    }
    /**
     * Check valid range for markRanges. Check ranges with access to the context
     * string. Range values are double checked, lengths that extend the mark
     * beyond the string length are limitied and ranges containing only
     * whitespace are ignored
     * @param {Mark~rangeObject} range - the current range object
     * @param {number} originalLength - original length of the context string
     * @param {string} string - current content string
     * @return {Mark~validObject}
     * @access protected
     */
    ;

    _proto2.checkWhitespaceRanges = function checkWhitespaceRanges(range, originalLength, string) {
      var end,
          valid = true,
          // the max value changes after the DOM is manipulated
      max = string.length,
          // adjust offset to account for wrapped text node
      offset = originalLength - max,
          start = parseInt(range.start, 10) - offset; // make sure to stop at max

      start = start > max ? max : start;
      end = start + parseInt(range.length, 10);

      if (end > max) {
        end = max;
        this.log("End range automatically set to the max value of " + max);
      }

      if (start < 0 || end - start < 0 || start > max || end > max) {
        valid = false;
        this.log("Invalid range: " + JSON.stringify(range));
        this.opt.noMatch(range);
      } else if (string.substring(start, end).replace(/\s+/g, '') === '') {
        valid = false; // whitespace only; even if wrapped it is not visible

        this.log('Skipping whitespace only range: ' + JSON.stringify(range));
        this.opt.noMatch(range);
      }

      return {
        start: start,
        end: end,
        valid: valid
      };
    }
    /**
     * @typedef Mark~getTextNodesDict
     * @type {object.<string>}
     * @property {string} value - The composite value of all text nodes
     * @property {object[]} nodes - An array of objects
     * @property {number} nodes.start - The start position within the composite
     * value
     * @property {number} nodes.end - The end position within the composite
     * value
     * @property {HTMLElement} nodes.node - The DOM text node element
     */

    /**
     * Callback
     * @callback Mark~getTextNodesCallback
     * @param {Mark~getTextNodesDict}
     */

    /**
     * Calls the callback with an object containing all text nodes (including
     * iframe text nodes) with start and end positions and the composite value
     * of them (string)
     * @param {Mark~getTextNodesCallback} cb - Callback
     * @access protected
     */
    ;

    _proto2.getTextNodes = function getTextNodes(cb) {
      var _this10 = this;

      var val = '',
          nodes = [];
      this.iterator.forEachNode(NodeFilter.SHOW_TEXT, function (node) {
        nodes.push({
          start: val.length,
          end: (val += node.textContent).length,
          node: node
        });
      }, function (node) {
        if (_this10.matchesExclude(node.parentNode)) {
          return NodeFilter.FILTER_REJECT;
        } else {
          return NodeFilter.FILTER_ACCEPT;
        }
      }, function () {
        cb({
          value: val,
          nodes: nodes
        });
      });
    }
    /**
     * Checks if an element matches any of the specified exclude selectors. Also
     * it checks for elements in which no marks should be performed (e.g.
     * script and style tags) and optionally already marked elements
     * @param  {HTMLElement} el - The element to check
     * @return {boolean}
     * @access protected
     */
    ;

    _proto2.matchesExclude = function matchesExclude(el) {
      return DOMIterator.matches(el, this.opt.exclude.concat([// ignores the elements itself, not their childrens (selector *)
      'script', 'style', 'title', 'head', 'html']));
    }
    /**
     * Wraps the instance element and class around matches that fit the start
     * and end positions within the node
     * @param  {HTMLElement} node - The DOM text node
     * @param  {number} start - The position where to start wrapping
     * @param  {number} end - The position where to end wrapping
     * @return {HTMLElement} Returns the splitted text node that will appear
     * after the wrapped text node
     * @access protected
     */
    ;

    _proto2.wrapRangeInTextNode = function wrapRangeInTextNode(node, start, end) {
      var hEl = !this.opt.element ? 'mark' : this.opt.element,
          startNode = node.splitText(start),
          ret = startNode.splitText(end - start);
      var repl = document.createElement(hEl);
      repl.setAttribute('data-markjs', 'true');

      if (this.opt.className) {
        repl.setAttribute('class', this.opt.className);
      }

      repl.textContent = startNode.textContent;
      startNode.parentNode.replaceChild(repl, startNode);
      return ret;
    }
    /**
     * @typedef Mark~wrapRangeInMappedTextNodeDict
     * @type {object.<string>}
     * @property {string} value - The composite value of all text nodes
     * @property {object[]} nodes - An array of objects
     * @property {number} nodes.start - The start position within the composite
     * value
     * @property {number} nodes.end - The end position within the composite
     * value
     * @property {HTMLElement} nodes.node - The DOM text node element
     */

    /**
     * Each callback
     * @callback Mark~wrapMatchesEachCallback
     * @param {HTMLElement} node - The wrapped DOM element
     * @param {number} lastIndex - The last matching position within the
     * composite value of text nodes
     */

    /**
     * Filter callback
     * @callback Mark~wrapMatchesFilterCallback
     * @param {HTMLElement} node - The matching text node DOM element
     */

    /**
     * Determines matches by start and end positions using the text node
     * dictionary even across text nodes and calls
     * {@link Mark#wrapRangeInTextNode} to wrap them
     * @param  {Mark~wrapRangeInMappedTextNodeDict} dict - The dictionary
     * @param  {number} start - The start position of the match
     * @param  {number} end - The end position of the match
     * @param  {Mark~wrapMatchesFilterCallback} filterCb - Filter callback
     * @param  {Mark~wrapMatchesEachCallback} eachCb - Each callback
     * @access protected
     */
    ;

    _proto2.wrapRangeInMappedTextNode = function wrapRangeInMappedTextNode(dict, start, end, filterCb, eachCb) {
      var _this11 = this;

      // iterate over all text nodes to find the one matching the positions
      dict.nodes.every(function (n, i) {
        var sibl = dict.nodes[i + 1];

        if (typeof sibl === 'undefined' || sibl.start > start) {
          if (!filterCb(n.node)) {
            return false;
          } // map range from dict.value to text node


          var s = start - n.start,
              e = (end > n.end ? n.end : end) - n.start,
              startStr = dict.value.substr(0, n.start),
              endStr = dict.value.substr(e + n.start);
          n.node = _this11.wrapRangeInTextNode(n.node, s, e); // recalculate positions to also find subsequent matches in the
          // same text node. Necessary as the text node in dict now only
          // contains the splitted part after the wrapped one

          dict.value = startStr + endStr;
          dict.nodes.forEach(function (k, j) {
            if (j >= i) {
              if (dict.nodes[j].start > 0 && j !== i) {
                dict.nodes[j].start -= e;
              }

              dict.nodes[j].end -= e;
            }
          });
          end -= e;
          eachCb(n.node.previousSibling, n.start);

          if (end > n.end) {
            start = n.end;
          } else {
            return false;
          }
        }

        return true;
      });
    }
    /**
     * Filter callback before each wrapping
     * @callback Mark~wrapMatchesFilterCallback
     * @param {string} match - The matching string
     * @param {HTMLElement} node - The text node where the match occurs
     */

    /**
     * Callback for each wrapped element
     * @callback Mark~wrapMatchesEachCallback
     * @param {HTMLElement} element - The marked DOM element
     */

    /**
     * Callback on end
     * @callback Mark~wrapMatchesEndCallback
     */

    /**
     * Wraps the instance element and class around matches within single HTML
     * elements in all contexts
     * @param {RegExp} regex - The regular expression to be searched for
     * @param {number} ignoreGroups - A number indicating the amount of RegExp
     * matching groups to ignore
     * @param {Mark~wrapMatchesFilterCallback} filterCb
     * @param {Mark~wrapMatchesEachCallback} eachCb
     * @param {Mark~wrapMatchesEndCallback} endCb
     * @access protected
     */
    ;

    _proto2.wrapMatches = function wrapMatches(regex, ignoreGroups, filterCb, eachCb, endCb) {
      var _this12 = this;

      var matchIdx = ignoreGroups === 0 ? 0 : ignoreGroups + 1;
      this.getTextNodes(function (dict) {
        dict.nodes.forEach(function (node) {
          node = node.node;
          var match;

          while ((match = regex.exec(node.textContent)) !== null && match[matchIdx] !== '') {
            if (!filterCb(match[matchIdx], node)) {
              continue;
            }

            var pos = match.index;

            if (matchIdx !== 0) {
              for (var i = 1; i < matchIdx; i++) {
                pos += match[i].length;
              }
            }

            node = _this12.wrapRangeInTextNode(node, pos, pos + match[matchIdx].length);
            eachCb(node.previousSibling); // reset index of last match as the node changed and the
            // index isn't valid anymore http://tinyurl.com/htsudjd

            regex.lastIndex = 0;
          }
        });
        endCb();
      });
    }
    /**
     * Callback for each wrapped element
     * @callback Mark~wrapMatchesAcrossElementsEachCallback
     * @param {HTMLElement} element - The marked DOM element
     */

    /**
     * Filter callback before each wrapping
     * @callback Mark~wrapMatchesAcrossElementsFilterCallback
     * @param {string} match - The matching string
     * @param {HTMLElement} node - The text node where the match occurs
     */

    /**
     * Callback on end
     * @callback Mark~wrapMatchesAcrossElementsEndCallback
     */

    /**
     * Wraps the instance element and class around matches across all HTML
     * elements in all contexts
     * @param {RegExp} regex - The regular expression to be searched for
     * @param {number} ignoreGroups - A number indicating the amount of RegExp
     * matching groups to ignore
     * @param {Mark~wrapMatchesAcrossElementsFilterCallback} filterCb
     * @param {Mark~wrapMatchesAcrossElementsEachCallback} eachCb
     * @param {Mark~wrapMatchesAcrossElementsEndCallback} endCb
     * @access protected
     */
    ;

    _proto2.wrapMatchesAcrossElements = function wrapMatchesAcrossElements(regex, ignoreGroups, filterCb, eachCb, endCb) {
      var _this13 = this;

      var matchIdx = ignoreGroups === 0 ? 0 : ignoreGroups + 1;
      this.getTextNodes(function (dict) {
        var match;

        while ((match = regex.exec(dict.value)) !== null && match[matchIdx] !== '') {
          // calculate range inside dict.value
          var start = match.index;

          if (matchIdx !== 0) {
            for (var i = 1; i < matchIdx; i++) {
              start += match[i].length;
            }
          }

          var end = start + match[matchIdx].length; // note that dict will be updated automatically, as it'll change
          // in the wrapping process, due to the fact that text
          // nodes will be splitted

          _this13.wrapRangeInMappedTextNode(dict, start, end, function (node) {
            return filterCb(match[matchIdx], node);
          }, function (node, lastIndex) {
            regex.lastIndex = lastIndex;
            eachCb(node);
          });
        }

        endCb();
      });
    }
    /**
     * Callback for each wrapped element
     * @callback Mark~wrapRangeFromIndexEachCallback
     * @param {HTMLElement} element - The marked DOM element
     * @param {Mark~rangeObject} range - the current range object; provided
     * start and length values will be numeric integers modified from the
     * provided original ranges.
     */

    /**
     * Filter callback before each wrapping
     * @callback Mark~wrapRangeFromIndexFilterCallback
     * @param {HTMLElement} node - The text node which includes the range
     * @param {Mark~rangeObject} range - the current range object
     * @param {string} match - string extracted from the matching range
     * @param {number} counter - A counter indicating the number of all marks
     */

    /**
     * Callback on end
     * @callback Mark~wrapRangeFromIndexEndCallback
     */

    /**
     * Wraps the indicated ranges across all HTML elements in all contexts
     * @param {Mark~setOfRanges} ranges
     * @param {Mark~wrapRangeFromIndexFilterCallback} filterCb
     * @param {Mark~wrapRangeFromIndexEachCallback} eachCb
     * @param {Mark~wrapRangeFromIndexEndCallback} endCb
     * @access protected
     */
    ;

    _proto2.wrapRangeFromIndex = function wrapRangeFromIndex(ranges, filterCb, eachCb, endCb) {
      var _this14 = this;

      this.getTextNodes(function (dict) {
        var originalLength = dict.value.length;
        ranges.forEach(function (range, counter) {
          var _this14$checkWhitespa = _this14.checkWhitespaceRanges(range, originalLength, dict.value),
              start = _this14$checkWhitespa.start,
              end = _this14$checkWhitespa.end,
              valid = _this14$checkWhitespa.valid;

          if (valid) {
            _this14.wrapRangeInMappedTextNode(dict, start, end, function (node) {
              return filterCb(node, range, dict.value.substring(start, end), counter);
            }, function (node) {
              eachCb(node, range);
            });
          }
        });
        endCb();
      });
    }
    /**
     * Unwraps the specified DOM node with its content (text nodes or HTML)
     * without destroying possibly present events (using innerHTML) and
     * normalizes the parent at the end (merge splitted text nodes)
     * @param  {HTMLElement} node - The DOM node to unwrap
     * @access protected
     */
    ;

    _proto2.unwrapMatches = function unwrapMatches(node) {
      var parent = node.parentNode;
      var docFrag = document.createDocumentFragment();

      while (node.firstChild) {
        docFrag.appendChild(node.removeChild(node.firstChild));
      }

      parent.replaceChild(docFrag, node);

      if (!this.ie) {
        // use browser's normalize method
        parent.normalize();
      } else {
        // custom method (needs more time)
        this.normalizeTextNode(parent);
      }
    }
    /**
     * Normalizes text nodes. It's a workaround for the native normalize method
     * that has a bug in IE (see attached link). Should only be used in IE
     * browsers as it's slower than the native method.
     * @see {@link http://tinyurl.com/z5asa8c}
     * @param {HTMLElement} node - The DOM node to normalize
     * @access protected
     */
    ;

    _proto2.normalizeTextNode = function normalizeTextNode(node) {
      if (!node) {
        return;
      }

      if (node.nodeType === 3) {
        while (node.nextSibling && node.nextSibling.nodeType === 3) {
          node.nodeValue += node.nextSibling.nodeValue;
          node.parentNode.removeChild(node.nextSibling);
        }
      } else {
        this.normalizeTextNode(node.firstChild);
      }

      this.normalizeTextNode(node.nextSibling);
    }
    /**
     * Callback when finished
     * @callback Mark~commonDoneCallback
     * @param {number} totalMatches - The number of marked elements
     */

    /**
     * @typedef Mark~commonOptions
     * @type {object.<string>}
     * @property {string} [element="mark"] - HTML element tag name
     * @property {string} [className] - An optional class name
     * @property {string[]} [exclude] - An array with exclusion selectors.
     * Elements matching those selectors will be ignored
     * @property {boolean} [iframes=false] - Whether to search inside iframes
     * @property {Mark~commonDoneCallback} [done]
     * @property {boolean} [debug=false] - Wheter to log messages
     * @property {object} [log=window.console] - Where to log messages (only if
     * debug is true)
     */

    /**
     * Callback for each marked element
     * @callback Mark~markRegExpEachCallback
     * @param {HTMLElement} element - The marked DOM element
     */

    /**
     * Callback if there were no matches
     * @callback Mark~markRegExpNoMatchCallback
     * @param {RegExp} regexp - The regular expression
     */

    /**
     * Callback to filter matches
     * @callback Mark~markRegExpFilterCallback
     * @param {HTMLElement} textNode - The text node which includes the match
     * @param {string} match - The matching string for the RegExp
     * @param {number} counter - A counter indicating the number of all marks
     */

    /**
     * These options also include the common options from
     * {@link Mark~commonOptions}
     * @typedef Mark~markRegExpOptions
     * @type {object.<string>}
     * @property {Mark~markRegExpEachCallback} [each]
     * @property {Mark~markRegExpNoMatchCallback} [noMatch]
     * @property {Mark~markRegExpFilterCallback} [filter]
     */

    /**
     * Marks a custom regular expression
     * @param  {RegExp} regexp - The regular expression
     * @param  {Mark~markRegExpOptions} [opt] - Optional options object
     * @access public
     */
    ;

    _proto2.markRegExp = function markRegExp(regexp, opt) {
      var _this15 = this;

      this.opt = opt;
      this.log("Searching with expression \"" + regexp + "\"");
      var totalMatches = 0,
          fn = 'wrapMatches';

      var eachCb = function eachCb(element) {
        totalMatches++;

        _this15.opt.each(element);
      };

      if (this.opt.acrossElements) {
        fn = 'wrapMatchesAcrossElements';
      }

      this[fn](regexp, this.opt.ignoreGroups, function (match, node) {
        return _this15.opt.filter(node, match, totalMatches);
      }, eachCb, function () {
        if (totalMatches === 0) {
          _this15.opt.noMatch(regexp);
        }

        _this15.opt.done(totalMatches);
      });
    }
    /**
     * Callback for each marked element
     * @callback Mark~markEachCallback
     * @param {HTMLElement} element - The marked DOM element
     */

    /**
     * Callback if there were no matches
     * @callback Mark~markNoMatchCallback
     * @param {RegExp} term - The search term that was not found
     */

    /**
     * Callback to filter matches
     * @callback Mark~markFilterCallback
     * @param {HTMLElement} textNode - The text node which includes the match
     * @param {string} match - The matching term
     * @param {number} totalCounter - A counter indicating the number of all
     * marks
     * @param {number} termCounter - A counter indicating the number of marks
     * for the specific match
     */

    /**
     * @typedef Mark~markAccuracyObject
     * @type {object.<string>}
     * @property {string} value - A accuracy string value
     * @property {string[]} limiters - A custom array of limiters. For example
     * <code>["-", ","]</code>
     */

    /**
     * @typedef Mark~markAccuracySetting
     * @type {string}
     * @property {"partially"|"complementary"|"exactly"|Mark~markAccuracyObject}
     * [accuracy="partially"] - Either one of the following string values:
     * <ul>
     *   <li><i>partially</i>: When searching for "lor" only "lor" inside
     *   "lorem" will be marked</li>
     *   <li><i>complementary</i>: When searching for "lor" the whole word
     *   "lorem" will be marked</li>
     *   <li><i>exactly</i>: When searching for "lor" only those exact words
     *   will be marked. In this example nothing inside "lorem". This value
     *   is equivalent to the previous option <i>wordBoundary</i></li>
     * </ul>
     * Or an object containing two properties:
     * <ul>
     *   <li><i>value</i>: One of the above named string values</li>
     *   <li><i>limiters</i>: A custom array of string limiters for accuracy
     *   "exactly" or "complementary"</li>
     * </ul>
     */

    /**
     * @typedef Mark~markWildcardsSetting
     * @type {string}
     * @property {"disabled"|"enabled"|"withSpaces"}
     * [wildcards="disabled"] - Set to any of the following string values:
     * <ul>
     *   <li><i>disabled</i>: Disable wildcard usage</li>
     *   <li><i>enabled</i>: When searching for "lor?m", the "?" will match zero
     *   or one non-space character (e.g. "lorm", "loram", "lor3m", etc). When
     *   searching for "lor*m", the "*" will match zero or more non-space
     *   characters (e.g. "lorm", "loram", "lor123m", etc).</li>
     *   <li><i>withSpaces</i>: When searching for "lor?m", the "?" will
     *   match zero or one space or non-space character (e.g. "lor m", "loram",
     *   etc). When searching for "lor*m", the "*" will match zero or more space
     *   or non-space characters (e.g. "lorm", "lore et dolor ipsum", "lor: m",
     *   etc).</li>
     * </ul>
     */

    /**
     * @typedef Mark~markIgnorePunctuationSetting
     * @type {string[]}
     * @property {string} The strings in this setting will contain punctuation
     * marks that will be ignored:
     * <ul>
     *   <li>These punctuation marks can be between any characters, e.g. setting
     *   this option to <code>["'"]</code> would match "Worlds", "World's" and
     *   "Wo'rlds"</li>
     *   <li>One or more apostrophes between the letters would still produce a
     *   match (e.g. "W'o''r'l'd's").</li>
     *   <li>A typical setting for this option could be as follows:
     *   <pre>ignorePunctuation: ":;.,-–—‒_(){}[]!'\"+=".split(""),</pre> This
     *   setting includes common punctuation as well as a minus, en-dash,
     *   em-dash and figure-dash
     *   ({@link https://en.wikipedia.org/wiki/Dash#Figure_dash ref}), as well
     *   as an underscore.</li>
     * </ul>
     */

    /**
     * These options also include the common options from
     * {@link Mark~commonOptions}
     * @typedef Mark~markOptions
     * @type {object.<string>}
     * @property {boolean} [separateWordSearch=true] - Whether to search for
     * each word separated by a blank instead of the complete term
     * @property {boolean} [diacritics=true] - If diacritic characters should be
     * matched. ({@link https://en.wikipedia.org/wiki/Diacritic Diacritics})
     * @property {object} [synonyms] - An object with synonyms. The key will be
     * a synonym for the value and the value for the key
     * @property {Mark~markAccuracySetting} [accuracy]
     * @property {Mark~markWildcardsSetting} [wildcards]
     * @property {boolean} [acrossElements=false] - Whether to find matches
     * across HTML elements. By default, only matches within single HTML
     * elements will be found
     * @property {boolean} [ignoreJoiners=false] - Whether to ignore word
     * joiners inside of key words. These include soft-hyphens, zero-width
     * space, zero-width non-joiners and zero-width joiners.
     * @property {Mark~markIgnorePunctuationSetting} [ignorePunctuation]
     * @property {Mark~markEachCallback} [each]
     * @property {Mark~markNoMatchCallback} [noMatch]
     * @property {Mark~markFilterCallback} [filter]
     */

    /**
     * Marks the specified search terms
     * @param {string|string[]} [sv] - Search value, either a search string or
     * an array containing multiple search strings
     * @param  {Mark~markOptions} [opt] - Optional options object
     * @access public
     */
    ;

    _proto2.mark = function mark(sv, opt) {
      var _this16 = this;

      this.opt = opt;
      var totalMatches = 0,
          fn = 'wrapMatches';

      var _this$getSeparatedKey = this.getSeparatedKeywords(typeof sv === 'string' ? [sv] : sv),
          kwArr = _this$getSeparatedKey.keywords,
          kwArrLen = _this$getSeparatedKey.length,
          sens = this.opt.caseSensitive ? '' : 'i',
          handler = function handler(kw) {
        // async function calls as iframes are async too
        var regex = new RegExp(_this16.createRegExp(kw), "gm" + sens),
            matches = 0;

        _this16.log("Searching with expression \"" + regex + "\"");

        _this16[fn](regex, 1, function (term, node) {
          return _this16.opt.filter(node, kw, totalMatches, matches);
        }, function (element) {
          matches++;
          totalMatches++;

          _this16.opt.each(element);
        }, function () {
          if (matches === 0) {
            _this16.opt.noMatch(kw);
          }

          if (kwArr[kwArrLen - 1] === kw) {
            _this16.opt.done(totalMatches);
          } else {
            handler(kwArr[kwArr.indexOf(kw) + 1]);
          }
        });
      };

      if (this.opt.acrossElements) {
        fn = 'wrapMatchesAcrossElements';
      }

      if (kwArrLen === 0) {
        this.opt.done(totalMatches);
      } else {
        handler(kwArr[0]);
      }
    }
    /**
     * Callback for each marked element
     * @callback Mark~markRangesEachCallback
     * @param {HTMLElement} element - The marked DOM element
     * @param {array} range - array of range start and end points
     */

    /**
     * Callback if a processed range is invalid, out-of-bounds, overlaps another
     * range, or only matches whitespace
     * @callback Mark~markRangesNoMatchCallback
     * @param {Mark~rangeObject} range - a range object
     */

    /**
     * Callback to filter matches
     * @callback Mark~markRangesFilterCallback
     * @param {HTMLElement} node - The text node which includes the range
     * @param {array} range - array of range start and end points
     * @param {string} match - string extracted from the matching range
     * @param {number} counter - A counter indicating the number of all marks
     */

    /**
     * These options also include the common options from
     * {@link Mark~commonOptions}
     * @typedef Mark~markRangesOptions
     * @type {object.<string>}
     * @property {Mark~markRangesEachCallback} [each]
     * @property {Mark~markRangesNoMatchCallback} [noMatch]
     * @property {Mark~markRangesFilterCallback} [filter]
     */

    /**
     * Marks an array of objects containing a start with an end or length of the
     * string to mark
     * @param  {Mark~setOfRanges} rawRanges - The original (preprocessed)
     * array of objects
     * @param  {Mark~markRangesOptions} [opt] - Optional options object
     * @access public
     */
    ;

    _proto2.markRanges = function markRanges(rawRanges, opt) {
      var _this17 = this;

      this.opt = opt;
      var totalMatches = 0,
          ranges = this.checkRanges(rawRanges);

      if (ranges && ranges.length) {
        this.log('Starting to mark with the following ranges: ' + JSON.stringify(ranges));
        this.wrapRangeFromIndex(ranges, function (node, range, match, counter) {
          return _this17.opt.filter(node, range, match, counter);
        }, function (element, range) {
          totalMatches++;

          _this17.opt.each(element, range);
        }, function () {
          _this17.opt.done(totalMatches);
        });
      } else {
        this.opt.done(totalMatches);
      }
    }
    /**
     * Removes all marked elements inside the context with their HTML and
     * normalizes the parent at the end
     * @param  {Mark~commonOptions} [opt] - Optional options object
     * @access public
     */
    ;

    _proto2.unmark = function unmark(opt) {
      var _this18 = this;

      this.opt = opt;
      var sel = this.opt.element ? this.opt.element : '*';
      sel += '[data-markjs]';

      if (this.opt.className) {
        sel += "." + this.opt.className;
      }

      this.log("Removal selector \"" + sel + "\"");
      this.iterator.forEachNode(NodeFilter.SHOW_ELEMENT, function (node) {
        _this18.unwrapMatches(node);
      }, function (node) {
        var matchesSel = DOMIterator.matches(node, sel),
            matchesExclude = _this18.matchesExclude(node);

        if (!matchesSel || matchesExclude) {
          return NodeFilter.FILTER_REJECT;
        } else {
          return NodeFilter.FILTER_ACCEPT;
        }
      }, this.opt.done);
    };

    _createClass(Mark$1, [{
      key: "opt",
      get: function get() {
        return this._opt;
      }
      /**
       * An instance of DOMIterator
       * @type {DOMIterator}
       * @access protected
       */
      ,
      set: function set(val) {
        this._opt = Object.assign({}, {
          'element': '',
          'className': '',
          'exclude': [],
          'iframes': false,
          'iframesTimeout': 5000,
          'separateWordSearch': true,
          'diacritics': true,
          'synonyms': {},
          'accuracy': 'partially',
          'acrossElements': false,
          'caseSensitive': false,
          'ignoreJoiners': false,
          'ignoreGroups': 0,
          'ignorePunctuation': [],
          'wildcards': 'disabled',
          'each': function each() {},
          'noMatch': function noMatch() {},
          'filter': function filter() {
            return true;
          },
          'done': function done() {},
          'debug': false,
          'log': window.console
        }, val);
      }
    }, {
      key: "iterator",
      get: function get() {
        // always return new instance in case there were option changes
        return new DOMIterator(this.ctx, this.opt.iframes, this.opt.exclude, this.opt.iframesTimeout);
      }
    }]);

    return Mark$1;
  }();

  function Mark(ctx) {
    var _this19 = this;

    var instance = new Mark$1(ctx);

    this.mark = function (sv, opt) {
      instance.mark(sv, opt);
      return _this19;
    };

    this.markRegExp = function (sv, opt) {
      instance.markRegExp(sv, opt);
      return _this19;
    };

    this.markRanges = function (sv, opt) {
      instance.markRanges(sv, opt);
      return _this19;
    };

    this.unmark = function (opt) {
      instance.unmark(opt);
      return _this19;
    };

    return this;
  }

  var defaultOptions = {
    exclude: [],
    separateWordSearch: true,
    accuracy: 'partially',
    diacritics: true,
    synonyms: {},
    iframes: false,
    iframesTimeout: 5000,
    acrossElements: true,
    caseSensitive: false,
    ignoreJoiners: false,
    wildcards: 'disabled',
    compatibility: false
  };

  if (Joomla.getOptions && typeof Joomla.getOptions === 'function' && Joomla.getOptions('highlight')) {
    var scriptOptions = Joomla.getOptions('highlight');
    scriptOptions.forEach(function (currentOpts) {
      var options = Object.assign({}, defaultOptions, currentOpts); // Continue only if the element exists

      if (!options.compatibility) {
        var element = document.querySelector("." + options.class);

        if (element) {
          var instance = new Mark(element); // Loop through the terms

          options.highLight.forEach(function (term) {
            instance.mark(term, options);
          });
        }
      } else {
        var start = document.querySelector("#" + options.start);
        document.querySelector("#" + options.end);
        var parent = start.parentNode;
        var targetNodes = [];
        var allElems = Array.from(parent.childNodes);
        allElems.forEach(function (element) {
          {
            return;
          }
        });
        targetNodes.forEach(function (node) {
          var instance = new Mark(node); // Loop through the terms

          options.highLight.map(function (term) {
            return instance.mark(term, options);
          });
        });
      }
    });
  }

})();
