zaira-ardor / Collect EBA Reporting Modules

// ==UserScript==
// @name         Collect EBA Reporting Modules
// @namespace    https://work.swindle.net/
// @version      v1.6.2
// @license      MIT
// @description  Extract and transform EBA regulatory module metadata.
// @author       Gregory Swindle <greg@work.swindle.net>
// @match        https://www.eba.europa.eu/reporting-time-traveller
// @match        https://www.eba.europa.eu/reporting-time-traveller/reporting-by-name*
// @grant        none
// @require      https://raw.githubusercontent.com/lodash/lodash/4.17.15-npm/lodash.js
// ==/UserScript==

/** <script src="semver.browser.js"> **/
;(function (exports) {
  // export the class if we are in a Node-like system.
  if (typeof module === 'object' && module.exports === exports)
    exports = module.exports = SemVer

  // The debug function is excluded entirely from the minified version.

  // Note: this is the semver.org version of the spec that it implements
  // Not necessarily the package version of this code.
  exports.SEMVER_SPEC_VERSION = '2.0.0'

  var MAX_LENGTH = 256
  var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991

  // The actual regexps go on exports.re
  var re = (exports.re = [])
  var src = (exports.src = [])
  var R = 0

  // The following Regular Expressions can be used for tokenizing,
  // validating, and parsing SemVer version strings.

  // ## Numeric Identifier
  // A single `0`, or a non-zero digit followed by zero or more digits.

  var NUMERICIDENTIFIER = R++
  src[NUMERICIDENTIFIER] = '0|[1-9]\\d*'
  var NUMERICIDENTIFIERLOOSE = R++
  src[NUMERICIDENTIFIERLOOSE] = '[0-9]+'

  // ## Non-numeric Identifier
  // Zero or more digits, followed by a letter or hyphen, and then zero or
  // more letters, digits, or hyphens.

  var NONNUMERICIDENTIFIER = R++
  src[NONNUMERICIDENTIFIER] = '\\d*[a-zA-Z-][a-zA-Z0-9-]*'

  // ## Main Version
  // Three dot-separated numeric identifiers.

  var MAINVERSION = R++
  src[MAINVERSION] =
    '(' +
    src[NUMERICIDENTIFIER] +
    ')\\.' +
    '(' +
    src[NUMERICIDENTIFIER] +
    ')\\.' +
    '(' +
    src[NUMERICIDENTIFIER] +
    ')'

  var MAINVERSIONLOOSE = R++
  src[MAINVERSIONLOOSE] =
    '(' +
    src[NUMERICIDENTIFIERLOOSE] +
    ')\\.' +
    '(' +
    src[NUMERICIDENTIFIERLOOSE] +
    ')\\.' +
    '(' +
    src[NUMERICIDENTIFIERLOOSE] +
    ')'

  // ## Pre-release Version Identifier
  // A numeric identifier, or a non-numeric identifier.

  var PRERELEASEIDENTIFIER = R++
  src[PRERELEASEIDENTIFIER] =
    '(?:' + src[NUMERICIDENTIFIER] + '|' + src[NONNUMERICIDENTIFIER] + ')'

  var PRERELEASEIDENTIFIERLOOSE = R++
  src[PRERELEASEIDENTIFIERLOOSE] =
    '(?:' + src[NUMERICIDENTIFIERLOOSE] + '|' + src[NONNUMERICIDENTIFIER] + ')'

  // ## Pre-release Version
  // Hyphen, followed by one or more dot-separated pre-release version
  // identifiers.

  var PRERELEASE = R++
  src[PRERELEASE] =
    '(?:-(' +
    src[PRERELEASEIDENTIFIER] +
    '(?:\\.' +
    src[PRERELEASEIDENTIFIER] +
    ')*))'

  var PRERELEASELOOSE = R++
  src[PRERELEASELOOSE] =
    '(?:-?(' +
    src[PRERELEASEIDENTIFIERLOOSE] +
    '(?:\\.' +
    src[PRERELEASEIDENTIFIERLOOSE] +
    ')*))'

  // ## Build Metadata Identifier
  // Any combination of digits, letters, or hyphens.

  var BUILDIDENTIFIER = R++
  src[BUILDIDENTIFIER] = '[0-9A-Za-z-]+'

  // ## Build Metadata
  // Plus sign, followed by one or more period-separated build metadata
  // identifiers.

  var BUILD = R++
  src[BUILD] =
    '(?:\\+(' + src[BUILDIDENTIFIER] + '(?:\\.' + src[BUILDIDENTIFIER] + ')*))'

  // ## Full Version String
  // A main version, followed optionally by a pre-release version and
  // build metadata.

  // Note that the only major, minor, patch, and pre-release sections of
  // the version string are capturing groups.  The build metadata is not a
  // capturing group, because it should not ever be used in version
  // comparison.

  var FULL = R++
  var FULLPLAIN =
    'v?' + src[MAINVERSION] + src[PRERELEASE] + '?' + src[BUILD] + '?'

  src[FULL] = '^' + FULLPLAIN + '$'

  // like full, but allows v1.2.3 and =1.2.3, which people do sometimes.
  // also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty
  // common in the npm registry.
  var LOOSEPLAIN =
    '[v=\\s]*' +
    src[MAINVERSIONLOOSE] +
    src[PRERELEASELOOSE] +
    '?' +
    src[BUILD] +
    '?'

  var LOOSE = R++
  src[LOOSE] = '^' + LOOSEPLAIN + '$'

  var GTLT = R++
  src[GTLT] = '((?:<|>)?=?)'

  // Something like "2.*" or "1.2.x".
  // Note that "x.x" is a valid xRange identifer, meaning "any version"
  // Only the first item is strictly required.
  var XRANGEIDENTIFIERLOOSE = R++
  src[XRANGEIDENTIFIERLOOSE] = src[NUMERICIDENTIFIERLOOSE] + '|x|X|\\*'
  var XRANGEIDENTIFIER = R++
  src[XRANGEIDENTIFIER] = src[NUMERICIDENTIFIER] + '|x|X|\\*'

  var XRANGEPLAIN = R++
  src[XRANGEPLAIN] =
    '[v=\\s]*(' +
    src[XRANGEIDENTIFIER] +
    ')' +
    '(?:\\.(' +
    src[XRANGEIDENTIFIER] +
    ')' +
    '(?:\\.(' +
    src[XRANGEIDENTIFIER] +
    ')' +
    '(?:' +
    src[PRERELEASE] +
    ')?' +
    src[BUILD] +
    '?' +
    ')?)?'

  var XRANGEPLAINLOOSE = R++
  src[XRANGEPLAINLOOSE] =
    '[v=\\s]*(' +
    src[XRANGEIDENTIFIERLOOSE] +
    ')' +
    '(?:\\.(' +
    src[XRANGEIDENTIFIERLOOSE] +
    ')' +
    '(?:\\.(' +
    src[XRANGEIDENTIFIERLOOSE] +
    ')' +
    '(?:' +
    src[PRERELEASELOOSE] +
    ')?' +
    src[BUILD] +
    '?' +
    ')?)?'

  var XRANGE = R++
  src[XRANGE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAIN] + '$'
  var XRANGELOOSE = R++
  src[XRANGELOOSE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAINLOOSE] + '$'

  // Tilde ranges.
  // Meaning is "reasonably at or greater than"
  var LONETILDE = R++
  src[LONETILDE] = '(?:~>?)'

  var TILDETRIM = R++
  src[TILDETRIM] = '(\\s*)' + src[LONETILDE] + '\\s+'
  re[TILDETRIM] = new RegExp(src[TILDETRIM], 'g')
  var tildeTrimReplace = '$1~'

  var TILDE = R++
  src[TILDE] = '^' + src[LONETILDE] + src[XRANGEPLAIN] + '$'
  var TILDELOOSE = R++
  src[TILDELOOSE] = '^' + src[LONETILDE] + src[XRANGEPLAINLOOSE] + '$'

  // Caret ranges.
  // Meaning is "at least and backwards compatible with"
  var LONECARET = R++
  src[LONECARET] = '(?:\\^)'

  var CARETTRIM = R++
  src[CARETTRIM] = '(\\s*)' + src[LONECARET] + '\\s+'
  re[CARETTRIM] = new RegExp(src[CARETTRIM], 'g')
  var caretTrimReplace = '$1^'

  var CARET = R++
  src[CARET] = '^' + src[LONECARET] + src[XRANGEPLAIN] + '$'
  var CARETLOOSE = R++
  src[CARETLOOSE] = '^' + src[LONECARET] + src[XRANGEPLAINLOOSE] + '$'

  // A simple gt/lt/eq thing, or just "" to indicate "any version"
  var COMPARATORLOOSE = R++
  src[COMPARATORLOOSE] = '^' + src[GTLT] + '\\s*(' + LOOSEPLAIN + ')$|^$'
  var COMPARATOR = R++
  src[COMPARATOR] = '^' + src[GTLT] + '\\s*(' + FULLPLAIN + ')$|^$'

  // An expression to strip any whitespace between the gtlt and the thing
  // it modifies, so that `> 1.2.3` ==> `>1.2.3`
  var COMPARATORTRIM = R++
  src[COMPARATORTRIM] =
    '(\\s*)' + src[GTLT] + '\\s*(' + LOOSEPLAIN + '|' + src[XRANGEPLAIN] + ')'

  // this one has to use the /g flag
  re[COMPARATORTRIM] = new RegExp(src[COMPARATORTRIM], 'g')
  var comparatorTrimReplace = '$1$2$3'

  // Something like `1.2.3 - 1.2.4`
  // Note that these all use the loose form, because they'll be
  // checked against either the strict or loose comparator form
  // later.
  var HYPHENRANGE = R++
  src[HYPHENRANGE] =
    '^\\s*(' +
    src[XRANGEPLAIN] +
    ')' +
    '\\s+-\\s+' +
    '(' +
    src[XRANGEPLAIN] +
    ')' +
    '\\s*$'

  var HYPHENRANGELOOSE = R++
  src[HYPHENRANGELOOSE] =
    '^\\s*(' +
    src[XRANGEPLAINLOOSE] +
    ')' +
    '\\s+-\\s+' +
    '(' +
    src[XRANGEPLAINLOOSE] +
    ')' +
    '\\s*$'

  // Star ranges basically just allow anything at all.
  var STAR = R++
  src[STAR] = '(<|>)?=?\\s*\\*'

  // Compile to actual regexp objects.
  // All are flag-free, unless they were created above with a flag.
  for (var i = 0; i < R; i++) {
    if (!re[i]) re[i] = new RegExp(src[i])
  }

  exports.parse = parse
  function parse(version, loose) {
    if (version instanceof SemVer) return version

    if (typeof version !== 'string') return null

    if (version.length > MAX_LENGTH) return null

    var r = loose ? re[LOOSE] : re[FULL]
    if (!r.test(version)) return null

    try {
      return new SemVer(version, loose)
    } catch (er) {
      return null
    }
  }

  exports.valid = valid
  function valid(version, loose) {
    var v = parse(version, loose)
    return v ? v.version : null
  }

  exports.clean = clean
  function clean(version, loose) {
    var s = parse(version.trim().replace(/^[=v]+/, ''), loose)
    return s ? s.version : null
  }

  exports.SemVer = SemVer

  function SemVer(version, loose) {
    if (version instanceof SemVer) {
      if (version.loose === loose) return version
      else version = version.version
    } else if (typeof version !== 'string') {
      throw new TypeError('Invalid Version: ' + version)
    }

    if (version.length > MAX_LENGTH)
      throw new TypeError(
        'version is longer than ' + MAX_LENGTH + ' characters',
      )

    if (!(this instanceof SemVer)) return new SemVer(version, loose)

    this.loose = loose
    var m = version.trim().match(loose ? re[LOOSE] : re[FULL])

    if (!m) throw new TypeError('Invalid Version: ' + version)

    this.raw = version

    // these are actually numbers
    this.major = +m[1]
    this.minor = +m[2]
    this.patch = +m[3]

    if (this.major > MAX_SAFE_INTEGER || this.major < 0)
      throw new TypeError('Invalid major version')

    if (this.minor > MAX_SAFE_INTEGER || this.minor < 0)
      throw new TypeError('Invalid minor version')

    if (this.patch > MAX_SAFE_INTEGER || this.patch < 0)
      throw new TypeError('Invalid patch version')

    // numberify any prerelease numeric ids
    if (!m[4]) this.prerelease = []
    else
      this.prerelease = m[4].split('.').map(function (id) {
        if (/^[0-9]+$/.test(id)) {
          var num = +id
          if (num >= 0 && num < MAX_SAFE_INTEGER) return num
        }
        return id
      })

    this.build = m[5] ? m[5].split('.') : []
    this.format()
  }

  SemVer.prototype.format = function () {
    this.version = this.major + '.' + this.minor + '.' + this.patch
    if (this.prerelease.length) this.version += '-' + this.prerelease.join('.')
    return this.version
  }

  SemVer.prototype.inspect = function () {
    return '<SemVer "' + this + '">'
  }

  SemVer.prototype.toString = function () {
    return this.version
  }

  SemVer.prototype.compare = function (other) {
    if (!(other instanceof SemVer)) other = new SemVer(other, this.loose)

    return this.compareMain(other) || this.comparePre(other)
  }

  SemVer.prototype.compareMain = function (other) {
    if (!(other instanceof SemVer)) other = new SemVer(other, this.loose)

    return (
      compareIdentifiers(this.major, other.major) ||
      compareIdentifiers(this.minor, other.minor) ||
      compareIdentifiers(this.patch, other.patch)
    )
  }

  SemVer.prototype.comparePre = function (other) {
    if (!(other instanceof SemVer)) other = new SemVer(other, this.loose)

    // NOT having a prerelease is > having one
    if (this.prerelease.length && !other.prerelease.length) return -1
    else if (!this.prerelease.length && other.prerelease.length) return 1
    else if (!this.prerelease.length && !other.prerelease.length) return 0

    var i = 0
    do {
      var a = this.prerelease[i]
      var b = other.prerelease[i]
      if (a === undefined && b === undefined) return 0
      else if (b === undefined) return 1
      else if (a === undefined) return -1
      else if (a === b) continue
      else return compareIdentifiers(a, b)
    } while (++i)
  }

  // preminor will bump the version up to the next minor release, and immediately
  // down to pre-release. premajor and prepatch work the same way.
  SemVer.prototype.inc = function (release, identifier) {
    switch (release) {
      case 'premajor':
        this.prerelease.length = 0
        this.patch = 0
        this.minor = 0
        this.major++
        this.inc('pre', identifier)
        break
      case 'preminor':
        this.prerelease.length = 0
        this.patch = 0
        this.minor++
        this.inc('pre', identifier)
        break
      case 'prepatch':
        // If this is already a prerelease, it will bump to the next version
        // drop any prereleases that might already exist, since they are not
        // relevant at this point.
        this.prerelease.length = 0
        this.inc('patch', identifier)
        this.inc('pre', identifier)
        break
      // If the input is a non-prerelease version, this acts the same as
      // prepatch.
      case 'prerelease':
        if (this.prerelease.length === 0) this.inc('patch', identifier)
        this.inc('pre', identifier)
        break

      case 'major':
        // If this is a pre-major version, bump up to the same major version.
        // Otherwise increment major.
        // 1.0.0-5 bumps to 1.0.0
        // 1.1.0 bumps to 2.0.0
        if (
          this.minor !== 0 ||
          this.patch !== 0 ||
          this.prerelease.length === 0
        )
          this.major++
        this.minor = 0
        this.patch = 0
        this.prerelease = []
        break
      case 'minor':
        // If this is a pre-minor version, bump up to the same minor version.
        // Otherwise increment minor.
        // 1.2.0-5 bumps to 1.2.0
        // 1.2.1 bumps to 1.3.0
        if (this.patch !== 0 || this.prerelease.length === 0) this.minor++
        this.patch = 0
        this.prerelease = []
        break
      case 'patch':
        // If this is not a pre-release version, it will increment the patch.
        // If it is a pre-release it will bump up to the same patch version.
        // 1.2.0-5 patches to 1.2.0
        // 1.2.0 patches to 1.2.1
        if (this.prerelease.length === 0) this.patch++
        this.prerelease = []
        break
      // This probably shouldn't be used publicly.
      // 1.0.0 "pre" would become 1.0.0-0 which is the wrong direction.
      case 'pre':
        if (this.prerelease.length === 0) this.prerelease = [0]
        else {
          var i = this.prerelease.length
          while (--i >= 0) {
            if (typeof this.prerelease[i] === 'number') {
              this.prerelease[i]++
              i = -2
            }
          }
          if (i === -1)
            // didn't increment anything
            this.prerelease.push(0)
        }
        if (identifier) {
          // 1.2.0-beta.1 bumps to 1.2.0-beta.2,
          // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0
          if (this.prerelease[0] === identifier) {
            if (isNaN(this.prerelease[1])) this.prerelease = [identifier, 0]
          } else this.prerelease = [identifier, 0]
        }
        break

      default:
        throw new Error('invalid increment argument: ' + release)
    }
    this.format()
    return this
  }

  exports.inc = inc
  function inc(version, release, loose, identifier) {
    if (typeof loose === 'string') {
      identifier = loose
      loose = undefined
    }

    try {
      return new SemVer(version, loose).inc(release, identifier).version
    } catch (er) {
      return null
    }
  }

  exports.diff = diff
  function diff(version1, version2) {
    if (eq(version1, version2)) {
      return null
    } else {
      var v1 = parse(version1)
      var v2 = parse(version2)
      if (v1.prerelease.length || v2.prerelease.length) {
        for (var key in v1) {
          if (key === 'major' || key === 'minor' || key === 'patch') {
            if (v1[key] !== v2[key]) {
              return 'pre' + key
            }
          }
        }
        return 'prerelease'
      }
      for (var key in v1) {
        if (key === 'major' || key === 'minor' || key === 'patch') {
          if (v1[key] !== v2[key]) {
            return key
          }
        }
      }
    }
  }

  exports.compareIdentifiers = compareIdentifiers

  var numeric = /^[0-9]+$/
  function compareIdentifiers(a, b) {
    var anum = numeric.test(a)
    var bnum = numeric.test(b)

    if (anum && bnum) {
      a = +a
      b = +b
    }

    return anum && !bnum ? -1 : bnum && !anum ? 1 : a < b ? -1 : a > b ? 1 : 0
  }

  exports.rcompareIdentifiers = rcompareIdentifiers
  function rcompareIdentifiers(a, b) {
    return compareIdentifiers(b, a)
  }

  exports.major = major
  function major(a, loose) {
    return new SemVer(a, loose).major
  }

  exports.minor = minor
  function minor(a, loose) {
    return new SemVer(a, loose).minor
  }

  exports.patch = patch
  function patch(a, loose) {
    return new SemVer(a, loose).patch
  }

  exports.compare = compare
  function compare(a, b, loose) {
    return new SemVer(a, loose).compare(b)
  }

  exports.compareLoose = compareLoose
  function compareLoose(a, b) {
    return compare(a, b, true)
  }

  exports.rcompare = rcompare
  function rcompare(a, b, loose) {
    return compare(b, a, loose)
  }

  exports.sort = sort
  function sort(list, loose) {
    return list.sort(function (a, b) {
      return exports.compare(a, b, loose)
    })
  }

  exports.rsort = rsort
  function rsort(list, loose) {
    return list.sort(function (a, b) {
      return exports.rcompare(a, b, loose)
    })
  }

  exports.gt = gt
  function gt(a, b, loose) {
    return compare(a, b, loose) > 0
  }

  exports.lt = lt
  function lt(a, b, loose) {
    return compare(a, b, loose) < 0
  }

  exports.eq = eq
  function eq(a, b, loose) {
    return compare(a, b, loose) === 0
  }

  exports.neq = neq
  function neq(a, b, loose) {
    return compare(a, b, loose) !== 0
  }

  exports.gte = gte
  function gte(a, b, loose) {
    return compare(a, b, loose) >= 0
  }

  exports.lte = lte
  function lte(a, b, loose) {
    return compare(a, b, loose) <= 0
  }

  exports.cmp = cmp
  function cmp(a, op, b, loose) {
    var ret
    switch (op) {
      case '===':
        if (typeof a === 'object') a = a.version
        if (typeof b === 'object') b = b.version
        ret = a === b
        break
      case '!==':
        if (typeof a === 'object') a = a.version
        if (typeof b === 'object') b = b.version
        ret = a !== b
        break
      case '':
      case '=':
      case '==':
        ret = eq(a, b, loose)
        break
      case '!=':
        ret = neq(a, b, loose)
        break
      case '>':
        ret = gt(a, b, loose)
        break
      case '>=':
        ret = gte(a, b, loose)
        break
      case '<':
        ret = lt(a, b, loose)
        break
      case '<=':
        ret = lte(a, b, loose)
        break
      default:
        throw new TypeError('Invalid operator: ' + op)
    }
    return ret
  }

  exports.Comparator = Comparator
  function Comparator(comp, loose) {
    if (comp instanceof Comparator) {
      if (comp.loose === loose) return comp
      else comp = comp.value
    }

    if (!(this instanceof Comparator)) return new Comparator(comp, loose)

    this.loose = loose
    this.parse(comp)

    if (this.semver === ANY) this.value = ''
    else this.value = this.operator + this.semver.version
  }

  var ANY = {}
  Comparator.prototype.parse = function (comp) {
    var r = this.loose ? re[COMPARATORLOOSE] : re[COMPARATOR]
    var m = comp.match(r)

    if (!m) throw new TypeError('Invalid comparator: ' + comp)

    this.operator = m[1]
    if (this.operator === '=') this.operator = ''

    // if it literally is just '>' or '' then allow anything.
    if (!m[2]) this.semver = ANY
    else this.semver = new SemVer(m[2], this.loose)
  }

  Comparator.prototype.inspect = function () {
    return '<SemVer Comparator "' + this + '">'
  }

  Comparator.prototype.toString = function () {
    return this.value
  }

  Comparator.prototype.test = function (version) {
    if (this.semver === ANY) return true

    if (typeof version === 'string') version = new SemVer(version, this.loose)

    return cmp(version, this.operator, this.semver, this.loose)
  }

  exports.Range = Range
  function Range(range, loose) {
    if (range instanceof Range && range.loose === loose) return range

    if (!(this instanceof Range)) return new Range(range, loose)

    this.loose = loose

    // First, split based on boolean or ||
    this.raw = range
    this.set = range
      .split(/\s*\|\|\s*/)
      .map(function (range) {
        return this.parseRange(range.trim())
      }, this)
      .filter(function (c) {
        // throw out any that are not relevant for whatever reason
        return c.length
      })

    if (!this.set.length) {
      throw new TypeError('Invalid SemVer Range: ' + range)
    }

    this.format()
  }

  Range.prototype.inspect = function () {
    return '<SemVer Range "' + this.range + '">'
  }

  Range.prototype.format = function () {
    this.range = this.set
      .map(function (comps) {
        return comps.join(' ').trim()
      })
      .join('||')
      .trim()
    return this.range
  }

  Range.prototype.toString = function () {
    return this.range
  }

  Range.prototype.parseRange = function (range) {
    var loose = this.loose
    range = range.trim()
    // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4`
    var hr = loose ? re[HYPHENRANGELOOSE] : re[HYPHENRANGE]
    range = range.replace(hr, hyphenReplace)
    // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5`
    range = range.replace(re[COMPARATORTRIM], comparatorTrimReplace)
    // `~ 1.2.3` => `~1.2.3`
    range = range.replace(re[TILDETRIM], tildeTrimReplace)

    // `^ 1.2.3` => `^1.2.3`
    range = range.replace(re[CARETTRIM], caretTrimReplace)

    // normalize spaces
    range = range.split(/\s+/).join(' ')

    // At this point, the range is completely trimmed and
    // ready to be split into comparators.

    var compRe = loose ? re[COMPARATORLOOSE] : re[COMPARATOR]
    var set = range
      .split(' ')
      .map(function (comp) {
        return parseComparator(comp, loose)
      })
      .join(' ')
      .split(/\s+/)
    if (this.loose) {
      // in loose mode, throw out any that are not valid comparators
      set = set.filter(function (comp) {
        return !!comp.match(compRe)
      })
    }
    set = set.map(function (comp) {
      return new Comparator(comp, loose)
    })

    return set
  }

  // Mostly just for testing and legacy API reasons
  exports.toComparators = toComparators
  function toComparators(range, loose) {
    return new Range(range, loose).set.map(function (comp) {
      return comp
        .map(function (c) {
          return c.value
        })
        .join(' ')
        .trim()
        .split(' ')
    })
  }

  // comprised of xranges, tildes, stars, and gtlt's at this point.
  // already replaced the hyphen ranges
  // turn into a set of JUST comparators.
  function parseComparator(comp, loose) {
    comp = replaceCarets(comp, loose)
    comp = replaceTildes(comp, loose)
    comp = replaceXRanges(comp, loose)
    comp = replaceStars(comp, loose)
    return comp
  }

  function isX(id) {
    return !id || id.toLowerCase() === 'x' || id === '*'
  }

  // ~, ~> --> * (any, kinda silly)
  // ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0
  // ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0
  // ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0
  // ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0
  // ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0
  function replaceTildes(comp, loose) {
    return comp
      .trim()
      .split(/\s+/)
      .map(function (comp) {
        return replaceTilde(comp, loose)
      })
      .join(' ')
  }

  function replaceTilde(comp, loose) {
    var r = loose ? re[TILDELOOSE] : re[TILDE]
    return comp.replace(r, function (_, M, m, p, pr) {
      var ret

      if (isX(M)) ret = ''
      else if (isX(m)) ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'
      else if (isX(p))
        // ~1.2 == >=1.2.0- <1.3.0-
        ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'
      else if (pr) {
        if (pr.charAt(0) !== '-') pr = '-' + pr
        ret =
          '>=' + M + '.' + m + '.' + p + pr + ' <' + M + '.' + (+m + 1) + '.0'
      } else
        // ~1.2.3 == >=1.2.3 <1.3.0
        ret = '>=' + M + '.' + m + '.' + p + ' <' + M + '.' + (+m + 1) + '.0'

      return ret
    })
  }

  // ^ --> * (any, kinda silly)
  // ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0
  // ^2.0, ^2.0.x --> >=2.0.0 <3.0.0
  // ^1.2, ^1.2.x --> >=1.2.0 <2.0.0
  // ^1.2.3 --> >=1.2.3 <2.0.0
  // ^1.2.0 --> >=1.2.0 <2.0.0
  function replaceCarets(comp, loose) {
    return comp
      .trim()
      .split(/\s+/)
      .map(function (comp) {
        return replaceCaret(comp, loose)
      })
      .join(' ')
  }

  function replaceCaret(comp, loose) {
    var r = loose ? re[CARETLOOSE] : re[CARET]
    return comp.replace(r, function (_, M, m, p, pr) {
      var ret

      if (isX(M)) ret = ''
      else if (isX(m)) ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'
      else if (isX(p)) {
        if (M === '0')
          ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'
        else ret = '>=' + M + '.' + m + '.0 <' + (+M + 1) + '.0.0'
      } else if (pr) {
        if (pr.charAt(0) !== '-') pr = '-' + pr
        if (M === '0') {
          if (m === '0')
            ret =
              '>=' +
              M +
              '.' +
              m +
              '.' +
              p +
              pr +
              ' <' +
              M +
              '.' +
              m +
              '.' +
              (+p + 1)
          else
            ret =
              '>=' +
              M +
              '.' +
              m +
              '.' +
              p +
              pr +
              ' <' +
              M +
              '.' +
              (+m + 1) +
              '.0'
        } else
          ret = '>=' + M + '.' + m + '.' + p + pr + ' <' + (+M + 1) + '.0.0'
      } else {
        if (M === '0') {
          if (m === '0')
            ret =
              '>=' + M + '.' + m + '.' + p + ' <' + M + '.' + m + '.' + (+p + 1)
          else
            ret =
              '>=' + M + '.' + m + '.' + p + ' <' + M + '.' + (+m + 1) + '.0'
        } else ret = '>=' + M + '.' + m + '.' + p + ' <' + (+M + 1) + '.0.0'
      }

      return ret
    })
  }

  function replaceXRanges(comp, loose) {
    return comp
      .split(/\s+/)
      .map(function (comp) {
        return replaceXRange(comp, loose)
      })
      .join(' ')
  }

  function replaceXRange(comp, loose) {
    comp = comp.trim()
    var r = loose ? re[XRANGELOOSE] : re[XRANGE]
    return comp.replace(r, function (ret, gtlt, M, m, p, pr) {
      var xM = isX(M)
      var xm = xM || isX(m)
      var xp = xm || isX(p)
      var anyX = xp

      if (gtlt === '=' && anyX) gtlt = ''

      if (xM) {
        if (gtlt === '>' || gtlt === '<') {
          // nothing is allowed
          ret = '<0.0.0'
        } else {
          // nothing is forbidden
          ret = '*'
        }
      } else if (gtlt && anyX) {
        // replace X with 0
        if (xm) m = 0
        if (xp) p = 0

        if (gtlt === '>') {
          // >1 => >=2.0.0
          // >1.2 => >=1.3.0
          // >1.2.3 => >= 1.2.4
          gtlt = '>='
          if (xm) {
            M = +M + 1
            m = 0
            p = 0
          } else if (xp) {
            m = +m + 1
            p = 0
          }
        } else if (gtlt === '<=') {
          // <=0.7.x is actually <0.8.0, since any 0.7.x should
          // pass.  Similarly, <=7.x is actually <8.0.0, etc.
          gtlt = '<'
          if (xm) M = +M + 1
          else m = +m + 1
        }

        ret = gtlt + M + '.' + m + '.' + p
      } else if (xm) {
        ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'
      } else if (xp) {
        ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'
      }

      return ret
    })
  }

  // Because * is AND-ed with everything else in the comparator,
  // and '' means "any version", just remove the *s entirely.
  function replaceStars(comp, loose) {
    // Looseness is ignored here.  star is always as loose as it gets!
    return comp.trim().replace(re[STAR], '')
  }

  // This function is passed to string.replace(re[HYPHENRANGE])
  // M, m, patch, prerelease, build
  // 1.2 - 3.4.5 => >=1.2.0 <=3.4.5
  // 1.2.3 - 3.4 => >=1.2.0 <3.5.0 Any 3.4.x will do
  // 1.2 - 3.4 => >=1.2.0 <3.5.0
  function hyphenReplace(
    $0,
    from,
    fM,
    fm,
    fp,
    fpr,
    fb,
    to,
    tM,
    tm,
    tp,
    tpr,
    tb,
  ) {
    if (isX(fM)) from = ''
    else if (isX(fm)) from = '>=' + fM + '.0.0'
    else if (isX(fp)) from = '>=' + fM + '.' + fm + '.0'
    else from = '>=' + from

    if (isX(tM)) to = ''
    else if (isX(tm)) to = '<' + (+tM + 1) + '.0.0'
    else if (isX(tp)) to = '<' + tM + '.' + (+tm + 1) + '.0'
    else if (tpr) to = '<=' + tM + '.' + tm + '.' + tp + '-' + tpr
    else to = '<=' + to

    return (from + ' ' + to).trim()
  }

  // if ANY of the sets match ALL of its comparators, then pass
  Range.prototype.test = function (version) {
    if (!version) return false

    if (typeof version === 'string') version = new SemVer(version, this.loose)

    for (var i = 0; i < this.set.length; i++) {
      if (testSet(this.set[i], version)) return true
    }
    return false
  }

  function testSet(set, version) {
    for (var i = 0; i < set.length; i++) {
      if (!set[i].test(version)) return false
    }

    if (version.prerelease.length) {
      // Find the set of versions that are allowed to have prereleases
      // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0
      // That should allow `1.2.3-pr.2` to pass.
      // However, `1.2.4-alpha.notready` should NOT be allowed,
      // even though it's within the range set by the comparators.
      for (var i = 0; i < set.length; i++) {
        if (set[i].semver === ANY) continue

        if (set[i].semver.prerelease.length > 0) {
          var allowed = set[i].semver
          if (
            allowed.major === version.major &&
            allowed.minor === version.minor &&
            allowed.patch === version.patch
          )
            return true
        }
      }

      // Version has a -pre, but it's not one of the ones we like.
      return false
    }

    return true
  }

  exports.satisfies = satisfies
  function satisfies(version, range, loose) {
    try {
      range = new Range(range, loose)
    } catch (er) {
      return false
    }
    return range.test(version)
  }

  exports.maxSatisfying = maxSatisfying
  function maxSatisfying(versions, range, loose) {
    return (
      versions
        .filter(function (version) {
          return satisfies(version, range, loose)
        })
        .sort(function (a, b) {
          return rcompare(a, b, loose)
        })[0] || null
    )
  }

  exports.validRange = validRange
  function validRange(range, loose) {
    try {
      // Return '*' instead of '' so that truthiness works.
      // This will throw if it's invalid anyway
      return new Range(range, loose).range || '*'
    } catch (er) {
      return null
    }
  }

  // Determine if version is less than all the versions possible in the range
  exports.ltr = ltr
  function ltr(version, range, loose) {
    return outside(version, range, '<', loose)
  }

  // Determine if version is greater than all the versions possible in the range.
  exports.gtr = gtr
  function gtr(version, range, loose) {
    return outside(version, range, '>', loose)
  }

  exports.outside = outside
  function outside(version, range, hilo, loose) {
    version = new SemVer(version, loose)
    range = new Range(range, loose)

    var gtfn, ltefn, ltfn, comp, ecomp
    switch (hilo) {
      case '>':
        gtfn = gt
        ltefn = lte
        ltfn = lt
        comp = '>'
        ecomp = '>='
        break
      case '<':
        gtfn = lt
        ltefn = gte
        ltfn = gt
        comp = '<'
        ecomp = '<='
        break
      default:
        throw new TypeError('Must provide a hilo val of "<" or ">"')
    }

    // If it satisifes the range it is not outside
    if (satisfies(version, range, loose)) {
      return false
    }

    // From now on, variable terms are as if we're in "gtr" mode.
    // but note that everything is flipped for the "ltr" function.

    for (var i = 0; i < range.set.length; ++i) {
      var comparators = range.set[i]

      var high = null
      var low = null

      comparators.forEach(function (comparator) {
        if (comparator.semver === ANY) {
          comparator = new Comparator('>=0.0.0')
        }
        high = high || comparator
        low = low || comparator
        if (gtfn(comparator.semver, high.semver, loose)) {
          high = comparator
        } else if (ltfn(comparator.semver, low.semver, loose)) {
          low = comparator
        }
      })

      // If the edge version comparator has a operator then our version
      // isn't outside it
      if (high.operator === comp || high.operator === ecomp) {
        return false
      }

      // If the lowest version comparator has an operator and our version
      // is less than it then it isn't higher than the range
      if (
        (!low.operator || low.operator === comp) &&
        ltefn(version, low.semver)
      ) {
        return false
      } else if (low.operator === ecomp && ltfn(version, low.semver)) {
        return false
      }
    }
    return true
  }

  // Use the define() function if we're in AMD land
  if (typeof define === 'function' && define.amd) define(exports)
})(
  typeof exports === 'object'
    ? exports
    : typeof define === 'function' && define.amd
      ? {}
      : (window.semver = {}),
)
/** </script src="semver.browser.js"> **/
;(function collectEbaReportingModules(window, document) {
  'use strict'

  const selectors = {
    MODULE_ARTICLE: `#block-eba-theme-content > div > div > div.view-content > div > article.rtt-module`,
    FRAMEWORK_RELEASE: `div.rtt-module__metadata > div > a`,
    META_DATA: `div.rtt-module__metadata`,
    MODULE_ABBR: `div.rtt-module__metadata > span.badge.badge--rtt`,
    MODULE_DESC: `div.rtt-module__text`,
    MODULE_TITLE: `.rtt-module__title > a`,
    RESOURCES: `.rtt-module__actions > a`,
    STATUS: `.rtt-module__metadata > span.link-icon.link-icon--activity > strong`,
    VALID_FROM: `div.rtt-module__metadata > span.link-icon.link-icon--calendar > strong > time`,
    VALID_TO: ``,
  }

  const EBA_REPORTING_MODULE_TIME_TRAVELLER_URL =
    'https://www.eba.europa.eu/reporting-time-traveller'
  const EBA_REPORTING_MODULE_BY_NAME_URL =
    'https://www.eba.europa.eu/reporting-time-traveller/reporting-by-name?name='

  class NavigateStep {
    constructor(title, url, type = 'navigate') {
      this.assertedEvents = [
        {
          type: 'navigation',
          title,
          url,
        },
      ]
      this.type = type
      this.url = url
    }

    toString() {
      return JSON.stringify(this)
    }
  }

  const createNavigationEventSteps = (window = window, document = document) => {
    const selectMenu =
      document.getElementById('edit-name') ||
      document.getElementById('edit-reporting-module')

    return Array.from(selectMenu.options).map(
      ({ textContent, value }) =>
        new NavigateStep(
          textContent,
          `${EBA_REPORTING_MODULE_BY_NAME_URL}${value}`,
        ),
    )
  }

  const createViewportStep = () => {
    return {
      type: 'setViewport',
      width: 1495,
      height: 564,
      deviceScaleFactor: 1,
      isMobile: false,
      hasTouch: false,
      isLandscape: false,
    }
  }

  const createRecorderJson = (window = window, document = document) => {
    const isoDate = new Date().toISOString().replace(/T.*/gim, '')
    const title = `eba-reporting-modules-by-name-${isoDate}`
    const viewportStep = createViewportStep()
    const navigationEvents = createNavigationEventSteps(
      window,
      document,
    ).filter(({ url }) => !url.endsWith('?name=All'))
    const steps = [viewportStep, ...navigationEvents]
    return {
      steps,
      title,
    }
  }

  const loadReportingModuleRecording = (
    window = window,
    document = document,
  ) => {
    const recording = createRecorderJson(window, document)
    window.localStorage.setItem(
      'za.eba.recording.elt.reportingModules',
      JSON.stringify(recording),
    )
    return recording
  }

  const getAbbr = (element = document) => {
    const elem = element.querySelector(selectors.MODULE_ABBR)
    let abbreviation = null
    if (elem) {
      abbreviation = elem.innerText
    } else {
      try {
        const title = getTitle()
        const words = title.split(/\s+/)
        const ONE = 1
        if (words.length == ONE) {
          abbreviation = words[0].toUpperCase()
        } else {
          abbreviation = words
            .map((word) => word.charAt(0).toUpperCase())
            .join('')
        }
      } catch (err) {
        window.console.error(err)
      }
    }
    return abbreviation
      ? abbreviation.replace(/[\s+|\-|\(|\)]/g, '_')
      : abbreviation
  }

  const loadAllModulesFromLocalStorage = () => {
    const len = window.localStorage.length
    const modules = []
    for (let i = 0; i < len; i++) {
      const key = window.localStorage.key(i)
      const json = window.localStorage.getItem(key)
      modules.push(JSON.parse(json))
    }
    return _.orderBy(modules, ['abbr', 'release'])
  }

  const getItemBySelector = (selector, element = document) => {
    let item = null
    try {
      item = element.querySelector(selector).innerText
    } catch (err) {
      window.console.error(err)
    }
    return item
  }

  const getDescription = (element = document) => {
    return getItemBySelector(selectors.MODULE_DESC, element)
  }

  const getFileExtension = (elem) => {
    const uri = new URL(elem.href)
    const ext = uri.pathname.split('.').pop()
    return ext.toLowerCase()
  }

  const getRelease = (element = document) => {
    const release = getItemBySelector(selectors.FRAMEWORK_RELEASE, element)
    return release
  }

  const getStatus = (element = document) => {
    const status = getItemBySelector(selectors.STATUS, element)
    if (status) {
      return status.toLocaleLowerCase()
    }
    return status
  }

  const getTitle = (element = document) => {
    try {
      const option =
        element.getElementById('edit-reporting-module') ||
        element.getElementById('edit-name')
      return option.querySelector(`option:checked`).text
    } catch (err) {
      window.console.error(err)
      return null
    }
  }

  const getValidFrom = (element = document) => {
    const validFrom = element.querySelector(selectors.VALID_FROM)
    return validFrom.getAttribute('datetime')
  }

  const convertReleaseToSemver = (module) => {
    let version = null
    if (_.has(module, 'release')) {
      version = semver.parse(`${module.release.replace(/^v/, '')}.0`)
    }
    return version
  }

  const setValidFromDates = (modules = []) => {
    const grouped = _.groupBy(modules, 'abbr')
    const abbrs = Object.keys(grouped)

    abbrs.forEach((abbr) => {
      const { semver } = window
      const modules = _.sortBy(grouped[abbr], ['release'])
      const ONE = 1
      const { length } = modules
      for (let index = 0; index < length; index++) {
        if (length > ONE) {
          const a = modules[index]
          const b = modules[index + 1]
          if (b && a && a.status === 'superseded') {
            const releaseA = convertReleaseToSemver(a)
            const releaseB = convertReleaseToSemver(b)
            if (b && semver.lt(releaseA, releaseB)) {
              const validTo = new Date(b.valid_from)
              validTo.setDate(validTo.getDate() - ONE)
              a.valid_to = validTo.toISOString()
            }
          }
        }
      }
    })

    return modules
  }

  const isTemplate = (elem) => {
    return getFileExtension(elem) === 'xlsx'
  }

  const loadUrls = (reportingModule, element = document) => {
    reportingModule.urls.source = location.href
    const resources = Array.from(element.querySelectorAll(selectors.RESOURCES))
    resources.forEach((elem) => {
      if (isTemplate(elem)) {
        reportingModule.urls.templates.push(elem.href)
      } else {
        reportingModule.urls.docs.push(elem.href)
      }
    })
  }

  const nullReportingModule = {
    abbr: getAbbr(),
    description: null,
    release: null,
    status: null,
    templates: [],
    title: getTitle(),
    urls: {
      docs: [],
      source: location.href,
      templates: [],
    },
    valid_from: null,
    valid_to: null,
  }

  class ReportingModule {
    constructor(element = document) {
      try {
        this.abbr = getAbbr(element)
        this.description = getDescription(element)
        this.release = getRelease(element)
        this.status = getStatus(element)
        this.templates = []
        this.title = getTitle()
        this.urls = { docs: [], source: null, templates: [] }
        this.valid_from = getValidFrom(element)
        this.valid_to = null
        loadUrls(this, element)
      } catch (err) {
        Object.assign(this, nullReportingModule)
      }
    }

    toString() {
      return JSON.stringify(this)
    }
  }

  const extractEbaReportingModules = (window = window, document = document) => {
    const za = { eba: {} }
    const modules = document.querySelectorAll(selectors.MODULE_ARTICLE)
    modules.forEach((module) => {
      za.eba.module = new ReportingModule(module)
      const moduleKey = `za.eba.module.${za.eba.module.abbr}.${za.eba.module.release}`
      window.localStorage.setItem(moduleKey, za.eba.module.toString())
    })

    return setValidFromDates(loadAllModulesFromLocalStorage())
  }

  const extendDocumentWithNamespace = (
    document,
    reportingModules = [],
    recording = null,
    namespace = 'za',
  ) => {
    const ns = namespace || 'za'
    document[ns] = document[ns] ? document[ns] : { eba: {} }
    document[ns] = {
      eba: {
        recording,
        reportingModules,
      },
    }
  }

  const assertEquals = (elemOne, elemTwo) => {
    const verdict =
      elemOne === elemTwo || JSON.stringify(elemOne) === JSON.stringify(elemTwo)
    if (!verdict) {
      const message = 'AssertionError: The two elements are not equal.'
      throw new TypeError(message)
    }
  }

  const main = (window = window, document = document) => {
    const reportingModules = extractEbaReportingModules(
      window,
      document,
    ).filter((module) => {
      return !_.has(module, 'steps') && !Array.isArray(module.steps)
    })

    const recording = loadReportingModuleRecording(window, document)
    extendDocumentWithNamespace(document, reportingModules, recording)
    assertEquals(reportingModules, document.za.eba.reportingModules)

    // document.za.eba.recording = recording
    window.console.log(`## Usage

### List EBA Reporting Modules' Metadata

  document.za.eba.reportingModules

### Get Reporting Module ELT Instructions

  document.za.eba.recording
    `)
  }

  main(window, document)
})(window, document)