/*!***************************************************
* mark.js v9.0.0
* https://markjs.io/
* Copyright (c) 2014–2018, Julian Kühnel
* Released under the MIT license https://git.io/vwTVl
*****************************************************/

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jquery')) :
  typeof define === 'function' && define.amd ? define(['jquery'], factory) :
  (global.Mark = factory(global.jQuery));
}(this, (function ($) { 'use strict';

  $ = $ && $.hasOwnProperty('default') ? $['default'] : $;

  class DOMIterator {
    constructor(ctx, iframes = true, exclude = [], iframesTimeout = 5000) {
      this.ctx = ctx;
      this.iframes = iframes;
      this.exclude = exclude;
      this.iframesTimeout = iframesTimeout;
    }
    static matches(element, selector) {
      const selectors = typeof selector === 'string' ? [selector] : selector,
        fn = (
          element.matches ||
          element.matchesSelector ||
          element.msMatchesSelector ||
          element.mozMatchesSelector ||
          element.oMatchesSelector ||
          element.webkitMatchesSelector
        );
      if (fn) {
        let match = false;
        selectors.every(sel => {
          if (fn.call(element, sel)) {
            match = true;
            return false;
          }
          return true;
        });
        return match;
      } else {
        return false;
      }
    }
    getContexts() {
      let ctx,
        filteredCtx = [];
      if (typeof this.ctx === 'undefined' || !this.ctx) {
        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 {
        ctx = [this.ctx];
      }
      ctx.forEach(ctx => {
        const isDescendant = filteredCtx.filter(contexts => {
          return contexts.contains(ctx);
        }).length > 0;
        if (filteredCtx.indexOf(ctx) === -1 && !isDescendant) {
          filteredCtx.push(ctx);
        }
      });
      return filteredCtx;
    }
    getIframeContents(ifr, successFn, errorFn = () => {}) {
      let doc;
      try {
        const ifrWin = ifr.contentWindow;
        doc = ifrWin.document;
        if (!ifrWin || !doc) {
          throw new Error('iframe inaccessible');
        }
      } catch (e) {
        errorFn();
      }
      if (doc) {
        successFn(doc);
      }
    }
    isIframeBlank(ifr) {
      const bl = 'about:blank',
        src = ifr.getAttribute('src').trim(),
        href = ifr.contentWindow.location.href;
      return href === bl && src !== bl && src;
    }
    observeIframeLoad(ifr, successFn, errorFn) {
      let called = false,
        tout = null;
      const listener = () => {
        if (called) {
          return;
        }
        called = true;
        clearTimeout(tout);
        try {
          if (!this.isIframeBlank(ifr)) {
            ifr.removeEventListener('load', listener);
            this.getIframeContents(ifr, successFn, errorFn);
          }
        } catch (e) {
          errorFn();
        }
      };
      ifr.addEventListener('load', listener);
      tout = setTimeout(listener, this.iframesTimeout);
    }
    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) {
        errorFn();
      }
    }
    waitForIframes(ctx, done) {
      let eachCalled = 0;
      this.forEachIframe(ctx, () => true, ifr => {
        eachCalled++;
        this.waitForIframes(ifr.querySelector('html'), () => {
          if (!(--eachCalled)) {
            done();
          }
        });
      }, handled => {
        if (!handled) {
          done();
        }
      });
    }
    forEachIframe(ctx, filter, each, end = () => {}) {
      let ifr = ctx.querySelectorAll('iframe'),
        open = ifr.length,
        handled = 0;
      ifr = Array.prototype.slice.call(ifr);
      const checkEnd = () => {
        if (--open <= 0) {
          end(handled);
        }
      };
      if (!open) {
        checkEnd();
      }
      ifr.forEach(ifr => {
        if (DOMIterator.matches(ifr, this.exclude)) {
          checkEnd();
        } else {
          this.onIframeReady(ifr, con => {
            if (filter(ifr)) {
              handled++;
              each(con);
            }
            checkEnd();
          }, checkEnd);
        }
      });
    }
    createIterator(ctx, whatToShow, filter) {
      return document.createNodeIterator(ctx, whatToShow, filter, false);
    }
    createInstanceOnIframe(contents) {
      return new DOMIterator(contents.querySelector('html'), this.iframes);
    }
    compareNodeIframe(node, prevNode, ifr) {
      const compCurr = node.compareDocumentPosition(ifr),
        prev = Node.DOCUMENT_POSITION_PRECEDING;
      if (compCurr & prev) {
        if (prevNode !== null) {
          const compPrev = prevNode.compareDocumentPosition(ifr),
            after = Node.DOCUMENT_POSITION_FOLLOWING;
          if (compPrev & after) {
            return true;
          }
        } else {
          return true;
        }
      }
      return false;
    }
    getIteratorNode(itr) {
      const prevNode = itr.previousNode();
      let node;
      if (prevNode === null) {
        node = itr.nextNode();
      } else {
        node = itr.nextNode() && itr.nextNode();
      }
      return {
        prevNode,
        node
      };
    }
    checkIframeFilter(node, prevNode, currIfr, ifr) {
      let key = false,
        handled = false;
      ifr.forEach((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;
    }
    handleOpenIframes(ifr, whatToShow, eCb, fCb) {
      ifr.forEach(ifrDict => {
        if (!ifrDict.handled) {
          this.getIframeContents(ifrDict.val, con => {
            this.createInstanceOnIframe(con).forEachNode(
              whatToShow, eCb, fCb
            );
          });
        }
      });
    }
    iterateThroughNodes(whatToShow, ctx, eachCb, filterCb, doneCb) {
      const itr = this.createIterator(ctx, whatToShow, filterCb);
      let ifr = [],
        elements = [],
        node, prevNode, retrieveNodes = () => {
          ({
            prevNode,
            node
          } = this.getIteratorNode(itr));
          return node;
        };
      while (retrieveNodes()) {
        if (this.iframes) {
          this.forEachIframe(ctx, currIfr => {
            return this.checkIframeFilter(node, prevNode, currIfr, ifr);
          }, con => {
            this.createInstanceOnIframe(con).forEachNode(
              whatToShow, ifrNode => elements.push(ifrNode), filterCb
            );
          });
        }
        elements.push(node);
      }
      elements.forEach(node => {
        eachCb(node);
      });
      if (this.iframes) {
        this.handleOpenIframes(ifr, whatToShow, eachCb, filterCb);
      }
      doneCb();
    }
    forEachNode(whatToShow, each, filter, done = () => {}) {
      const contexts = this.getContexts();
      let open = contexts.length;
      if (!open) {
        done();
      }
      contexts.forEach(ctx => {
        const ready = () => {
          this.iterateThroughNodes(whatToShow, ctx, each, filter, () => {
            if (--open <= 0) {
              done();
            }
          });
        };
        if (this.iframes) {
          this.waitForIframes(ctx, ready);
        } else {
          ready();
        }
      });
    }
  }

  class RegExpCreator {
    constructor(options) {
      this.opt = Object.assign({}, {
        'diacritics': true,
        'synonyms': {},
        'accuracy': 'partially',
        'caseSensitive': false,
        'ignoreJoiners': false,
        'ignorePunctuation': [],
        'wildcards': 'disabled'
      }, options);
    }
    create(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 new RegExp(str, `gm${this.opt.caseSensitive ? '' : 'i'}`);
    }
    sortByLength(arry) {
      return arry.sort((a, b) => a.length === b.length ?
        (a > b ? 1 : -1) :
        b.length - a.length
      );
    }
    escapeStr(str) {
      return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
    }
    createSynonymsRegExp(str) {
      const syn = this.opt.synonyms,
        sens = this.opt.caseSensitive ? '' : 'i',
        joinerPlaceholder = this.opt.ignoreJoiners ||
        this.opt.ignorePunctuation.length ? '\u0000' : '';
      for (let index in syn) {
        if (syn.hasOwnProperty(index)) {
          let keys = Array.isArray(syn[index]) ? syn[index] : [syn[index]];
          keys.unshift(index);
          keys = this.sortByLength(keys).map(key => {
            if (this.opt.wildcards !== 'disabled') {
              key = this.setupWildcardsRegExp(key);
            }
            key = this.escapeStr(key);
            return key;
          }).filter(k => k !== '');
          if (keys.length > 1) {
            str = str.replace(
              new RegExp(
                `(${keys.map(k => this.escapeStr(k)).join('|')})`,
                `gm${sens}`
              ),
              joinerPlaceholder +
              `(${keys.map(k => this.processSynonyms(k)).join('|')})` +
              joinerPlaceholder
            );
          }
        }
      }
      return str;
    }
    processSynonyms(str) {
      if (this.opt.ignoreJoiners || this.opt.ignorePunctuation.length) {
        str = this.setupIgnoreJoinersRegExp(str);
      }
      return str;
    }
    setupWildcardsRegExp(str) {
      str = str.replace(/(?:\\)*\?/g, val => {
        return val.charAt(0) === '\\' ? '?' : '\u0001';
      });
      return str.replace(/(?:\\)*\*/g, val => {
        return val.charAt(0) === '\\' ? '*' : '\u0002';
      });
    }
    createWildcardsRegExp(str) {
      let spaces = this.opt.wildcards === 'withSpaces';
      return str
        .replace(/\u0001/g, spaces ? '[\\S\\s]?' : '\\S?')
        .replace(/\u0002/g, spaces ? '[\\S\\s]*?' : '\\S*');
    }
    setupIgnoreJoinersRegExp(str) {
      return str.replace(/[^(|)\\]/g, (val, indx, original) => {
        let nextChar = original.charAt(indx + 1);
        if (/[(|)\\]/.test(nextChar) || nextChar === '') {
          return val;
        } else {
          return val + '\u0000';
        }
      });
    }
    createJoinersRegExp(str) {
      let joiner = [];
      const ignorePunctuation = this.opt.ignorePunctuation;
      if (Array.isArray(ignorePunctuation) && ignorePunctuation.length) {
        joiner.push(this.escapeStr(ignorePunctuation.join('')));
      }
      if (this.opt.ignoreJoiners) {
        joiner.push('\\u00ad\\u200b\\u200c\\u200d');
      }
      return joiner.length ?
        str.split(/\u0000+/).join(`[${joiner.join('')}]*`) :
        str;
    }
    createDiacriticsRegExp(str) {
      const 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ŽŻŹ'
        ];
      let handled = [];
      str.split('').forEach(ch => {
        dct.every(dct => {
          if (dct.indexOf(ch) !== -1) {
            if (handled.indexOf(dct) > -1) {
              return false;
            }
            str = str.replace(
              new RegExp(`[${dct}]`, `gm${sens}`), `[${dct}]`
            );
            handled.push(dct);
          }
          return true;
        });
      });
      return str;
    }
    createMergedBlanksRegExp(str) {
      return str.replace(/[\s]+/gmi, '[\\s]+');
    }
    createAccuracyRegExp(str) {
      const chars = '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~¡¿';
      let acc = this.opt.accuracy,
        val = typeof acc === 'string' ? acc : acc.value,
        ls = typeof acc === 'string' ? [] : acc.limiters,
        lsJoin = '';
      ls.forEach(limiter => {
        lsJoin += `|${this.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})`;
      }
    }
  }

  class Mark {
    constructor(ctx) {
      this.ctx = ctx;
      this.ie = false;
      const ua = window.navigator.userAgent;
      if (ua.indexOf('MSIE') > -1 || ua.indexOf('Trident') > -1) {
        this.ie = true;
      }
    }
    set opt(val) {
      this._opt = Object.assign({}, {
        'element': '',
        'className': '',
        'exclude': [],
        'iframes': false,
        'iframesTimeout': 5000,
        'separateWordSearch': true,
        'acrossElements': false,
        'ignoreGroups': 0,
        'each': () => {},
        'noMatch': () => {},
        'filter': () => true,
        'done': () => {},
        'debug': false,
        'log': window.console
      }, val);
    }
    get opt() {
      return this._opt;
    }
    get iterator() {
      return new DOMIterator(
        this.ctx,
        this.opt.iframes,
        this.opt.exclude,
        this.opt.iframesTimeout
      );
    }
    log(msg, level = 'debug') {
      const log = this.opt.log;
      if (!this.opt.debug) {
        return;
      }
      if (typeof log === 'object' && typeof log[level] === 'function') {
        log[level](`mark.js: ${msg}`);
      }
    }
    getSeparatedKeywords(sv) {
      let stack = [];
      sv.forEach(kw => {
        if (!this.opt.separateWordSearch) {
          if (kw.trim() && stack.indexOf(kw) === -1) {
            stack.push(kw);
          }
        } else {
          kw.split(' ').forEach(kwSplitted => {
            if (kwSplitted.trim() && stack.indexOf(kwSplitted) === -1) {
              stack.push(kwSplitted);
            }
          });
        }
      });
      return {
        'keywords': stack.sort((a, b) => {
          return b.length - a.length;
        }),
        'length': stack.length
      };
    }
    isNumeric(value) {
      return Number(parseFloat(value)) == value;
    }
    checkRanges(array) {
      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 [];
      }
      const stack = [];
      let last = 0;
      array
        .sort((a, b) => {
          return a.start - b.start;
        })
        .forEach(item => {
          let {start, end, valid} = this.callNoMatchOnInvalidRanges(item, last);
          if (valid) {
            item.start = start;
            item.length = end - start;
            stack.push(item);
            last = end;
          }
        });
      return stack;
    }
    callNoMatchOnInvalidRanges(range, last) {
      let start, end,
        valid = false;
      if (range && typeof range.start !== 'undefined') {
        start = parseInt(range.start, 10);
        end = start + parseInt(range.length, 10);
        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
      };
    }
    checkWhitespaceRanges(range, originalLength, string) {
      let end,
        valid = true,
        max = string.length,
        offset = originalLength - max,
        start = parseInt(range.start, 10) - offset;
      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;
        this.log('Skipping whitespace only range: ' + JSON.stringify(range));
        this.opt.noMatch(range);
      }
      return {
        start: start,
        end: end,
        valid: valid
      };
    }
    getTextNodes(cb) {
      let val = '',
        nodes = [];
      this.iterator.forEachNode(NodeFilter.SHOW_TEXT, node => {
        nodes.push({
          start: val.length,
          end: (val += node.textContent).length,
          node
        });
      }, node => {
        if (this.matchesExclude(node.parentNode)) {
          return NodeFilter.FILTER_REJECT;
        } else {
          return NodeFilter.FILTER_ACCEPT;
        }
      }, () => {
        cb({
          value: val,
          nodes: nodes
        });
      });
    }
    matchesExclude(el) {
      return DOMIterator.matches(el, this.opt.exclude.concat([
        'script', 'style', 'title', 'head', 'html'
      ]));
    }
    wrapRangeInTextNode(node, start, end) {
      const hEl = !this.opt.element ? 'mark' : this.opt.element,
        startNode = node.splitText(start),
        ret = startNode.splitText(end - start);
      let 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;
    }
    wrapRangeInMappedTextNode(dict, start, end, filterCb, eachCb) {
      dict.nodes.every((n, i) => {
        const sibl = dict.nodes[i + 1];
        if (typeof sibl === 'undefined' || sibl.start > start) {
          if (!filterCb(n.node)) {
            return false;
          }
          const 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 = this.wrapRangeInTextNode(n.node, s, e);
          dict.value = startStr + endStr;
          dict.nodes.forEach((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;
      });
    }
    wrapGroups(node, pos, len, eachCb) {
      node = this.wrapRangeInTextNode(node, pos, pos + len);
      eachCb(node.previousSibling);
      return node;
    }
    separateGroups(node, match, matchIdx, filterCb, eachCb) {
      let matchLen = match.length;
      for (let i = 1; i < matchLen; i++) {
        let pos = node.textContent.indexOf(match[i]);
        if (match[i] && pos > -1 && filterCb(match[i], node)) {
          node = this.wrapGroups(node, pos, match[i].length, eachCb);
        }
      }
      return node;
    }
    wrapMatches(regex, ignoreGroups, filterCb, eachCb, endCb) {
      const matchIdx = ignoreGroups === 0 ? 0 : ignoreGroups + 1;
      this.getTextNodes(dict => {
        dict.nodes.forEach(node => {
          node = node.node;
          let match;
          while (
            (match = regex.exec(node.textContent)) !== null &&
            match[matchIdx] !== ''
          ) {
            if (this.opt.separateGroups) {
              node = this.separateGroups(
                node,
                match,
                matchIdx,
                filterCb,
                eachCb
              );
            } else {
              if (!filterCb(match[matchIdx], node)) {
                continue;
              }
              let pos = match.index;
              if (matchIdx !== 0) {
                for (let i = 1; i < matchIdx; i++) {
                  pos += match[i].length;
                }
              }
              node = this.wrapGroups(node, pos, match[matchIdx].length, eachCb);
            }
            regex.lastIndex = 0;
          }
        });
        endCb();
      });
    }
    wrapMatchesAcrossElements(regex, ignoreGroups, filterCb, eachCb, endCb) {
      const matchIdx = ignoreGroups === 0 ? 0 : ignoreGroups + 1;
      this.getTextNodes(dict => {
        let match;
        while (
          (match = regex.exec(dict.value)) !== null &&
          match[matchIdx] !== ''
        ) {
          let start = match.index;
          if (matchIdx !== 0) {
            for (let i = 1; i < matchIdx; i++) {
              start += match[i].length;
            }
          }
          const end = start + match[matchIdx].length;
          this.wrapRangeInMappedTextNode(dict, start, end, node => {
            return filterCb(match[matchIdx], node);
          }, (node, lastIndex) => {
            regex.lastIndex = lastIndex;
            eachCb(node);
          });
        }
        endCb();
      });
    }
    wrapRangeFromIndex(ranges, filterCb, eachCb, endCb) {
      this.getTextNodes(dict => {
        const originalLength = dict.value.length;
        ranges.forEach((range, counter) => {
          let {start, end, valid} = this.checkWhitespaceRanges(
            range,
            originalLength,
            dict.value
          );
          if (valid) {
            this.wrapRangeInMappedTextNode(dict, start, end, node => {
              return filterCb(
                node,
                range,
                dict.value.substring(start, end),
                counter
              );
            }, node => {
              eachCb(node, range);
            });
          }
        });
        endCb();
      });
    }
    unwrapMatches(node) {
      const parent = node.parentNode;
      let docFrag = document.createDocumentFragment();
      while (node.firstChild) {
        docFrag.appendChild(node.removeChild(node.firstChild));
      }
      parent.replaceChild(docFrag, node);
      if (!this.ie) {
        parent.normalize();
      } else {
        this.normalizeTextNode(parent);
      }
    }
    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);
    }
    markRegExp(regexp, opt) {
      this.opt = opt;
      this.log(`Searching with expression "${regexp}"`);
      let totalMatches = 0,
        fn = 'wrapMatches';
      const eachCb = element => {
        totalMatches++;
        this.opt.each(element);
      };
      if (this.opt.acrossElements) {
        fn = 'wrapMatchesAcrossElements';
      }
      this[fn](regexp, this.opt.ignoreGroups, (match, node) => {
        return this.opt.filter(node, match, totalMatches);
      }, eachCb, () => {
        if (totalMatches === 0) {
          this.opt.noMatch(regexp);
        }
        this.opt.done(totalMatches);
      });
    }
    mark(sv, opt) {
      this.opt = opt;
      let totalMatches = 0,
        fn = 'wrapMatches';
      const {
          keywords: kwArr,
          length: kwArrLen
        } = this.getSeparatedKeywords(typeof sv === 'string' ? [sv] : sv),
        handler = kw => {
          const regex = new RegExpCreator(this.opt).create(kw);
          let matches = 0;
          this.log(`Searching with expression "${regex}"`);
          this[fn](regex, 1, (term, node) => {
            return this.opt.filter(node, kw, totalMatches, matches);
          }, element => {
            matches++;
            totalMatches++;
            this.opt.each(element);
          }, () => {
            if (matches === 0) {
              this.opt.noMatch(kw);
            }
            if (kwArr[kwArrLen - 1] === kw) {
              this.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]);
      }
    }
    markRanges(rawRanges, opt) {
      this.opt = opt;
      let 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, (node, range, match, counter) => {
            return this.opt.filter(node, range, match, counter);
          }, (element, range) => {
            totalMatches++;
            this.opt.each(element, range);
          }, () => {
            this.opt.done(totalMatches);
          }
        );
      } else {
        this.opt.done(totalMatches);
      }
    }
    unmark(opt) {
      this.opt = opt;
      let 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, node => {
        this.unwrapMatches(node);
      }, node => {
        const matchesSel = DOMIterator.matches(node, sel),
          matchesExclude = this.matchesExclude(node);
        if (!matchesSel || matchesExclude) {
          return NodeFilter.FILTER_REJECT;
        } else {
          return NodeFilter.FILTER_ACCEPT;
        }
      }, this.opt.done);
    }
  }

  $.fn.mark = function(sv, opt) {
    new Mark(this.get()).mark(sv, opt);
    return this;
  };
  $.fn.markRegExp = function(regexp, opt) {
    new Mark(this.get()).markRegExp(regexp, opt);
    return this;
  };
  $.fn.markRanges = function(ranges, opt) {
    new Mark(this.get()).markRanges(ranges, opt);
    return this;
  };
  $.fn.unmark = function(opt) {
    new Mark(this.get()).unmark(opt);
    return this;
  };

  return $;

})));
