Brazen Framework - Tag Query Engine

Tokenize, merge, and subtract space-separated booru tag queries

이 스크립트는 직접 설치하는 용도가 아닙니다. 다른 스크립트에서 메타 지시문 // @require https://update.greasyfork.org/scripts/583965/1857992/Brazen%20Framework%20-%20Tag%20Query%20Engine.js을(를) 사용하여 포함하는 라이브러리입니다.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

// ==UserScript==
// @name         Brazen Framework - Tag Query Engine
// @namespace    brazenvoid
// @version      1.0.0
// @author       brazenvoid
// @license      GPL-3.0-only
// @description  Tokenize, merge, and subtract space-separated booru tag queries
// @run-at       document-end
// ==/UserScript==

/**
 * Core tag-query engine: parenthesis-aware tokenize, idempotent merge, subtract for display.
 * Site-specific URL/param handling lives in adapters (e.g. GelbooruFamilySearchAdapter).
 */
class BrazenTagQueryEngine
{
  /**
   * @param {{singleInstanceKeys?: Set<string>, metatagKey?: function(string): (string|null)}} [options]
   */
  constructor(options = {})
  {
    this._singleInstanceKeys = options.singleInstanceKeys ?? new Set()
    this._metatagKey = options.metatagKey ?? BrazenTagQueryEngine.defaultMetatagKey
  }

  /**
   * @param {string} token
   * @return {string|null}
   */
  static defaultMetatagKey(token)
  {
    let subject = token.startsWith('-') ? token.slice(1) : token
    if (subject.startsWith('(')) {
      return null
    }
    let colon = subject.indexOf(':')
    return colon > 0 ? subject.slice(0, colon) : null
  }

  /**
   * @param {string} query
   * @return {string[]}
   */
  tokenize(query)
  {
    let tokens = []
    if (!query) {
      return tokens
    }
    let index = 0
    let length = query.length

    while (index < length) {

      while (index < length && /\s/.test(query[index])) {
        index++
      }
      if (index >= length) {
        break
      }

      let start = index
      if (query[index] === '(') {

        let depth = 0
        while (index < length) {
          if (query[index] === '(') {
            depth++
          } else if (query[index] === ')') {
            depth--
            if (depth === 0) {
              index++
              break
            }
          }
          index++
        }

      } else {
        while (index < length && !/\s/.test(query[index])) {
          index++
        }
      }
      tokens.push(query.slice(start, index))
    }
    return tokens
  }

  /**
   * @param {string} token
   * @return {string|null}
   */
  metatagKey(token)
  {
    return this._metatagKey(token)
  }

  /**
   * @param {string} userQuery
   * @param {string[]} defaultTokens
   * @return {string[]}
   */
  merge(userQuery, defaultTokens)
  {
    let userTokens = this.tokenize(userQuery)
    let userKeys = new Set()
    let present = new Set(userTokens)

    for (let token of userTokens) {
      let key = this.metatagKey(token)
      if (key) {
        userKeys.add(key)
      }
    }

    let merged = [...userTokens]
    for (let token of defaultTokens) {

      let key = this.metatagKey(token)
      if (key && this._singleInstanceKeys.has(key) && userKeys.has(key)) {
        continue
      }
      if (present.has(token)) {
        continue
      }
      merged.push(token)
      present.add(token)
    }
    return merged
  }

  /**
   * @param {string[]} tokens
   * @param {string[]} defaultTokens
   * @return {string[]}
   */
  subtract(tokens, defaultTokens)
  {
    let defaults = new Set(defaultTokens)
    return tokens.filter((token) => !defaults.has(token))
  }
}