10 Commits

184 changed files with 4605 additions and 7945 deletions

View File

@ -8,6 +8,5 @@
!.flaskenv !.flaskenv
!boot.sh !boot.sh
!config.py !config.py
!docker-nopaque-entrypoint.sh
!nopaque.py !nopaque.py
!requirements.txt !requirements.txt

210
.env.tpl
View File

@ -1,32 +1,204 @@
############################################################################## ################################################################################
# Variables for use in Docker Compose YAML files # # Docker #
############################################################################## ################################################################################
# DEFAULT: ./data
# NOTE: Use `.` as <project-basedir>
# HOST_DATA_DIR=
# Example: 1000
# HINT: Use this bash command `id -u` # HINT: Use this bash command `id -u`
# NOTE: 0 (= root user) is not allowed
HOST_UID= HOST_UID=
# Example: 1000
# HINT: Use this bash command `id -g` # HINT: Use this bash command `id -g`
HOST_GID= HOST_GID=
# Example: 999
# HINT: Use this bash command `getent group docker | cut -d: -f3` # HINT: Use this bash command `getent group docker | cut -d: -f3`
HOST_DOCKER_GID= HOST_DOCKER_GID=
# DEFAULT: nopaque # DEFAULT: ./logs
# DOCKER_DEFAULT_NETWORK_NAME= # NOTES: Use `.` as <project-basedir>
# HOST_LOG_DIR=
# DEFAULT: ./volumes/db/data # DEFAULT: nopaque_default
# NOTE: Use `.` as <project-basedir> # DOCKER_NETWORK_NAME=
# DOCKER_DB_SERVICE_DATA_VOLUME_SOURCE_PATH=
# DEFAULT: ./volumes/mq/data ################################################################################
# NOTE: Use `.` as <project-basedir> # Flask #
# DOCKER_MQ_SERVICE_DATA_VOLUME_SOURCE_PATH= # https://flask.palletsprojects.com/en/1.1.x/config/ #
################################################################################
# CHOOSE ONE: http, https
# DEFAULT: http
# PREFERRED_URL_SCHEME=
# NOTE: This must be a network share and it must be available on all # DEFAULT: hard to guess string
# Docker Swarm nodes, mounted to the same path with the same # HINT: Use this bash command `python -c "import uuid; print(uuid.uuid4().hex)"`
# user and group ownership. # SECRET_KEY=
DOCKER_NOPAQUE_SERVICE_DATA_VOLUME_SOURCE_PATH=
# DEFAULT: ./volumes/nopaque/logs # DEFAULT: localhost:5000
# NOTE: Use `.` as <project-basedir> # Example: nopaque.example.com/nopaque.example.com:5000
# DOCKER_NOPAQUE_SERVICE_LOGS_VOLUME_SOURCE_PATH=. # HINT: If your instance is publicly available on a different Port then 80/443,
# you will have to add this to the server name
# SERVER_NAME=
# CHOOSE ONE: False, True
# DEFAULT: False
# HINT: Set to true if you redirect http to https
# SESSION_COOKIE_SECURE=
################################################################################
# Flask-Assets #
# https://webassets.readthedocs.io/en/latest/ #
################################################################################
# CHOOSE ONE: False, True
# DEFAULT: False
# ASSETS_DEBUG=
################################################################################
# Flask-Hashids #
# https://github.com/Pevtrick/Flask-Hashids #
################################################################################
# DEFAULT: 16
# HASHIDS_MIN_LENGTH=
# NOTE: Use this bash command `python -c "import uuid; print(uuid.uuid4().hex)"`
# It is strongly recommended that this is NEVER the same as the SECRET_KEY
HASHIDS_SALT=
################################################################################
# Flask-Login #
# https://flask-login.readthedocs.io/en/latest/ #
################################################################################
# CHOOSE ONE: False, True
# DEFAULT: False
# HINT: Set to true if you redirect http to https
# REMEMBER_COOKIE_SECURE=
################################################################################
# Flask-Mail #
# https://pythonhosted.org/Flask-Mail/ #
################################################################################
# EXAMPLE: nopaque Admin <nopaque@example.com>
MAIL_DEFAULT_SENDER=
MAIL_PASSWORD=
# EXAMPLE: smtp.example.com
MAIL_SERVER=
# EXAMPLE: 587
MAIL_PORT=
# CHOOSE ONE: False, True
# DEFAULT: False
# MAIL_USE_SSL=
# CHOOSE ONE: False, True
# DEFAULT: False
# MAIL_USE_TLS=
# EXAMPLE: nopaque@example.com
MAIL_USERNAME=
################################################################################
# Flask-SQLAlchemy #
# https://flask-sqlalchemy.palletsprojects.com/en/2.x/config/ #
################################################################################
# DEFAULT: 'sqlite:///<nopaque-basedir>/data.sqlite'
# NOTE: Use `.` as <nopaque-basedir>,
# Don't use a SQLite database when using Docker
# SQLALCHEMY_DATABASE_URI=
################################################################################
# nopaque #
################################################################################
# An account is registered with this email adress gets automatically assigned
# the administrator role.
# EXAMPLE: admin.nopaque@example.com
NOPAQUE_ADMIN=
# DEFAULT: /mnt/nopaque
# NOTE: This must be a network share and it must be available on all Docker
# Swarm nodes
# NOPAQUE_DATA_DIR=
# CHOOSE ONE: False, True
# DEFAULT: True
# NOPAQUE_IS_PRIMARY_INSTANCE=
# transport://[userid:password]@hostname[:port]/[virtual_host]
NOPAQUE_SOCKETIO_MESSAGE_QUEUE_URI=
# NOTE: Get these from the nopaque development team
NOPAQUE_DOCKER_REGISTRY_USERNAME=
NOPAQUE_DOCKER_REGISTRY_PASSWORD=
# DEFAULT: %Y-%m-%d %H:%M:%S
# NOPAQUE_LOG_DATE_FORMAT=
# DEFAULT: [%(asctime)s] %(levelname)s in %(pathname)s (function: %(funcName)s, line: %(lineno)d): %(message)s
# NOPAQUE_LOG_FORMAT=
# DEFAULT: INFO
# CHOOSE ONE: CRITICAL, ERROR, WARNING, INFO, DEBUG
# NOPAQUE_LOG_LEVEL=
# CHOOSE ONE: False, True
# DEFAULT: True
# NOPAQUE_LOG_FILE_ENABLED=
# DEFAULT: <nopaque-basedir>/logs
# NOTE: Use `.` as <nopaque-basedir>
# NOPAQUE_LOG_FILE_DIR=
# DEFAULT: NOPAQUE_LOG_LEVEL
# CHOOSE ONE: CRITICAL, ERROR, WARNING, INFO, DEBUG
# NOPAQUE_LOG_FILE_LEVEL=
# CHOOSE ONE: False, True
# DEFAULT: False
# NOPAQUE_LOG_STDERR_ENABLED=
# CHOOSE ONE: CRITICAL, ERROR, WARNING, INFO, DEBUG
# DEFAULT: NOPAQUE_LOG_LEVEL
# NOPAQUE_LOG_STDERR_LEVEL=
# CHOOSE ONE: False, True
# DEFAULT: False
# HINT: Set this to True only if you are using a proxy in front of nopaque
# NOPAQUE_PROXY_FIX_ENABLED=
# DEFAULT: 0
# Number of values to trust for X-Forwarded-For
# NOPAQUE_PROXY_FIX_X_FOR=
# DEFAULT: 0
# Number of values to trust for X-Forwarded-Host
# NOPAQUE_PROXY_FIX_X_HOST=
# DEFAULT: 0
# Number of values to trust for X-Forwarded-Port
# NOPAQUE_PROXY_FIX_X_PORT=
# DEFAULT: 0
# Number of values to trust for X-Forwarded-Prefix
# NOPAQUE_PROXY_FIX_X_PREFIX=
# DEFAULT: 0
# Number of values to trust for X-Forwarded-Proto
# NOPAQUE_PROXY_FIX_X_PROTO=
# CHOOSE ONE: False, True
# DEFAULT: False
# NOPAQUE_TRANSKRIBUS_ENABLED=
# READ-COOP account data: https://readcoop.eu/
# NOPAQUE_READCOOP_USERNAME=
# NOPAQUE_READCOOP_PASSWORD=

2
.gitignore vendored
View File

@ -1,6 +1,6 @@
# nopaque specifics # nopaque specifics
app/static/gen/ app/static/gen/
volumes/ data/
docker-compose.override.yml docker-compose.override.yml
logs/ logs/
!logs/dummy !logs/dummy

View File

@ -1,84 +0,0 @@
include:
- template: Security/Container-Scanning.gitlab-ci.yml
##############################################################################
# Pipeline stages in order of execution #
##############################################################################
stages:
- build
- publish
- sca
##############################################################################
# Pipeline behavior #
##############################################################################
workflow:
rules:
# Run the pipeline on commits to the default branch
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
variables:
# Set the Docker image tag to `latest`
DOCKER_IMAGE: $CI_REGISTRY_IMAGE:latest
when: always
# Run the pipeline on tag creation
- if: $CI_COMMIT_TAG
variables:
# Set the Docker image tag to the Git tag name
DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
when: always
# Don't run the pipeline on all other occasions
- when: never
##############################################################################
# Default values for pipeline jobs #
##############################################################################
default:
image: docker:24.0.6
services:
- docker:24.0.6-dind
tags:
- docker
##############################################################################
# CI/CD variables for all jobs in the pipeline #
##############################################################################
variables:
DOCKER_TLS_CERTDIR: /certs
DOCKER_BUILD_PATH: .
DOCKERFILE: Dockerfile
##############################################################################
# Pipeline jobs #
##############################################################################
build:
stage: build
script:
- docker build --tag $DOCKER_IMAGE --file $DOCKERFILE $DOCKER_BUILD_PATH
- docker save $DOCKER_IMAGE > docker_image.tar
artifacts:
paths:
- docker_image.tar
publish:
stage: publish
before_script:
- docker login --username gitlab-ci-token --password $CI_JOB_TOKEN $CI_REGISTRY
script:
- docker load --input docker_image.tar
- docker push $DOCKER_IMAGE
after_script:
- docker logout $CI_REGISTRY
container_scanning:
stage: sca
rules:
# Run the job on commits to the default branch
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: always
# Run the job on tag creation
- if: $CI_COMMIT_TAG
when: always
# Don't run the job on all other occasions
- when: never
variables:
CS_IMAGE: $DOCKER_IMAGE

View File

@ -1,8 +1,7 @@
{ {
"recommendations": [ "recommendations": [
"irongeek.vscode-env", "samuelcolvin.jinjahtml",
"ms-azuretools.vscode-docker", "ms-azuretools.vscode-docker",
"ms-python.python", "ms-python.python"
"samuelcolvin.jinjahtml"
] ]
} }

View File

@ -1,9 +1,13 @@
{ {
"editor.rulers": [79], "editor.rulers": [79],
"files.insertFinalNewline": true, "files.insertFinalNewline": true,
"python.terminal.activateEnvironment": false,
"[css]": { "[css]": {
"editor.tabSize": 2 "editor.tabSize": 2
}, },
"[scss]": {
"editor.tabSize": 2
},
"[html]": { "[html]": {
"editor.tabSize": 2 "editor.tabSize": 2
}, },
@ -13,7 +17,7 @@
"[jinja-html]": { "[jinja-html]": {
"editor.tabSize": 2 "editor.tabSize": 2
}, },
"[scss]": { "[jinja-js]": {
"editor.tabSize": 2 "editor.tabSize": 2
} }
} }

View File

@ -1,9 +1,14 @@
FROM python:3.10.13-slim-bookworm FROM python:3.8.10-slim-buster
LABEL authors="Patrick Jentsch <p.jentsch@uni-bielefeld.de>" LABEL authors="Patrick Jentsch <p.jentsch@uni-bielefeld.de>"
ARG DOCKER_GID
ARG UID
ARG GID
ENV LANG="C.UTF-8" ENV LANG="C.UTF-8"
ENV PYTHONDONTWRITEBYTECODE="1" ENV PYTHONDONTWRITEBYTECODE="1"
ENV PYTHONUNBUFFERED="1" ENV PYTHONUNBUFFERED="1"
@ -12,42 +17,34 @@ ENV PYTHONUNBUFFERED="1"
RUN apt-get update \ RUN apt-get update \
&& apt-get install --no-install-recommends --yes \ && apt-get install --no-install-recommends --yes \
build-essential \ build-essential \
gosu \
libpq-dev \ libpq-dev \
&& rm --recursive /var/lib/apt/lists/* && rm --recursive /var/lib/apt/lists/*
RUN useradd --create-home --no-log-init nopaque \ RUN groupadd --gid "${DOCKER_GID}" docker \
&& groupadd docker \ && groupadd --gid "${GID}" nopaque \
&& usermod --append --groups docker nopaque && useradd --create-home --gid nopaque --groups "${DOCKER_GID}" --no-log-init --uid "${UID}" nopaque
USER nopaque USER nopaque
WORKDIR /home/nopaque WORKDIR /home/nopaque
ENV NOPAQUE_PYTHON3_VENV_PATH="/home/nopaque/.venv" ENV PYTHON3_VENV_PATH="/home/nopaque/venv"
RUN python3 -m venv "${NOPAQUE_PYTHON3_VENV_PATH}" RUN python3 -m venv "${PYTHON3_VENV_PATH}"
ENV PATH="${NOPAQUE_PYTHON3_VENV_PATH}/bin:${PATH}" ENV PATH="${PYTHON3_VENV_PATH}/bin:${PATH}"
COPY --chown=nopaque:nopaque requirements.txt .
RUN python3 -m pip install --requirement requirements.txt \
&& rm requirements.txt
COPY --chown=nopaque:nopaque app app COPY --chown=nopaque:nopaque app app
COPY --chown=nopaque:nopaque migrations migrations COPY --chown=nopaque:nopaque migrations migrations
COPY --chown=nopaque:nopaque tests tests COPY --chown=nopaque:nopaque tests tests
COPY --chown=nopaque:nopaque .flaskenv boot.sh config.py nopaque.py requirements.txt ./ COPY --chown=nopaque:nopaque .flaskenv boot.sh config.py nopaque.py ./
RUN python3 -m pip install --requirement requirements.txt \
&& mkdir logs
USER root
COPY docker-nopaque-entrypoint.sh /usr/local/bin/
EXPOSE 5000 EXPOSE 5000
ENTRYPOINT ["docker-nopaque-entrypoint.sh"] ENTRYPOINT ["./boot.sh"]

View File

@ -1,8 +1,5 @@
# nopaque # nopaque
![release badge](https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque/-/badges/release.svg)
![pipeline badge](https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque/badges/master/pipeline.svg?ignore_skipped=true)
nopaque bundles various tools and services that provide humanities scholars with DH methods and thus can support their various individual research processes. Using nopaque, researchers can subject digitized sources to Optical Character Recognition (OCR). The resulting text files can then be used as a data basis for Natural Language Processing (NLP). The texts are automatically subjected to various linguistic annotations. The data processed via NLP can then be summarized in the web application as corpora and analyzed by means of an information retrieval system through complex search queries. The range of functions of the web application will be successively extended according to the needs of the researchers. nopaque bundles various tools and services that provide humanities scholars with DH methods and thus can support their various individual research processes. Using nopaque, researchers can subject digitized sources to Optical Character Recognition (OCR). The resulting text files can then be used as a data basis for Natural Language Processing (NLP). The texts are automatically subjected to various linguistic annotations. The data processed via NLP can then be summarized in the web application as corpora and analyzed by means of an information retrieval system through complex search queries. The range of functions of the web application will be successively extended according to the needs of the researchers.
## Prerequisites and requirements ## Prerequisites and requirements

View File

@ -8,7 +8,7 @@
pipeline_name: 'ca_core_news_md' pipeline_name: 'ca_core_news_md'
version: '3.2.0' version: '3.2.0'
compatible_service_versions: compatible_service_versions:
- '0.1.0' - '0.1.0'
- title: 'German' - title: 'German'
description: 'German pipeline optimized for CPU. Components: tok2vec, tagger, morphologizer, parser, senter, ner, attribute_ruler, lemmatizer.' description: 'German pipeline optimized for CPU. Components: tok2vec, tagger, morphologizer, parser, senter, ner, attribute_ruler, lemmatizer.'
url: 'https://github.com/explosion/spacy-models/releases/download/de_core_news_md-3.2.0/de_core_news_md-3.2.0.tar.gz' url: 'https://github.com/explosion/spacy-models/releases/download/de_core_news_md-3.2.0/de_core_news_md-3.2.0.tar.gz'
@ -19,7 +19,7 @@
pipeline_name: 'de_core_news_md' pipeline_name: 'de_core_news_md'
version: '3.2.0' version: '3.2.0'
compatible_service_versions: compatible_service_versions:
- '0.1.0' - '0.1.0'
- title: 'Greek' - title: 'Greek'
description: 'Greek pipeline optimized for CPU. Components: tok2vec, morphologizer, parser, senter, ner, attribute_ruler, lemmatizer.' description: 'Greek pipeline optimized for CPU. Components: tok2vec, morphologizer, parser, senter, ner, attribute_ruler, lemmatizer.'
url: 'https://github.com/explosion/spacy-models/releases/download/el_core_news_md-3.2.0/el_core_news_md-3.2.0.tar.gz' url: 'https://github.com/explosion/spacy-models/releases/download/el_core_news_md-3.2.0/el_core_news_md-3.2.0.tar.gz'
@ -120,6 +120,7 @@
version: '3.4.0' version: '3.4.0'
compatible_service_versions: compatible_service_versions:
- '0.1.1' - '0.1.1'
- '0.1.2'
- title: 'German' - title: 'German'
description: 'German pipeline optimized for CPU. Components: tok2vec, tagger, morphologizer, parser, lemmatizer (trainable_lemmatizer), senter, ner.' description: 'German pipeline optimized for CPU. Components: tok2vec, tagger, morphologizer, parser, lemmatizer (trainable_lemmatizer), senter, ner.'
url: 'https://github.com/explosion/spacy-models/releases/download/de_core_news_md-3.4.0/de_core_news_md-3.4.0.tar.gz' url: 'https://github.com/explosion/spacy-models/releases/download/de_core_news_md-3.4.0/de_core_news_md-3.4.0.tar.gz'
@ -131,6 +132,7 @@
version: '3.4.0' version: '3.4.0'
compatible_service_versions: compatible_service_versions:
- '0.1.1' - '0.1.1'
- '0.1.2'
- title: 'Greek' - title: 'Greek'
description: 'Greek pipeline optimized for CPU. Components: tok2vec, morphologizer, parser, lemmatizer (trainable_lemmatizer), senter, ner, attribute_ruler.' description: 'Greek pipeline optimized for CPU. Components: tok2vec, morphologizer, parser, lemmatizer (trainable_lemmatizer), senter, ner, attribute_ruler.'
url: 'https://github.com/explosion/spacy-models/releases/download/el_core_news_md-3.4.0/el_core_news_md-3.4.0.tar.gz' url: 'https://github.com/explosion/spacy-models/releases/download/el_core_news_md-3.4.0/el_core_news_md-3.4.0.tar.gz'
@ -142,6 +144,7 @@
version: '3.4.0' version: '3.4.0'
compatible_service_versions: compatible_service_versions:
- '0.1.1' - '0.1.1'
- '0.1.2'
- title: 'English' - title: 'English'
description: 'English pipeline optimized for CPU. Components: tok2vec, tagger, parser, senter, ner, attribute_ruler, lemmatizer.' description: 'English pipeline optimized for CPU. Components: tok2vec, tagger, parser, senter, ner, attribute_ruler, lemmatizer.'
url: 'https://github.com/explosion/spacy-models/releases/download/en_core_web_md-3.4.1/en_core_web_md-3.4.1.tar.gz' url: 'https://github.com/explosion/spacy-models/releases/download/en_core_web_md-3.4.1/en_core_web_md-3.4.1.tar.gz'
@ -153,6 +156,7 @@
version: '3.4.1' version: '3.4.1'
compatible_service_versions: compatible_service_versions:
- '0.1.1' - '0.1.1'
- '0.1.2'
- title: 'Spanish' - title: 'Spanish'
description: 'Spanish pipeline optimized for CPU. Components: tok2vec, morphologizer, parser, senter, ner, attribute_ruler, lemmatizer.' description: 'Spanish pipeline optimized for CPU. Components: tok2vec, morphologizer, parser, senter, ner, attribute_ruler, lemmatizer.'
url: 'https://github.com/explosion/spacy-models/releases/download/es_core_news_md-3.4.0/es_core_news_md-3.4.0.tar.gz' url: 'https://github.com/explosion/spacy-models/releases/download/es_core_news_md-3.4.0/es_core_news_md-3.4.0.tar.gz'
@ -164,6 +168,7 @@
version: '3.4.0' version: '3.4.0'
compatible_service_versions: compatible_service_versions:
- '0.1.1' - '0.1.1'
- '0.1.2'
- title: 'French' - title: 'French'
description: 'French pipeline optimized for CPU. Components: tok2vec, morphologizer, parser, senter, ner, attribute_ruler, lemmatizer.' description: 'French pipeline optimized for CPU. Components: tok2vec, morphologizer, parser, senter, ner, attribute_ruler, lemmatizer.'
url: 'https://github.com/explosion/spacy-models/releases/download/fr_core_news_md-3.4.0/fr_core_news_md-3.4.0.tar.gz' url: 'https://github.com/explosion/spacy-models/releases/download/fr_core_news_md-3.4.0/fr_core_news_md-3.4.0.tar.gz'
@ -175,6 +180,7 @@
version: '3.4.0' version: '3.4.0'
compatible_service_versions: compatible_service_versions:
- '0.1.1' - '0.1.1'
- '0.1.2'
- title: 'Italian' - title: 'Italian'
description: 'Italian pipeline optimized for CPU. Components: tok2vec, morphologizer, tagger, parser, lemmatizer (trainable_lemmatizer), senter, ner' description: 'Italian pipeline optimized for CPU. Components: tok2vec, morphologizer, tagger, parser, lemmatizer (trainable_lemmatizer), senter, ner'
url: 'https://github.com/explosion/spacy-models/releases/download/it_core_news_md-3.4.0/it_core_news_md-3.4.0.tar.gz' url: 'https://github.com/explosion/spacy-models/releases/download/it_core_news_md-3.4.0/it_core_news_md-3.4.0.tar.gz'
@ -186,6 +192,7 @@
version: '3.4.0' version: '3.4.0'
compatible_service_versions: compatible_service_versions:
- '0.1.1' - '0.1.1'
- '0.1.2'
- title: 'Polish' - title: 'Polish'
description: 'Polish pipeline optimized for CPU. Components: tok2vec, morphologizer, parser, lemmatizer (trainable_lemmatizer), tagger, senter, ner.' description: 'Polish pipeline optimized for CPU. Components: tok2vec, morphologizer, parser, lemmatizer (trainable_lemmatizer), tagger, senter, ner.'
url: 'https://github.com/explosion/spacy-models/releases/download/pl_core_news_md-3.4.0/pl_core_news_md-3.4.0.tar.gz' url: 'https://github.com/explosion/spacy-models/releases/download/pl_core_news_md-3.4.0/pl_core_news_md-3.4.0.tar.gz'
@ -197,6 +204,7 @@
version: '3.4.0' version: '3.4.0'
compatible_service_versions: compatible_service_versions:
- '0.1.1' - '0.1.1'
- '0.1.2'
- title: 'Russian' - title: 'Russian'
description: 'Russian pipeline optimized for CPU. Components: tok2vec, morphologizer, parser, senter, ner, attribute_ruler, lemmatizer.' description: 'Russian pipeline optimized for CPU. Components: tok2vec, morphologizer, parser, senter, ner, attribute_ruler, lemmatizer.'
url: 'https://github.com/explosion/spacy-models/releases/download/ru_core_news_md-3.4.0/ru_core_news_md-3.4.0.tar.gz' url: 'https://github.com/explosion/spacy-models/releases/download/ru_core_news_md-3.4.0/ru_core_news_md-3.4.0.tar.gz'
@ -208,6 +216,7 @@
version: '3.4.0' version: '3.4.0'
compatible_service_versions: compatible_service_versions:
- '0.1.1' - '0.1.1'
- '0.1.2'
- title: 'Chinese' - title: 'Chinese'
description: 'Chinese pipeline optimized for CPU. Components: tok2vec, tagger, parser, senter, ner, attribute_ruler.' description: 'Chinese pipeline optimized for CPU. Components: tok2vec, tagger, parser, senter, ner, attribute_ruler.'
url: 'https://github.com/explosion/spacy-models/releases/download/zh_core_web_md-3.4.0/zh_core_web_md-3.4.0.tar.gz' url: 'https://github.com/explosion/spacy-models/releases/download/zh_core_web_md-3.4.0/zh_core_web_md-3.4.0.tar.gz'
@ -219,3 +228,4 @@
version: '3.4.0' version: '3.4.0'
compatible_service_versions: compatible_service_versions:
- '0.1.1' - '0.1.1'
- '0.1.2'

View File

@ -9,7 +9,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Amharic' # - title: 'Amharic'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/amh.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/amh.traineddata'
@ -21,7 +20,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
- title: 'Arabic' - title: 'Arabic'
description: '' description: ''
url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/ara.traineddata' url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/ara.traineddata'
@ -33,7 +31,6 @@
compatible_service_versions: compatible_service_versions:
- '0.1.0' - '0.1.0'
- '0.1.1' - '0.1.1'
- '0.1.2'
# - title: 'Assamese' # - title: 'Assamese'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/asm.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/asm.traineddata'
@ -45,7 +42,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Azerbaijani' # - title: 'Azerbaijani'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/aze.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/aze.traineddata'
@ -57,7 +53,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Azerbaijani - Cyrillic' # - title: 'Azerbaijani - Cyrillic'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/aze_cyrl.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/aze_cyrl.traineddata'
@ -69,7 +64,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Belarusian' # - title: 'Belarusian'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/bel.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/bel.traineddata'
@ -81,7 +75,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Bengali' # - title: 'Bengali'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/ben.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/ben.traineddata'
@ -93,7 +86,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Tibetan' # - title: 'Tibetan'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/bod.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/bod.traineddata'
@ -105,7 +97,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Bosnian' # - title: 'Bosnian'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/bos.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/bos.traineddata'
@ -117,7 +108,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Bulgarian' # - title: 'Bulgarian'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/bul.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/bul.traineddata'
@ -129,7 +119,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Catalan; Valencian' # - title: 'Catalan; Valencian'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/cat.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/cat.traineddata'
@ -141,7 +130,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Cebuano' # - title: 'Cebuano'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/ceb.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/ceb.traineddata'
@ -153,7 +141,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Czech' # - title: 'Czech'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/ces.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/ces.traineddata'
@ -165,7 +152,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Chinese - Simplified' # - title: 'Chinese - Simplified'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/chi_sim.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/chi_sim.traineddata'
@ -177,7 +163,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
- title: 'Chinese - Traditional' - title: 'Chinese - Traditional'
description: '' description: ''
url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/chi_tra.traineddata' url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/chi_tra.traineddata'
@ -189,7 +174,6 @@
compatible_service_versions: compatible_service_versions:
- '0.1.0' - '0.1.0'
- '0.1.1' - '0.1.1'
- '0.1.2'
# - title: 'Cherokee' # - title: 'Cherokee'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/chr.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/chr.traineddata'
@ -201,7 +185,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Welsh' # - title: 'Welsh'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/cym.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/cym.traineddata'
@ -213,7 +196,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
- title: 'Danish' - title: 'Danish'
description: '' description: ''
url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/dan.traineddata' url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/dan.traineddata'
@ -225,7 +207,6 @@
compatible_service_versions: compatible_service_versions:
- '0.1.0' - '0.1.0'
- '0.1.1' - '0.1.1'
- '0.1.2'
- title: 'German' - title: 'German'
description: '' description: ''
url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/deu.traineddata' url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/deu.traineddata'
@ -237,7 +218,6 @@
compatible_service_versions: compatible_service_versions:
- '0.1.0' - '0.1.0'
- '0.1.1' - '0.1.1'
- '0.1.2'
# - title: 'Dzongkha' # - title: 'Dzongkha'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/dzo.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/dzo.traineddata'
@ -249,7 +229,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
- title: 'Greek, Modern (1453-)' - title: 'Greek, Modern (1453-)'
description: '' description: ''
url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/ell.traineddata' url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/ell.traineddata'
@ -261,7 +240,6 @@
compatible_service_versions: compatible_service_versions:
- '0.1.0' - '0.1.0'
- '0.1.1' - '0.1.1'
- '0.1.2'
- title: 'English' - title: 'English'
description: '' description: ''
url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/eng.traineddata' url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/eng.traineddata'
@ -273,7 +251,6 @@
compatible_service_versions: compatible_service_versions:
- '0.1.0' - '0.1.0'
- '0.1.1' - '0.1.1'
- '0.1.2'
- title: 'English, Middle (1100-1500)' - title: 'English, Middle (1100-1500)'
description: '' description: ''
url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/enm.traineddata' url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/enm.traineddata'
@ -285,7 +262,6 @@
compatible_service_versions: compatible_service_versions:
- '0.1.0' - '0.1.0'
- '0.1.1' - '0.1.1'
- '0.1.2'
# - title: 'Esperanto' # - title: 'Esperanto'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/epo.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/epo.traineddata'
@ -297,7 +273,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Estonian' # - title: 'Estonian'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/est.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/est.traineddata'
@ -309,7 +284,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Basque' # - title: 'Basque'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/eus.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/eus.traineddata'
@ -321,7 +295,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Persian' # - title: 'Persian'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/fas.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/fas.traineddata'
@ -333,7 +306,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Finnish' # - title: 'Finnish'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/fin.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/fin.traineddata'
@ -345,7 +317,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
- title: 'French' - title: 'French'
description: '' description: ''
url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/fra.traineddata' url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/fra.traineddata'
@ -357,7 +328,6 @@
compatible_service_versions: compatible_service_versions:
- '0.1.0' - '0.1.0'
- '0.1.1' - '0.1.1'
- '0.1.2'
- title: 'German Fraktur' - title: 'German Fraktur'
description: '' description: ''
url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/frk.traineddata' url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/frk.traineddata'
@ -369,7 +339,6 @@
compatible_service_versions: compatible_service_versions:
- '0.1.0' - '0.1.0'
- '0.1.1' - '0.1.1'
- '0.1.2'
- title: 'French, Middle (ca. 1400-1600)' - title: 'French, Middle (ca. 1400-1600)'
description: '' description: ''
url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/frm.traineddata' url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/frm.traineddata'
@ -381,7 +350,6 @@
compatible_service_versions: compatible_service_versions:
- '0.1.0' - '0.1.0'
- '0.1.1' - '0.1.1'
- '0.1.2'
# - title: 'Irish' # - title: 'Irish'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/gle.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/gle.traineddata'
@ -393,7 +361,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Galician' # - title: 'Galician'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/glg.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/glg.traineddata'
@ -405,7 +372,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
- title: 'Greek, Ancient (-1453)' - title: 'Greek, Ancient (-1453)'
description: '' description: ''
url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/grc.traineddata' url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/grc.traineddata'
@ -417,7 +383,6 @@
compatible_service_versions: compatible_service_versions:
- '0.1.0' - '0.1.0'
- '0.1.1' - '0.1.1'
- '0.1.2'
# - title: 'Gujarati' # - title: 'Gujarati'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/guj.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/guj.traineddata'
@ -429,7 +394,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Haitian; Haitian Creole' # - title: 'Haitian; Haitian Creole'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/hat.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/hat.traineddata'
@ -441,7 +405,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Hebrew' # - title: 'Hebrew'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/heb.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/heb.traineddata'
@ -453,7 +416,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Hindi' # - title: 'Hindi'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/hin.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/hin.traineddata'
@ -465,7 +427,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Croatian' # - title: 'Croatian'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/hrv.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/hrv.traineddata'
@ -477,7 +438,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Hungarian' # - title: 'Hungarian'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/hun.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/hun.traineddata'
@ -489,7 +449,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Inuktitut' # - title: 'Inuktitut'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/iku.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/iku.traineddata'
@ -501,7 +460,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Indonesian' # - title: 'Indonesian'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/ind.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/ind.traineddata'
@ -513,7 +471,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Icelandic' # - title: 'Icelandic'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/isl.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/isl.traineddata'
@ -525,7 +482,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
- title: 'Italian' - title: 'Italian'
description: '' description: ''
url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/ita.traineddata' url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/ita.traineddata'
@ -537,7 +493,6 @@
compatible_service_versions: compatible_service_versions:
- '0.1.0' - '0.1.0'
- '0.1.1' - '0.1.1'
- '0.1.2'
- title: 'Italian - Old' - title: 'Italian - Old'
description: '' description: ''
url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/ita_old.traineddata' url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/ita_old.traineddata'
@ -549,7 +504,6 @@
compatible_service_versions: compatible_service_versions:
- '0.1.0' - '0.1.0'
- '0.1.1' - '0.1.1'
- '0.1.2'
# - title: 'Javanese' # - title: 'Javanese'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/jav.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/jav.traineddata'
@ -561,7 +515,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Japanese' # - title: 'Japanese'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/jpn.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/jpn.traineddata'
@ -573,7 +526,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Kannada' # - title: 'Kannada'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/kan.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/kan.traineddata'
@ -585,7 +537,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Georgian' # - title: 'Georgian'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/kat.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/kat.traineddata'
@ -597,7 +548,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Georgian - Old' # - title: 'Georgian - Old'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/kat_old.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/kat_old.traineddata'
@ -609,7 +559,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Kazakh' # - title: 'Kazakh'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/kaz.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/kaz.traineddata'
@ -621,7 +570,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Central Khmer' # - title: 'Central Khmer'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/khm.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/khm.traineddata'
@ -633,7 +581,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Kirghiz; Kyrgyz' # - title: 'Kirghiz; Kyrgyz'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/kir.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/kir.traineddata'
@ -645,7 +592,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Korean' # - title: 'Korean'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/kor.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/kor.traineddata'
@ -657,7 +603,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Kurdish' # - title: 'Kurdish'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/kur.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/kur.traineddata'
@ -669,7 +614,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Lao' # - title: 'Lao'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/lao.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/lao.traineddata'
@ -681,7 +625,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Latin' # - title: 'Latin'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/lat.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/lat.traineddata'
@ -693,7 +636,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Latvian' # - title: 'Latvian'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/lav.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/lav.traineddata'
@ -705,7 +647,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Lithuanian' # - title: 'Lithuanian'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/lit.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/lit.traineddata'
@ -717,7 +658,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Malayalam' # - title: 'Malayalam'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/mal.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/mal.traineddata'
@ -729,7 +669,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Marathi' # - title: 'Marathi'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/mar.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/mar.traineddata'
@ -741,7 +680,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Macedonian' # - title: 'Macedonian'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/mkd.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/mkd.traineddata'
@ -753,7 +691,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Maltese' # - title: 'Maltese'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/mlt.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/mlt.traineddata'
@ -765,7 +702,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Malay' # - title: 'Malay'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/msa.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/msa.traineddata'
@ -777,7 +713,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Burmese' # - title: 'Burmese'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/mya.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/mya.traineddata'
@ -789,7 +724,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Nepali' # - title: 'Nepali'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/nep.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/nep.traineddata'
@ -801,7 +735,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Dutch; Flemish' # - title: 'Dutch; Flemish'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/nld.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/nld.traineddata'
@ -813,7 +746,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Norwegian' # - title: 'Norwegian'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/nor.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/nor.traineddata'
@ -825,7 +757,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Oriya' # - title: 'Oriya'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/ori.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/ori.traineddata'
@ -837,7 +768,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Panjabi; Punjabi' # - title: 'Panjabi; Punjabi'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/pan.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/pan.traineddata'
@ -849,7 +779,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Polish' # - title: 'Polish'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/pol.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/pol.traineddata'
@ -861,7 +790,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
- title: 'Portuguese' - title: 'Portuguese'
description: '' description: ''
url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/por.traineddata' url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/por.traineddata'
@ -873,7 +801,6 @@
compatible_service_versions: compatible_service_versions:
- '0.1.0' - '0.1.0'
- '0.1.1' - '0.1.1'
- '0.1.2'
# - title: 'Pushto; Pashto' # - title: 'Pushto; Pashto'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/pus.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/pus.traineddata'
@ -885,7 +812,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Romanian; Moldavian; Moldovan' # - title: 'Romanian; Moldavian; Moldovan'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/ron.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/ron.traineddata'
@ -897,7 +823,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
- title: 'Russian' - title: 'Russian'
description: '' description: ''
url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/rus.traineddata' url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/rus.traineddata'
@ -909,7 +834,6 @@
compatible_service_versions: compatible_service_versions:
- '0.1.0' - '0.1.0'
- '0.1.1' - '0.1.1'
- '0.1.2'
# - title: 'Sanskrit' # - title: 'Sanskrit'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/san.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/san.traineddata'
@ -921,7 +845,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Sinhala; Sinhalese' # - title: 'Sinhala; Sinhalese'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/sin.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/sin.traineddata'
@ -933,7 +856,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Slovak' # - title: 'Slovak'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/slk.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/slk.traineddata'
@ -945,7 +867,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Slovenian' # - title: 'Slovenian'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/slv.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/slv.traineddata'
@ -957,7 +878,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
- title: 'Spanish; Castilian' - title: 'Spanish; Castilian'
description: '' description: ''
url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/spa.traineddata' url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/spa.traineddata'
@ -969,7 +889,6 @@
compatible_service_versions: compatible_service_versions:
- '0.1.0' - '0.1.0'
- '0.1.1' - '0.1.1'
- '0.1.2'
- title: 'Spanish; Castilian - Old' - title: 'Spanish; Castilian - Old'
description: '' description: ''
url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/spa_old.traineddata' url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/spa_old.traineddata'
@ -981,7 +900,6 @@
compatible_service_versions: compatible_service_versions:
- '0.1.0' - '0.1.0'
- '0.1.1' - '0.1.1'
- '0.1.2'
# - title: 'Albanian' # - title: 'Albanian'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/sqi.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/sqi.traineddata'
@ -993,7 +911,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Serbian' # - title: 'Serbian'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/srp.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/srp.traineddata'
@ -1005,7 +922,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Serbian - Latin' # - title: 'Serbian - Latin'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/srp_latn.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/srp_latn.traineddata'
@ -1017,7 +933,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Swahili' # - title: 'Swahili'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/swa.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/swa.traineddata'
@ -1029,7 +944,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Swedish' # - title: 'Swedish'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/swe.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/swe.traineddata'
@ -1041,7 +955,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Syriac' # - title: 'Syriac'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/syr.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/syr.traineddata'
@ -1053,7 +966,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Tamil' # - title: 'Tamil'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/tam.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/tam.traineddata'
@ -1065,7 +977,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Telugu' # - title: 'Telugu'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/tel.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/tel.traineddata'
@ -1077,7 +988,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Tajik' # - title: 'Tajik'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/tgk.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/tgk.traineddata'
@ -1089,7 +999,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Tagalog' # - title: 'Tagalog'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/tgl.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/tgl.traineddata'
@ -1101,7 +1010,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Thai' # - title: 'Thai'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/tha.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/tha.traineddata'
@ -1113,7 +1021,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Tigrinya' # - title: 'Tigrinya'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/tir.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/tir.traineddata'
@ -1125,7 +1032,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Turkish' # - title: 'Turkish'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/tur.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/tur.traineddata'
@ -1137,7 +1043,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Uighur; Uyghur' # - title: 'Uighur; Uyghur'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/uig.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/uig.traineddata'
@ -1149,7 +1054,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Ukrainian' # - title: 'Ukrainian'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/ukr.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/ukr.traineddata'
@ -1161,7 +1065,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Urdu' # - title: 'Urdu'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/urd.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/urd.traineddata'
@ -1173,7 +1076,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Uzbek' # - title: 'Uzbek'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/uzb.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/uzb.traineddata'
@ -1185,7 +1087,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Uzbek - Cyrillic' # - title: 'Uzbek - Cyrillic'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/uzb_cyrl.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/uzb_cyrl.traineddata'
@ -1197,7 +1098,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Vietnamese' # - title: 'Vietnamese'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/vie.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/vie.traineddata'
@ -1209,7 +1109,6 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'
# - title: 'Yiddish' # - title: 'Yiddish'
# description: '' # description: ''
# url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/yid.traineddata' # url: 'https://github.com/tesseract-ocr/tessdata/raw/4.1.0/yid.traineddata'
@ -1221,4 +1120,3 @@
# compatible_service_versions: # compatible_service_versions:
# - '0.1.0' # - '0.1.0'
# - '0.1.1' # - '0.1.1'
# - '0.1.2'

View File

@ -13,6 +13,7 @@ from flask_paranoid import Paranoid
from flask_socketio import SocketIO from flask_socketio import SocketIO
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_hashids import Hashids from flask_hashids import Hashids
from werkzeug.exceptions import HTTPException
apifairy = APIFairy() apifairy = APIFairy()
@ -73,10 +74,8 @@ def create_app(config: Config = Config) -> Flask:
app.register_blueprint(contributions_blueprint, url_prefix='/contributions') app.register_blueprint(contributions_blueprint, url_prefix='/contributions')
from .corpora import bp as corpora_blueprint from .corpora import bp as corpora_blueprint
from .corpora.cqi_over_sio import CQiNamespace
default_breadcrumb_root(corpora_blueprint, '.corpora') default_breadcrumb_root(corpora_blueprint, '.corpora')
app.register_blueprint(corpora_blueprint, cli_group='corpus', url_prefix='/corpora') app.register_blueprint(corpora_blueprint, cli_group='corpus', url_prefix='/corpora')
socketio.on_namespace(CQiNamespace('/cqi_over_sio'))
from .errors import bp as errors_bp from .errors import bp as errors_bp
app.register_blueprint(errors_bp) app.register_blueprint(errors_bp)
@ -101,7 +100,4 @@ def create_app(config: Config = Config) -> Flask:
default_breadcrumb_root(users_blueprint, '.users') default_breadcrumb_root(users_blueprint, '.users')
app.register_blueprint(users_blueprint, url_prefix='/users') app.register_blueprint(users_blueprint, url_prefix='/users')
from .workshops import bp as workshops_blueprint
app.register_blueprint(workshops_blueprint, url_prefix='/workshops')
return app return app

View File

@ -16,4 +16,4 @@ def before_request():
pass pass
from . import cli, files, followers, routes, json_routes from . import cli, cqi_over_socketio, files, followers, routes, json_routes

View File

@ -19,9 +19,6 @@ def reset():
for corpus in [x for x in Corpus.query.all() if x.status in status]: for corpus in [x for x in Corpus.query.all() if x.status in status]:
print(f'Resetting corpus {corpus}') print(f'Resetting corpus {corpus}')
shutil.rmtree(os.path.join(corpus.path, 'cwb'), ignore_errors=True) shutil.rmtree(os.path.join(corpus.path, 'cwb'), ignore_errors=True)
os.mkdir(os.path.join(corpus.path, 'cwb'))
os.mkdir(os.path.join(corpus.path, 'cwb', 'data'))
os.mkdir(os.path.join(corpus.path, 'cwb', 'registry'))
corpus.status = CorpusStatus.UNPREPARED corpus.status = CorpusStatus.UNPREPARED
corpus.num_analysis_sessions = 0 corpus.num_analysis_sessions = 0
db.session.commit() db.session.commit()

View File

@ -1,206 +0,0 @@
from cqi import CQiClient
from cqi.errors import CQiException
from cqi.status import CQiStatus
from docker.models.containers import Container
from flask import current_app, session
from flask_login import current_user
from flask_socketio import Namespace
from inspect import signature
from threading import Lock
from typing import Callable, Dict, List, Optional
from app import db, docker_client, hashids, socketio
from app.decorators import socketio_login_required
from app.models import Corpus, CorpusStatus
from . import extensions
'''
This package tunnels the Corpus Query interface (CQi) protocol through
Socket.IO (SIO) by tunneling CQi API calls through an event called "exec".
Basic concept:
1. A client connects to the "/cqi_over_sio" namespace.
2. The client emits the "init" event and provides a corpus id for the corpus
that should be analysed in this session.
1.1 The analysis session counter of the corpus is incremented.
1.2 A CQiClient and a (Mutex) Lock belonging to it is created.
1.3 Wait until the CQP server is running.
1.4 Connect the CQiClient to the server.
1.5 Save the CQiClient, the Lock and the corpus id in the session for
subsequential use.
2. The client emits the "exec" event provides the name of a CQi API function
arguments (optional).
- The event "exec" handler will execute the function, make sure that the
result is serializable and returns the result back to the client.
4. Wait for more events
5. The client disconnects from the "/cqi_over_sio" namespace
1.1 The analysis session counter of the corpus is decremented.
1.2 The CQiClient and (Mutex) Lock belonging to it are teared down.
'''
CQI_API_FUNCTION_NAMES: List[str] = [
'ask_feature_cl_2_3',
'ask_feature_cqi_1_0',
'ask_feature_cqp_2_3',
'cl_alg2cpos',
'cl_attribute_size',
'cl_cpos2alg',
'cl_cpos2id',
'cl_cpos2lbound',
'cl_cpos2rbound',
'cl_cpos2str',
'cl_cpos2struc',
'cl_drop_attribute',
'cl_id2cpos',
'cl_id2freq',
'cl_id2str',
'cl_idlist2cpos',
'cl_lexicon_size',
'cl_regex2id',
'cl_str2id',
'cl_struc2cpos',
'cl_struc2str',
'corpus_alignment_attributes',
'corpus_charset',
'corpus_drop_corpus',
'corpus_full_name',
'corpus_info',
'corpus_list_corpora',
'corpus_positional_attributes',
'corpus_properties',
'corpus_structural_attribute_has_values',
'corpus_structural_attributes',
'cqp_drop_subcorpus',
'cqp_dump_subcorpus',
'cqp_fdist_1',
'cqp_fdist_2',
'cqp_list_subcorpora',
'cqp_query',
'cqp_subcorpus_has_field',
'cqp_subcorpus_size',
'ctrl_bye',
'ctrl_connect',
'ctrl_last_general_error',
'ctrl_ping',
'ctrl_user_abort'
]
class CQiNamespace(Namespace):
@socketio_login_required
def on_connect(self):
pass
@socketio_login_required
def on_init(self, db_corpus_hashid: str):
db_corpus_id: int = hashids.decode(db_corpus_hashid)
db_corpus: Optional[Corpus] = Corpus.query.get(db_corpus_id)
if db_corpus is None:
return {'code': 404, 'msg': 'Not Found'}
if not (db_corpus.user == current_user
or current_user.is_following_corpus(db_corpus)
or current_user.is_administrator()):
return {'code': 403, 'msg': 'Forbidden'}
if db_corpus.status not in [
CorpusStatus.BUILT,
CorpusStatus.STARTING_ANALYSIS_SESSION,
CorpusStatus.RUNNING_ANALYSIS_SESSION,
CorpusStatus.CANCELING_ANALYSIS_SESSION
]:
return {'code': 424, 'msg': 'Failed Dependency'}
if db_corpus.num_analysis_sessions is None:
db_corpus.num_analysis_sessions = 0
db.session.commit()
db_corpus.num_analysis_sessions = Corpus.num_analysis_sessions + 1
db.session.commit()
retry_counter: int = 20
while db_corpus.status != CorpusStatus.RUNNING_ANALYSIS_SESSION:
if retry_counter == 0:
db_corpus.num_analysis_sessions = Corpus.num_analysis_sessions - 1
db.session.commit()
return {'code': 408, 'msg': 'Request Timeout'}
socketio.sleep(3)
retry_counter -= 1
db.session.refresh(db_corpus)
# cqi_client: CQiClient = CQiClient(f'cqpserver_{db_corpus_id}')
cqpserver_container_name: str = f'cqpserver_{db_corpus_id}'
cqpserver_container: Container = docker_client.containers.get(cqpserver_container_name)
cqpserver_host: str = cqpserver_container.attrs['NetworkSettings']['Networks'][current_app.config['NOPAQUE_DOCKER_NETWORK_NAME']]['IPAddress']
cqi_client: CQiClient = CQiClient(cqpserver_host)
session['cqi_over_sio'] = {
'cqi_client': cqi_client,
'cqi_client_lock': Lock(),
'db_corpus_id': db_corpus_id
}
return {'code': 200, 'msg': 'OK'}
@socketio_login_required
def on_exec(self, fn_name: str, fn_args: Dict = {}):
try:
cqi_client: CQiClient = session['cqi_over_sio']['cqi_client']
cqi_client_lock: Lock = session['cqi_over_sio']['cqi_client_lock']
except KeyError:
return {'code': 424, 'msg': 'Failed Dependency'}
if fn_name in CQI_API_FUNCTION_NAMES:
fn: Callable = getattr(cqi_client.api, fn_name)
elif fn_name in extensions.CQI_EXTENSION_FUNCTION_NAMES:
fn: Callable = getattr(extensions, fn_name)
else:
return {'code': 400, 'msg': 'Bad Request'}
for param in signature(fn).parameters.values():
if param.default is param.empty:
if param.name not in fn_args:
return {'code': 400, 'msg': 'Bad Request'}
else:
if param.name not in fn_args:
continue
if type(fn_args[param.name]) is not param.annotation:
return {'code': 400, 'msg': 'Bad Request'}
cqi_client_lock.acquire()
try:
fn_return_value = fn(**fn_args)
except BrokenPipeError as e:
return {'code': 500, 'msg': 'Internal Server Error'}
except CQiException as e:
return {
'code': 502,
'msg': 'Bad Gateway',
'payload': {
'code': e.code,
'desc': e.description,
'msg': e.__class__.__name__
}
}
finally:
cqi_client_lock.release()
if isinstance(fn_return_value, CQiStatus):
payload = {
'code': fn_return_value.code,
'msg': fn_return_value.__class__.__name__
}
else:
payload = fn_return_value
return {'code': 200, 'msg': 'OK', 'payload': payload}
def on_disconnect(self):
try:
cqi_client: CQiClient = session['cqi_over_sio']['cqi_client']
cqi_client_lock: Lock = session['cqi_over_sio']['cqi_client_lock']
db_corpus_id: int = session['cqi_over_sio']['db_corpus_id']
except KeyError:
return
cqi_client_lock.acquire()
try:
session.pop('cqi_over_sio')
except KeyError:
pass
try:
cqi_client.api.ctrl_bye()
except (BrokenPipeError, CQiException):
pass
cqi_client_lock.release()
db_corpus: Optional[Corpus] = Corpus.query.get(db_corpus_id)
if db_corpus is None:
return
db_corpus.num_analysis_sessions = Corpus.num_analysis_sessions - 1
db.session.commit()

View File

@ -1,288 +0,0 @@
from collections import Counter
from cqi import CQiClient
from cqi.models.corpora import Corpus as CQiCorpus
from cqi.models.subcorpora import Subcorpus as CQiSubcorpus
from cqi.models.attributes import (
PositionalAttribute as CQiPositionalAttribute,
StructuralAttribute as CQiStructuralAttribute
)
from cqi.status import StatusOk as CQiStatusOk
from flask import session
from typing import Dict, List
import gzip
import json
import math
import os
from app import db
from app.models import Corpus
from .utils import lookups_by_cpos, partial_export_subcorpus, export_subcorpus
CQI_EXTENSION_FUNCTION_NAMES: List[str] = [
'ext_corpus_update_db',
'ext_corpus_static_data',
'ext_corpus_paginate_corpus',
'ext_cqp_paginate_subcorpus',
'ext_cqp_partial_export_subcorpus',
'ext_cqp_export_subcorpus',
]
def ext_corpus_update_db(corpus: str) -> CQiStatusOk:
cqi_client: CQiClient = session['cqi_over_sio']['cqi_client']
db_corpus_id: int = session['cqi_over_sio']['db_corpus_id']
db_corpus: Corpus = Corpus.query.get(db_corpus_id)
cqi_corpus: CQiCorpus = cqi_client.corpora.get(corpus)
db_corpus.num_tokens = cqi_corpus.size
db.session.commit()
return CQiStatusOk()
def ext_corpus_static_data(corpus: str) -> Dict:
db_corpus_id: int = session['cqi_over_sio']['db_corpus_id']
db_corpus: Corpus = Corpus.query.get(db_corpus_id)
static_data_file_path: str = os.path.join(db_corpus.path, 'cwb', 'static.json.gz')
if os.path.exists(static_data_file_path):
with open(static_data_file_path, 'rb') as f:
return f.read()
cqi_client: CQiClient = session['cqi_over_sio']['cqi_client']
cqi_corpus: CQiCorpus = cqi_client.corpora.get(corpus)
cqi_p_attrs: List[CQiPositionalAttribute] = cqi_corpus.positional_attributes.list()
cqi_s_attrs: List[CQiStructuralAttribute] = cqi_corpus.structural_attributes.list()
static_data = {
'corpus': {
'bounds': [0, cqi_corpus.size - 1],
'freqs': {}
},
'p_attrs': {},
's_attrs': {},
'values': {'p_attrs': {}, 's_attrs': {}}
}
for p_attr in cqi_p_attrs:
print(f'corpus.freqs.{p_attr.name}')
static_data['corpus']['freqs'][p_attr.name] = []
p_attr_id_list: List[int] = list(range(p_attr.lexicon_size))
static_data['corpus']['freqs'][p_attr.name].extend(p_attr.freqs_by_ids(p_attr_id_list))
del p_attr_id_list
print(f'p_attrs.{p_attr.name}')
static_data['p_attrs'][p_attr.name] = []
cpos_list: List[int] = list(range(cqi_corpus.size))
static_data['p_attrs'][p_attr.name].extend(p_attr.ids_by_cpos(cpos_list))
del cpos_list
print(f'values.p_attrs.{p_attr.name}')
static_data['values']['p_attrs'][p_attr.name] = []
p_attr_id_list: List[int] = list(range(p_attr.lexicon_size))
static_data['values']['p_attrs'][p_attr.name].extend(p_attr.values_by_ids(p_attr_id_list))
del p_attr_id_list
for s_attr in cqi_s_attrs:
if s_attr.has_values:
continue
static_data['s_attrs'][s_attr.name] = {'lexicon': [], 'values': None}
if s_attr.name in ['s', 'ent']:
##############################################################
# A faster way to get cpos boundaries for smaller s_attrs #
# Note: Needs more testing, don't use it in production #
##############################################################
cqi_corpus.query('Last', f'<{s_attr.name}> []* </{s_attr.name}>;')
cqi_subcorpus: CQiSubcorpus = cqi_corpus.subcorpora.get('Last')
first_match: int = 0
last_match: int = cqi_subcorpus.size - 1
match_boundaries = zip(
range(first_match, last_match + 1),
cqi_subcorpus.dump(
cqi_subcorpus.fields['match'],
first_match,
last_match
),
cqi_subcorpus.dump(
cqi_subcorpus.fields['matchend'],
first_match,
last_match
)
)
cqi_subcorpus.drop()
del cqi_subcorpus, first_match, last_match
for id, lbound, rbound in match_boundaries:
static_data['s_attrs'][s_attr.name]['lexicon'].append({})
print(f's_attrs.{s_attr.name}.lexicon.{id}.bounds')
static_data['s_attrs'][s_attr.name]['lexicon'][id]['bounds'] = [lbound, rbound]
del match_boundaries
if s_attr.name != 'text':
continue
for id in range(0, s_attr.size):
static_data['s_attrs'][s_attr.name]['lexicon'].append({})
# This is a very slow operation, thats why we only use it for
# the text attribute
lbound, rbound = s_attr.cpos_by_id(id)
print(f's_attrs.{s_attr.name}.lexicon.{id}.bounds')
static_data['s_attrs'][s_attr.name]['lexicon'][id]['bounds'] = [lbound, rbound]
static_data['s_attrs'][s_attr.name]['lexicon'][id]['freqs'] = {}
cpos_list: List[int] = list(range(lbound, rbound + 1))
for p_attr in cqi_p_attrs:
p_attr_ids: List[int] = []
p_attr_ids.extend(p_attr.ids_by_cpos(cpos_list))
print(f's_attrs.{s_attr.name}.lexicon.{id}.freqs.{p_attr.name}')
static_data['s_attrs'][s_attr.name]['lexicon'][id]['freqs'][p_attr.name] = dict(Counter(p_attr_ids))
del p_attr_ids
del cpos_list
sub_s_attrs: List[CQiStructuralAttribute] = cqi_corpus.structural_attributes.list(filters={'part_of': s_attr})
print(f's_attrs.{s_attr.name}.values')
static_data['s_attrs'][s_attr.name]['values'] = [
sub_s_attr.name[(len(s_attr.name) + 1):]
for sub_s_attr in sub_s_attrs
]
s_attr_id_list: List[int] = list(range(s_attr.size))
sub_s_attr_values: List[str] = []
for sub_s_attr in sub_s_attrs:
tmp = []
tmp.extend(sub_s_attr.values_by_ids(s_attr_id_list))
sub_s_attr_values.append(tmp)
del tmp
del s_attr_id_list
print(f'values.s_attrs.{s_attr.name}')
static_data['values']['s_attrs'][s_attr.name] = [
{
s_attr_value_name: sub_s_attr_values[s_attr_value_name_idx][s_attr_id]
for s_attr_value_name_idx, s_attr_value_name in enumerate(
static_data['s_attrs'][s_attr.name]['values']
)
} for s_attr_id in range(0, s_attr.size)
]
del sub_s_attr_values
print('Saving static data to file')
with gzip.open(static_data_file_path, 'wt') as f:
json.dump(static_data, f)
del static_data
print('Sending static data to client')
with open(static_data_file_path, 'rb') as f:
return f.read()
def ext_corpus_paginate_corpus(
corpus: str,
page: int = 1,
per_page: int = 20
) -> Dict:
cqi_client: CQiClient = session['cqi_over_sio']['cqi_client']
cqi_corpus = cqi_client.corpora.get(corpus)
# Sanity checks
if (
per_page < 1
or page < 1
or (
cqi_corpus.size > 0
and page > math.ceil(cqi_corpus.size / per_page)
)
):
return {'code': 416, 'msg': 'Range Not Satisfiable'}
first_cpos = (page - 1) * per_page
last_cpos = min(cqi_corpus.size, first_cpos + per_page)
cpos_list = [*range(first_cpos, last_cpos)]
lookups = lookups_by_cpos(cqi_corpus, cpos_list)
payload = {}
# the items for the current page
payload['items'] = [cpos_list]
# the lookups for the items
payload['lookups'] = lookups
# the total number of items matching the query
payload['total'] = cqi_corpus.size
# the number of items to be displayed on a page.
payload['per_page'] = per_page
# The total number of pages
payload['pages'] = math.ceil(payload['total'] / payload['per_page'])
# the current page number (1 indexed)
payload['page'] = page if payload['pages'] > 0 else None
# True if a previous page exists
payload['has_prev'] = payload['page'] > 1 if payload['page'] else False
# True if a next page exists.
payload['has_next'] = payload['page'] < payload['pages'] if payload['page'] else False # noqa
# Number of the previous page.
payload['prev_num'] = payload['page'] - 1 if payload['has_prev'] else None
# Number of the next page
payload['next_num'] = payload['page'] + 1 if payload['has_next'] else None
return payload
def ext_cqp_paginate_subcorpus(
subcorpus: str,
context: int = 50,
page: int = 1,
per_page: int = 20
) -> Dict:
corpus_name, subcorpus_name = subcorpus.split(':', 1)
cqi_client: CQiClient = session['cqi_over_sio']['cqi_client']
cqi_corpus = cqi_client.corpora.get(corpus_name)
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
# Sanity checks
if (
per_page < 1
or page < 1
or (
cqi_subcorpus.size > 0
and page > math.ceil(cqi_subcorpus.size / per_page)
)
):
return {'code': 416, 'msg': 'Range Not Satisfiable'}
offset = (page - 1) * per_page
cutoff = per_page
cqi_results_export = export_subcorpus(
cqi_subcorpus, context=context, cutoff=cutoff, offset=offset)
payload = {}
# the items for the current page
payload['items'] = cqi_results_export.pop('matches')
# the lookups for the items
payload['lookups'] = cqi_results_export
# the total number of items matching the query
payload['total'] = cqi_subcorpus.size
# the number of items to be displayed on a page.
payload['per_page'] = per_page
# The total number of pages
payload['pages'] = math.ceil(payload['total'] / payload['per_page'])
# the current page number (1 indexed)
payload['page'] = page if payload['pages'] > 0 else None
# True if a previous page exists
payload['has_prev'] = payload['page'] > 1 if payload['page'] else False
# True if a next page exists.
payload['has_next'] = payload['page'] < payload['pages'] if payload['page'] else False # noqa
# Number of the previous page.
payload['prev_num'] = payload['page'] - 1 if payload['has_prev'] else None
# Number of the next page
payload['next_num'] = payload['page'] + 1 if payload['has_next'] else None
return payload
def ext_cqp_partial_export_subcorpus(
subcorpus: str,
match_id_list: list,
context: int = 50
) -> Dict:
corpus_name, subcorpus_name = subcorpus.split(':', 1)
cqi_client: CQiClient = session['cqi_over_sio']['cqi_client']
cqi_corpus = cqi_client.corpora.get(corpus_name)
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
cqi_subcorpus_partial_export = partial_export_subcorpus(cqi_subcorpus, match_id_list, context=context)
return cqi_subcorpus_partial_export
def ext_cqp_export_subcorpus(
subcorpus: str,
context: int = 50
) -> Dict:
corpus_name, subcorpus_name = subcorpus.split(':', 1)
cqi_client: CQiClient = session['cqi_over_sio']['cqi_client']
cqi_corpus = cqi_client.corpora.get(corpus_name)
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
cqi_subcorpus_export = export_subcorpus(cqi_subcorpus, context=context)
return cqi_subcorpus_export

View File

@ -1,131 +0,0 @@
from cqi.models.corpora import Corpus as CQiCorpus
from cqi.models.subcorpora import Subcorpus as CQiSubcorpus
from typing import Dict, List
def lookups_by_cpos(corpus: CQiCorpus, cpos_list: List[int]) -> Dict:
lookups = {}
lookups['cpos_lookup'] = {cpos: {} for cpos in cpos_list}
for attr in corpus.positional_attributes.list():
cpos_attr_values: List[str] = attr.values_by_cpos(cpos_list)
for i, cpos in enumerate(cpos_list):
lookups['cpos_lookup'][cpos][attr.name] = cpos_attr_values[i]
for attr in corpus.structural_attributes.list():
# We only want to iterate over non subattributes, identifiable by
# attr.has_values == False
if attr.has_values:
continue
cpos_attr_ids: List[int] = attr.ids_by_cpos(cpos_list)
for i, cpos in enumerate(cpos_list):
if cpos_attr_ids[i] == -1:
continue
lookups['cpos_lookup'][cpos][attr.name] = cpos_attr_ids[i]
occured_attr_ids = [x for x in set(cpos_attr_ids) if x != -1]
if len(occured_attr_ids) == 0:
continue
subattrs = corpus.structural_attributes.list(filters={'part_of': attr})
if len(subattrs) == 0:
continue
lookup_name: str = f'{attr.name}_lookup'
lookups[lookup_name] = {}
for attr_id in occured_attr_ids:
lookups[lookup_name][attr_id] = {}
for subattr in subattrs:
subattr_name = subattr.name[(len(attr.name) + 1):] # noqa
for i, subattr_value in enumerate(subattr.values_by_ids(occured_attr_ids)): # noqa
lookups[lookup_name][occured_attr_ids[i]][subattr_name] = subattr_value # noqa
return lookups
def partial_export_subcorpus(
subcorpus: CQiSubcorpus,
match_id_list: List[int],
context: int = 25
) -> Dict:
if subcorpus.size == 0:
return {"matches": []}
match_boundaries = []
for match_id in match_id_list:
if match_id < 0 or match_id >= subcorpus.size:
continue
match_boundaries.append(
(
match_id,
subcorpus.dump(subcorpus.fields['match'], match_id, match_id)[0],
subcorpus.dump(subcorpus.fields['matchend'], match_id, match_id)[0]
)
)
cpos_set = set()
matches = []
for match_boundary in match_boundaries:
match_num, match_start, match_end = match_boundary
c = (match_start, match_end)
if match_start == 0 or context == 0:
lc = None
cpos_list_lbound = match_start
else:
lc_lbound = max(0, (match_start - context))
lc_rbound = match_start - 1
lc = (lc_lbound, lc_rbound)
cpos_list_lbound = lc_lbound
if match_end == (subcorpus.collection.corpus.size - 1) or context == 0:
rc = None
cpos_list_rbound = match_end
else:
rc_lbound = match_end + 1
rc_rbound = min(
(match_end + context),
(subcorpus.collection.corpus.size - 1)
)
rc = (rc_lbound, rc_rbound)
cpos_list_rbound = rc_rbound
match = {'num': match_num, 'lc': lc, 'c': c, 'rc': rc}
matches.append(match)
cpos_set.update(range(cpos_list_lbound, cpos_list_rbound + 1))
lookups = lookups_by_cpos(subcorpus.collection.corpus, list(cpos_set))
return {'matches': matches, **lookups}
def export_subcorpus(
subcorpus: CQiSubcorpus,
context: int = 25,
cutoff: float = float('inf'),
offset: int = 0
) -> Dict:
if subcorpus.size == 0:
return {"matches": []}
first_match = max(0, offset)
last_match = min((offset + cutoff - 1), (subcorpus.size - 1))
match_boundaries = zip(
range(first_match, last_match + 1),
subcorpus.dump(subcorpus.fields['match'], first_match, last_match),
subcorpus.dump(subcorpus.fields['matchend'], first_match, last_match)
)
cpos_set = set()
matches = []
for match_num, match_start, match_end in match_boundaries:
c = (match_start, match_end)
if match_start == 0 or context == 0:
lc = None
cpos_list_lbound = match_start
else:
lc_lbound = max(0, (match_start - context))
lc_rbound = match_start - 1
lc = (lc_lbound, lc_rbound)
cpos_list_lbound = lc_lbound
if match_end == (subcorpus.collection.corpus.size - 1) or context == 0:
rc = None
cpos_list_rbound = match_end
else:
rc_lbound = match_end + 1
rc_rbound = min(
(match_end + context),
(subcorpus.collection.corpus.size - 1)
)
rc = (rc_lbound, rc_rbound)
cpos_list_rbound = rc_rbound
match = {'num': match_num, 'lc': lc, 'c': c, 'rc': rc}
matches.append(match)
cpos_set.update(range(cpos_list_lbound, cpos_list_rbound + 1))
lookups = lookups_by_cpos(subcorpus.collection.corpus, list(cpos_set))
return {'matches': matches, **lookups}

View File

@ -0,0 +1,115 @@
from flask import session
from flask_login import current_user
from flask_socketio import ConnectionRefusedError
from threading import Lock
import cqi
from app import db, hashids, socketio
from app.decorators import socketio_login_required
from app.models import Corpus, CorpusStatus
'''
This package tunnels the Corpus Query interface (CQi) protocol through
Socket.IO (SIO) by wrapping each CQi function in a seperate SIO event.
This module only handles the SIO connect/disconnect, which handles the setup
and teardown of necessary ressources for later use. Each CQi function has a
corresponding SIO event. The event handlers are spread across the different
modules within this package.
Basic concept:
1. A client connects to the SIO namespace and provides the id of a corpus to be
analysed.
1.1 The analysis session counter of the corpus is incremented.
1.2 A CQiClient and a (Mutex) Lock belonging to it is created.
1.3 Wait until the CQP server is running.
1.4 Connect the CQiClient to the server.
1.5 Save the CQiClient and the Lock in the session for subsequential use.
2. A client emits an event and may provide a single json object with necessary
arguments for the targeted CQi function.
3. A SIO event handler (decorated with cqi_over_socketio) gets executed.
- The event handler function defines all arguments. Hence the client
is sent as a single json object, the decorator decomposes it to fit
the functions signature. This also includes type checking and proper
use of the lock (acquire/release) mechanism.
4. Wait for more events
5. The client disconnects from the SIO namespace
1.1 The analysis session counter of the corpus is decremented.
1.2 The CQiClient and (Mutex) Lock belonging to it are teared down.
'''
NAMESPACE = '/corpora/corpus/corpus_analysis'
# Import all CQi over Socket.IO event handlers
from .cqi_corpora_corpus_subcorpora import * # noqa
from .cqi_corpora_corpus_structural_attributes import * # noqa
from .cqi_corpora_corpus_positional_attributes import * # noqa
from .cqi_corpora_corpus_alignment_attributes import * # noqa
from .cqi_corpora_corpus import * # noqa
from .cqi_corpora import * # noqa
from .cqi import * # noqa
@socketio.on('connect', namespace=NAMESPACE)
@socketio_login_required
def connect(auth):
# the auth variable is used in a hacky way. It contains the corpus id for
# which a corpus analysis session should be started.
corpus_id = hashids.decode(auth['corpus_id'])
corpus = Corpus.query.get(corpus_id)
if corpus is None:
# return {'code': 404, 'msg': 'Not Found'}
raise ConnectionRefusedError('Not Found')
if not (corpus.user == current_user
or current_user.is_following_corpus(corpus)
or current_user.is_administrator()):
# return {'code': 403, 'msg': 'Forbidden'}
raise ConnectionRefusedError('Forbidden')
if corpus.status not in [
CorpusStatus.BUILT,
CorpusStatus.STARTING_ANALYSIS_SESSION,
CorpusStatus.RUNNING_ANALYSIS_SESSION,
CorpusStatus.CANCELING_ANALYSIS_SESSION
]:
# return {'code': 424, 'msg': 'Failed Dependency'}
raise ConnectionRefusedError('Failed Dependency')
if corpus.num_analysis_sessions is None:
corpus.num_analysis_sessions = 0
db.session.commit()
corpus.num_analysis_sessions = Corpus.num_analysis_sessions + 1
db.session.commit()
retry_counter = 20
while corpus.status != CorpusStatus.RUNNING_ANALYSIS_SESSION:
if retry_counter == 0:
corpus.num_analysis_sessions = Corpus.num_analysis_sessions - 1
db.session.commit()
return {'code': 408, 'msg': 'Request Timeout'}
socketio.sleep(3)
retry_counter -= 1
db.session.refresh(corpus)
cqi_client = cqi.CQiClient(f'cqpserver_{corpus_id}')
session['d'] = {
'corpus_id': corpus_id,
'cqi_client': cqi_client,
'cqi_client_lock': Lock(),
}
# return {'code': 200, 'msg': 'OK'}
@socketio.on('disconnect', namespace=NAMESPACE)
def disconnect():
if 'd' not in session:
return
session['d']['cqi_client_lock'].acquire()
try:
session['d']['cqi_client'].disconnect()
except (BrokenPipeError, cqi.errors.CQiException):
pass
session['d']['cqi_client_lock'].release()
corpus = Corpus.query.get(session['d']['corpus_id'])
corpus.num_analysis_sessions = Corpus.num_analysis_sessions - 1
db.session.commit()
session.pop('d')
# return {'code': 200, 'msg': 'OK'}

View File

@ -0,0 +1,43 @@
from socket import gaierror
import cqi
from app import socketio
from app.decorators import socketio_login_required
from . import NAMESPACE as ns
from .utils import cqi_over_socketio
@socketio.on('cqi.connect', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_connect(cqi_client: cqi.CQiClient):
try:
cqi_status = cqi_client.connect()
except gaierror as e:
return {
'code': 500,
'msg': 'Internal Server Error',
'payload': {'code': e.args[0], 'desc': e.args[1]}
}
payload = {'code': cqi_status,
'msg': cqi.api.specification.lookup[cqi_status]}
return {'code': 200, 'msg': 'OK', 'payload': payload}
@socketio.on('cqi.disconnect', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_disconnect(cqi_client: cqi.CQiClient):
cqi_status = cqi_client.disconnect()
payload = {'code': cqi_status,
'msg': cqi.api.specification.lookup[cqi_status]}
return {'code': 200, 'msg': 'OK', 'payload': payload}
@socketio.on('cqi.ping', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_ping(cqi_client: cqi.CQiClient):
cqi_status = cqi_client.ping()
payload = {'code': cqi_status,
'msg': cqi.api.specification.lookup[cqi_status]}
return {'code': 200, 'msg': 'OK', 'payload': payload}

View File

@ -0,0 +1,22 @@
import cqi
from app import socketio
from app.decorators import socketio_login_required
from . import NAMESPACE as ns
from .utils import cqi_over_socketio
@socketio.on('cqi.corpora.get', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_corpora_get(cqi_client: cqi.CQiClient, corpus_name: str):
cqi_corpus = cqi_client.corpora.get(corpus_name)
payload = {**cqi_corpus.attrs}
return {'code': 200, 'msg': 'OK', 'payload': payload}
@socketio.on('cqi.corpora.list', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_corpora_list(cqi_client: cqi.CQiClient):
payload = [{**x.attrs} for x in cqi_client.corpora.list()]
return {'code': 200, 'msg': 'OK', 'payload': payload}

View File

@ -0,0 +1,85 @@
from flask import session
import cqi
import math
from app import db, socketio
from app.decorators import socketio_login_required
from app.models import Corpus
from . import NAMESPACE as ns
from .utils import cqi_over_socketio, lookups_by_cpos
@socketio.on('cqi.corpora.corpus.drop', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_corpora_corpus_drop(cqi_client: cqi.CQiClient, corpus_name: str):
cqi_corpus = cqi_client.corpora.get(corpus_name)
cqi_status = cqi_corpus.drop()
payload = {'code': cqi_status,
'msg': cqi.api.specification.lookup[cqi_status]}
return {'code': 200, 'msg': 'OK', 'payload': payload}
@socketio.on('cqi.corpora.corpus.query', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_corpora_corpus_query(cqi_client: cqi.CQiClient, corpus_name: str, subcorpus_name: str, query: str): # noqa
cqi_corpus = cqi_client.corpora.get(corpus_name)
cqi_status = cqi_corpus.query(subcorpus_name, query)
payload = {'code': cqi_status,
'msg': cqi.api.specification.lookup[cqi_status]}
return {'code': 200, 'msg': 'OK', 'payload': payload}
###############################################################################
# nopaque specific CQi extensions #
###############################################################################
@socketio.on('cqi.corpora.corpus.update_db', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_corpora_corpus_update_db(cqi_client: cqi.CQiClient, corpus_name: str):
corpus = Corpus.query.get(session['d']['corpus_id'])
corpus.num_tokens = cqi_client.corpora.get(corpus_name).attrs['size']
db.session.commit()
@socketio.on('cqi.corpora.corpus.paginate', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_corpora_corpus_paginate(cqi_client: cqi.CQiClient, corpus_name: str, page: int = 1, per_page: int = 20): # noqa
cqi_corpus = cqi_client.corpora.get(corpus_name)
# Sanity checks
if (
per_page < 1
or page < 1
or (
cqi_corpus.attrs['size'] > 0
and page > math.ceil(cqi_corpus.attrs['size'] / per_page)
)
):
return {'code': 416, 'msg': 'Range Not Satisfiable'}
first_cpos = (page - 1) * per_page
last_cpos = min(cqi_corpus.attrs['size'], first_cpos + per_page)
cpos_list = [*range(first_cpos, last_cpos)]
lookups = lookups_by_cpos(cqi_corpus, cpos_list)
payload = {}
# the items for the current page
payload['items'] = [cpos_list]
# the lookups for the items
payload['lookups'] = lookups
# the total number of items matching the query
payload['total'] = cqi_corpus.attrs['size']
# the number of items to be displayed on a page.
payload['per_page'] = per_page
# The total number of pages
payload['pages'] = math.ceil(payload['total'] / payload['per_page'])
# the current page number (1 indexed)
payload['page'] = page if payload['pages'] > 0 else None
# True if a previous page exists
payload['has_prev'] = payload['page'] > 1 if payload['page'] else False
# True if a next page exists.
payload['has_next'] = payload['page'] < payload['pages'] if payload['page'] else False # noqa
# Number of the previous page.
payload['prev_num'] = payload['page'] - 1 if payload['has_prev'] else None
# Number of the next page
payload['next_num'] = payload['page'] + 1 if payload['has_next'] else None
return {'code': 200, 'msg': 'OK', 'payload': payload}

View File

@ -0,0 +1,24 @@
import cqi
from app import socketio
from app.decorators import socketio_login_required
from . import NAMESPACE as ns
from .utils import cqi_over_socketio
@socketio.on('cqi.corpora.corpus.alignment_attributes.get', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_corpora_corpus_alignment_attributes_get(cqi_client: cqi.CQiClient, corpus_name: str, alignment_attribute_name: str): # noqa
cqi_corpus = cqi_client.corpora.get(corpus_name)
cqi_alignment_attribute = cqi_corpus.alignment_attributes.get(alignment_attribute_name) # noqa
payload = {**cqi_alignment_attribute.attrs}
return {'code': 200, 'msg': 'OK', 'payload': payload}
@socketio.on('cqi.corpora.corpus.alignment_attributes.list', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_corpora_corpus_alignment_attributes_list(cqi_client: cqi.CQiClient, corpus_name: str): # noqa
cqi_corpus = cqi_client.corpora.get(corpus_name)
payload = [{**x.attrs} for x in cqi_corpus.alignment_attributes.list()]
return {'code': 200, 'msg': 'OK', 'payload': payload}

View File

@ -0,0 +1,24 @@
import cqi
from app import socketio
from app.decorators import socketio_login_required
from . import NAMESPACE as ns
from .utils import cqi_over_socketio
@socketio.on('cqi.corpora.corpus.positional_attributes.get', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_corpora_corpus_positional_attributes_get(cqi_client: cqi.CQiClient, corpus_name: str, positional_attribute_name: str): # noqa
cqi_corpus = cqi_client.corpora.get(corpus_name)
cqi_positional_attribute = cqi_corpus.positional_attributes.get(positional_attribute_name) # noqa
payload = {**cqi_positional_attribute.attrs}
return {'code': 200, 'msg': 'OK', 'payload': payload}
@socketio.on('cqi.corpora.corpus.positional_attributes.list', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_corpora_corpus_positional_attributes_list(cqi_client: cqi.CQiClient, corpus_name: str): # noqa
cqi_corpus = cqi_client.corpora.get(corpus_name)
payload = [{**x.attrs} for x in cqi_corpus.positional_attributes.list()]
return {'code': 200, 'msg': 'OK', 'payload': payload}

View File

@ -0,0 +1,24 @@
import cqi
from app import socketio
from app.decorators import socketio_login_required
from . import NAMESPACE as ns
from .utils import cqi_over_socketio
@socketio.on('cqi.corpora.corpus.structural_attributes.get', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_corpora_corpus_structural_attributes_get(cqi_client: cqi.CQiClient, corpus_name: str, structural_attribute_name: str): # noqa
cqi_corpus = cqi_client.corpora.get(corpus_name)
cqi_structural_attribute = cqi_corpus.structural_attributes.get(structural_attribute_name) # noqa
payload = {**cqi_structural_attribute.attrs}
return {'code': 200, 'msg': 'OK', 'payload': payload}
@socketio.on('cqi.corpora.corpus.structural_attributes.list', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_corpora_corpus_structural_attributes_list(cqi_client: cqi.CQiClient, corpus_name: str): # noqa
cqi_corpus = cqi_client.corpora.get(corpus_name)
payload = [{**x.attrs} for x in cqi_corpus.structural_attributes.list()]
return {'code': 200, 'msg': 'OK', 'payload': payload}

View File

@ -0,0 +1,125 @@
import cqi
import math
from app import socketio
from app.decorators import socketio_login_required
from . import NAMESPACE as ns
from .utils import cqi_over_socketio, export_subcorpus, partial_export_subcorpus
@socketio.on('cqi.corpora.corpus.subcorpora.get', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_corpora_corpus_subcorpora_get(cqi_client: cqi.CQiClient, corpus_name: str, subcorpus_name: str): # noqa
cqi_corpus = cqi_client.corpora.get(corpus_name)
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
payload = {**cqi_subcorpus.attrs}
return {'code': 200, 'msg': 'OK', 'payload': payload}
@socketio.on('cqi.corpora.corpus.subcorpora.list', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_corpora_corpus_subcorpora_list(cqi_client: cqi.CQiClient, corpus_name: str): # noqa
cqi_corpus = cqi_client.corpora.get(corpus_name)
payload = [{**x.attrs} for x in cqi_corpus.subcorpora.list()]
return {'code': 200, 'msg': 'OK', 'payload': payload}
@socketio.on('cqi.corpora.corpus.subcorpora.subcorpus.drop', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_corpora_corpus_subcorpora_subcorpus_drop(cqi_client: cqi.CQiClient, corpus_name: str, subcorpus_name: str): # noqa
cqi_corpus = cqi_client.corpora.get(corpus_name)
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
cqi_status = cqi_subcorpus.drop()
payload = {'code': cqi_status,
'msg': cqi.api.specification.lookup[cqi_status]}
return {'code': 200, 'msg': 'OK', 'payload': payload}
@socketio.on('cqi.corpora.corpus.subcorpora.subcorpus.dump', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_corpora_corpus_subcorpora_subcorpus_dump(cqi_client: cqi.CQiClient):
return {'code': 501, 'msg': 'Not Implemented'}
@socketio.on('cqi.corpora.corpus.subcorpora.subcorpus.fdist_1', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_corpora_corpus_subcorpora_subcorpus_fdist_1(cqi_client: cqi.CQiClient):
return {'code': 501, 'msg': 'Not Implemented'}
@socketio.on('cqi.corpora.corpus.subcorpora.subcorpus.fdist_2', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_corpora_corpus_subcorpora_subcorpus_fdist_2(cqi_client: cqi.CQiClient):
return {'code': 501, 'msg': 'Not Implemented'}
###############################################################################
# nopaque specific CQi extensions #
###############################################################################
@socketio.on('cqi.corpora.corpus.subcorpora.subcorpus.paginate', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_corpora_corpus_subcorpora_subcorpus_paginate(cqi_client: cqi.CQiClient, corpus_name: str, subcorpus_name: str, context: int = 50, page: int = 1, per_page: int = 20): # noqa
cqi_corpus = cqi_client.corpora.get(corpus_name)
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
# Sanity checks
if (
per_page < 1
or page < 1
or (
cqi_subcorpus.attrs['size'] > 0
and page > math.ceil(cqi_subcorpus.attrs['size'] / per_page)
)
):
return {'code': 416, 'msg': 'Range Not Satisfiable'}
offset = (page - 1) * per_page
cutoff = per_page
cqi_results_export = export_subcorpus(
cqi_subcorpus, context=context, cutoff=cutoff, offset=offset)
payload = {}
# the items for the current page
payload['items'] = cqi_results_export.pop('matches')
# the lookups for the items
payload['lookups'] = cqi_results_export
# the total number of items matching the query
payload['total'] = cqi_subcorpus.attrs['size']
# the number of items to be displayed on a page.
payload['per_page'] = per_page
# The total number of pages
payload['pages'] = math.ceil(payload['total'] / payload['per_page'])
# the current page number (1 indexed)
payload['page'] = page if payload['pages'] > 0 else None
# True if a previous page exists
payload['has_prev'] = payload['page'] > 1 if payload['page'] else False
# True if a next page exists.
payload['has_next'] = payload['page'] < payload['pages'] if payload['page'] else False # noqa
# Number of the previous page.
payload['prev_num'] = payload['page'] - 1 if payload['has_prev'] else None
# Number of the next page
payload['next_num'] = payload['page'] + 1 if payload['has_next'] else None
return {'code': 200, 'msg': 'OK', 'payload': payload}
@socketio.on('cqi.corpora.corpus.subcorpora.subcorpus.partial_export', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_corpora_corpus_subcorpora_subcorpus_partial_export(cqi_client: cqi.CQiClient, corpus_name: str, subcorpus_name: str, match_id_list: list, context: int = 50): # noqa
cqi_corpus = cqi_client.corpora.get(corpus_name)
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
cqi_subcorpus_partial_export = partial_export_subcorpus(cqi_subcorpus, match_id_list, context=context)
return {'code': 200, 'msg': 'OK', 'payload': cqi_subcorpus_partial_export}
@socketio.on('cqi.corpora.corpus.subcorpora.subcorpus.export', namespace=ns)
@socketio_login_required
@cqi_over_socketio
def cqi_corpora_corpus_subcorpora_subcorpus_export(cqi_client: cqi.CQiClient, corpus_name: str, subcorpus_name: str, context: int = 50): # noqa
cqi_corpus = cqi_client.corpora.get(corpus_name)
cqi_subcorpus = cqi_corpus.subcorpora.get(subcorpus_name)
cqi_subcorpus_export = export_subcorpus(cqi_subcorpus, context=context)
return {'code': 200, 'msg': 'OK', 'payload': cqi_subcorpus_export}

View File

@ -0,0 +1,178 @@
from flask import session
from functools import wraps
from inspect import signature
import cqi
def cqi_over_socketio(f):
@wraps(f)
def wrapped(*args):
if 'd' not in session:
return {'code': 424, 'msg': 'Failed Dependency'}
f_args = {}
# Check for missing args and if all provided args are of the right type
for param in signature(f).parameters.values():
if param.name == 'corpus_name':
f_args[param.name] = f'NOPAQUE_{session["d"]["corpus_id"]}'
continue
if param.name == 'cqi_client':
f_args[param.name] = session['d']['cqi_client']
continue
if param.default is param.empty:
# args
if param.name not in args[0]:
return {'code': 400, 'msg': 'Bad Request'}
arg = args[0][param.name]
if type(arg) is not param.annotation:
return {'code': 400, 'msg': 'Bad Request'}
f_args[param.name] = arg
else:
# kwargs
if param.name not in args[0]:
continue
arg = args[0][param.name]
if type(arg) is not param.annotation:
return {'code': 400, 'msg': 'Bad Request'}
f_args[param.name] = arg
session['d']['cqi_client_lock'].acquire()
try:
return_value = f(**f_args)
except BrokenPipeError:
return_value = {
'code': 500,
'msg': 'Internal Server Error'
}
except cqi.errors.CQiException as e:
return_value = {
'code': 500,
'msg': 'Internal Server Error',
'payload': {
'code': e.code,
'desc': e.description,
'msg': e.name
}
}
finally:
session['d']['cqi_client_lock'].release()
return return_value
return wrapped
def lookups_by_cpos(corpus, cpos_list):
lookups = {}
lookups['cpos_lookup'] = {cpos: {} for cpos in cpos_list}
for attr in corpus.positional_attributes.list():
cpos_attr_values = attr.values_by_cpos(cpos_list)
for i, cpos in enumerate(cpos_list):
lookups['cpos_lookup'][cpos][attr.attrs['name']] = \
cpos_attr_values[i]
for attr in corpus.structural_attributes.list():
# We only want to iterate over non subattributes, identifiable by
# attr.attrs['has_values'] == False
if attr.attrs['has_values']:
continue
cpos_attr_ids = attr.ids_by_cpos(cpos_list)
for i, cpos in enumerate(cpos_list):
if cpos_attr_ids[i] == -1:
continue
lookups['cpos_lookup'][cpos][attr.attrs['name']] = cpos_attr_ids[i]
occured_attr_ids = [x for x in set(cpos_attr_ids) if x != -1]
if not occured_attr_ids:
continue
subattrs = corpus.structural_attributes.list(filters={'part_of': attr})
if not subattrs:
continue
lookup_name = f'{attr.attrs["name"]}_lookup'
lookups[lookup_name] = {}
for attr_id in occured_attr_ids:
lookups[lookup_name][attr_id] = {}
for subattr in subattrs:
subattr_name = subattr.attrs['name'][(len(attr.attrs['name']) + 1):] # noqa
for i, subattr_value in enumerate(subattr.values_by_ids(occured_attr_ids)): # noqa
lookups[lookup_name][occured_attr_ids[i]][subattr_name] = subattr_value # noqa
return lookups
def partial_export_subcorpus(subcorpus, match_id_list, context=25):
if subcorpus.attrs['size'] == 0:
return {"matches": []}
match_boundaries = []
for match_id in match_id_list:
if match_id < 0 or match_id >= subcorpus.attrs['size']:
continue
match_boundaries.append(
(
match_id,
subcorpus.dump(subcorpus.attrs['fields']['match'], match_id, match_id)[0],
subcorpus.dump(subcorpus.attrs['fields']['matchend'], match_id, match_id)[0]
)
)
cpos_set = set()
matches = []
for match_boundary in match_boundaries:
match_num, match_start, match_end = match_boundary
c = (match_start, match_end)
if match_start == 0 or context == 0:
lc = None
cpos_list_lbound = match_start
else:
lc_lbound = max(0, (match_start - context))
lc_rbound = match_start - 1
lc = (lc_lbound, lc_rbound)
cpos_list_lbound = lc_lbound
if match_end == (subcorpus.collection.corpus.attrs['size'] - 1) or context == 0:
rc = None
cpos_list_rbound = match_end
else:
rc_lbound = match_end + 1
rc_rbound = min(
(match_end + context),
(subcorpus.collection.corpus.attrs['size'] - 1)
)
rc = (rc_lbound, rc_rbound)
cpos_list_rbound = rc_rbound
match = {'num': match_num, 'lc': lc, 'c': c, 'rc': rc}
matches.append(match)
cpos_set.update(range(cpos_list_lbound, cpos_list_rbound + 1))
lookups = lookups_by_cpos(subcorpus.collection.corpus, list(cpos_set))
return {'matches': matches, **lookups}
def export_subcorpus(subcorpus, context=25, cutoff=float('inf'), offset=0):
if subcorpus.attrs['size'] == 0:
return {"matches": []}
first_match = max(0, offset)
last_match = min((offset + cutoff - 1), (subcorpus.attrs['size'] - 1))
match_boundaries = zip(
list(range(first_match, last_match + 1)),
subcorpus.dump(subcorpus.attrs['fields']['match'], first_match, last_match),
subcorpus.dump(subcorpus.attrs['fields']['matchend'], first_match, last_match)
)
cpos_set = set()
matches = []
for match_num, match_start, match_end in match_boundaries:
c = (match_start, match_end)
if match_start == 0 or context == 0:
lc = None
cpos_list_lbound = match_start
else:
lc_lbound = max(0, (match_start - context))
lc_rbound = match_start - 1
lc = (lc_lbound, lc_rbound)
cpos_list_lbound = lc_lbound
if match_end == (subcorpus.collection.corpus.attrs['size'] - 1) or context == 0:
rc = None
cpos_list_rbound = match_end
else:
rc_lbound = match_end + 1
rc_rbound = min(
(match_end + context),
(subcorpus.collection.corpus.attrs['size'] - 1)
)
rc = (rc_lbound, rc_rbound)
cpos_list_rbound = rc_rbound
match = {'num': match_num, 'lc': lc, 'c': c, 'rc': rc}
matches.append(match)
cpos_set.update(range(cpos_list_lbound, cpos_list_rbound + 1))
lookups = lookups_by_cpos(subcorpus.collection.corpus, list(cpos_set))
return {'matches': matches, **lookups}

View File

@ -12,65 +12,65 @@ from ..decorators import corpus_follower_permission_required
from . import bp from . import bp
@bp.route('/<hashid:corpus_id>/followers', methods=['POST']) # @bp.route('/<hashid:corpus_id>/followers', methods=['POST'])
@corpus_follower_permission_required('MANAGE_FOLLOWERS') # @corpus_follower_permission_required('MANAGE_FOLLOWERS')
@content_negotiation(consumes='application/json', produces='application/json') # @content_negotiation(consumes='application/json', produces='application/json')
def create_corpus_followers(corpus_id): # def create_corpus_followers(corpus_id):
usernames = request.json # usernames = request.json
if not (isinstance(usernames, list) or all(isinstance(u, str) for u in usernames)): # if not (isinstance(usernames, list) or all(isinstance(u, str) for u in usernames)):
abort(400) # abort(400)
corpus = Corpus.query.get_or_404(corpus_id) # corpus = Corpus.query.get_or_404(corpus_id)
for username in usernames: # for username in usernames:
user = User.query.filter_by(username=username, is_public=True).first_or_404() # user = User.query.filter_by(username=username, is_public=True).first_or_404()
user.follow_corpus(corpus) # user.follow_corpus(corpus)
db.session.commit() # db.session.commit()
response_data = { # response_data = {
'message': f'Users are now following "{corpus.title}"', # 'message': f'Users are now following "{corpus.title}"',
'category': 'corpus' # 'category': 'corpus'
} # }
return response_data, 200 # return response_data, 200
@bp.route('/<hashid:corpus_id>/followers/<hashid:follower_id>/role', methods=['PUT']) # @bp.route('/<hashid:corpus_id>/followers/<hashid:follower_id>/role', methods=['PUT'])
@corpus_follower_permission_required('MANAGE_FOLLOWERS') # @corpus_follower_permission_required('MANAGE_FOLLOWERS')
@content_negotiation(consumes='application/json', produces='application/json') # @content_negotiation(consumes='application/json', produces='application/json')
def update_corpus_follower_role(corpus_id, follower_id): # def update_corpus_follower_role(corpus_id, follower_id):
role_name = request.json # role_name = request.json
if not isinstance(role_name, str): # if not isinstance(role_name, str):
abort(400) # abort(400)
cfr = CorpusFollowerRole.query.filter_by(name=role_name).first() # cfr = CorpusFollowerRole.query.filter_by(name=role_name).first()
if cfr is None: # if cfr is None:
abort(400) # abort(400)
cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=follower_id).first_or_404() # cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=follower_id).first_or_404()
cfa.role = cfr # cfa.role = cfr
db.session.commit() # db.session.commit()
response_data = { # response_data = {
'message': f'User "{cfa.follower.username}" is now {cfa.role.name}', # 'message': f'User "{cfa.follower.username}" is now {cfa.role.name}',
'category': 'corpus' # 'category': 'corpus'
} # }
return response_data, 200 # return response_data, 200
@bp.route('/<hashid:corpus_id>/followers/<hashid:follower_id>', methods=['DELETE']) # @bp.route('/<hashid:corpus_id>/followers/<hashid:follower_id>', methods=['DELETE'])
def delete_corpus_follower(corpus_id, follower_id): # def delete_corpus_follower(corpus_id, follower_id):
cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=follower_id).first_or_404() # cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=follower_id).first_or_404()
if not ( # if not (
current_user.id == follower_id # current_user.id == follower_id
or current_user == cfa.corpus.user # or current_user == cfa.corpus.user
or CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=current_user.id).first().role.has_permission('MANAGE_FOLLOWERS') # or CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=current_user.id).first().role.has_permission('MANAGE_FOLLOWERS')
or current_user.is_administrator()): # or current_user.is_administrator()):
abort(403) # abort(403)
if current_user.id == follower_id: # if current_user.id == follower_id:
flash(f'You are no longer following "{cfa.corpus.title}"', 'corpus') # flash(f'You are no longer following "{cfa.corpus.title}"', 'corpus')
response = make_response() # response = make_response()
response.status_code = 204 # response.status_code = 204
else: # else:
response_data = { # response_data = {
'message': f'"{cfa.follower.username}" is not following "{cfa.corpus.title}" anymore', # 'message': f'"{cfa.follower.username}" is not following "{cfa.corpus.title}" anymore',
'category': 'corpus' # 'category': 'corpus'
} # }
response = jsonify(response_data) # response = jsonify(response_data)
response.status_code = 200 # response.status_code = 200
cfa.follower.unfollow_corpus(cfa.corpus) # cfa.follower.unfollow_corpus(cfa.corpus)
db.session.commit() # db.session.commit()
return response # return response

View File

@ -7,8 +7,6 @@ from app.decorators import content_negotiation
from app.models import Corpus, CorpusFollowerRole from app.models import Corpus, CorpusFollowerRole
from . import bp from . import bp
from .decorators import corpus_follower_permission_required, corpus_owner_or_admin_required from .decorators import corpus_follower_permission_required, corpus_owner_or_admin_required
import nltk
from string import punctuation
@bp.route('/<hashid:corpus_id>', methods=['DELETE']) @bp.route('/<hashid:corpus_id>', methods=['DELETE'])
@ -58,68 +56,56 @@ def build_corpus(corpus_id):
} }
return response_data, 202 return response_data, 202
@bp.route('/stopwords')
@content_negotiation(produces='application/json')
def get_stopwords():
nltk.download('stopwords', quiet=True)
languages = ["german", "english", "catalan", "greek", "spanish", "french", "italian", "russian", "chinese"]
stopwords = {}
for language in languages:
stopwords[language] = nltk.corpus.stopwords.words(language)
stopwords['punctuation'] = list(punctuation) + ['', '|', '', '', '', '--']
stopwords['user_stopwords'] = []
response_data = stopwords
return response_data, 202
@bp.route('/<hashid:corpus_id>/generate-share-link', methods=['POST']) # @bp.route('/<hashid:corpus_id>/generate-share-link', methods=['POST'])
@corpus_follower_permission_required('MANAGE_FOLLOWERS') # @corpus_follower_permission_required('MANAGE_FOLLOWERS')
@content_negotiation(consumes='application/json', produces='application/json') # @content_negotiation(consumes='application/json', produces='application/json')
def generate_corpus_share_link(corpus_id): # def generate_corpus_share_link(corpus_id):
data = request.json # data = request.json
if not isinstance(data, dict): # if not isinstance(data, dict):
abort(400) # abort(400)
expiration = data.get('expiration') # expiration = data.get('expiration')
if not isinstance(expiration, str): # if not isinstance(expiration, str):
abort(400) # abort(400)
role_name = data.get('role') # role_name = data.get('role')
if not isinstance(role_name, str): # if not isinstance(role_name, str):
abort(400) # abort(400)
expiration_date = datetime.strptime(expiration, '%b %d, %Y') # expiration_date = datetime.strptime(expiration, '%b %d, %Y')
cfr = CorpusFollowerRole.query.filter_by(name=role_name).first() # cfr = CorpusFollowerRole.query.filter_by(name=role_name).first()
if cfr is None: # if cfr is None:
abort(400) # abort(400)
corpus = Corpus.query.get_or_404(corpus_id) # corpus = Corpus.query.get_or_404(corpus_id)
token = current_user.generate_follow_corpus_token(corpus.hashid, role_name, expiration_date) # token = current_user.generate_follow_corpus_token(corpus.hashid, role_name, expiration_date)
corpus_share_link = url_for( # corpus_share_link = url_for(
'corpora.follow_corpus', # 'corpora.follow_corpus',
corpus_id=corpus_id, # corpus_id=corpus_id,
token=token, # token=token,
_external=True # _external=True
) # )
response_data = { # response_data = {
'message': 'Corpus share link generated', # 'message': 'Corpus share link generated',
'category': 'corpus', # 'category': 'corpus',
'corpusShareLink': corpus_share_link # 'corpusShareLink': corpus_share_link
} # }
return response_data, 200 # return response_data, 200
@bp.route('/<hashid:corpus_id>/is_public', methods=['PUT']) # @bp.route('/<hashid:corpus_id>/is_public', methods=['PUT'])
@corpus_owner_or_admin_required # @corpus_owner_or_admin_required
@content_negotiation(consumes='application/json', produces='application/json') # @content_negotiation(consumes='application/json', produces='application/json')
def update_corpus_is_public(corpus_id): # def update_corpus_is_public(corpus_id):
is_public = request.json # is_public = request.json
if not isinstance(is_public, bool): # if not isinstance(is_public, bool):
abort(400) # abort(400)
corpus = Corpus.query.get_or_404(corpus_id) # corpus = Corpus.query.get_or_404(corpus_id)
corpus.is_public = is_public # corpus.is_public = is_public
db.session.commit() # db.session.commit()
response_data = { # response_data = {
'message': ( # 'message': (
f'Corpus "{corpus.title}" is now' # f'Corpus "{corpus.title}" is now'
f' {"public" if is_public else "private"}' # f' {"public" if is_public else "private"}'
), # ),
'category': 'corpus' # 'category': 'corpus'
} # }
return response_data, 200 # return response_data, 200

View File

@ -68,19 +68,20 @@ def corpus(corpus_id):
corpus=corpus, corpus=corpus,
cfr=cfr, cfr=cfr,
cfrs=cfrs, cfrs=cfrs,
users=users users = users
) )
if (current_user.is_following_corpus(corpus) or corpus.is_public): if (current_user.is_following_corpus(corpus) or corpus.is_public):
cfas = CorpusFollowerAssociation.query.filter(Corpus.id == corpus_id, CorpusFollowerAssociation.follower_id != corpus.user.id).all() abort(404)
return render_template( # cfas = CorpusFollowerAssociation.query.filter(Corpus.id == corpus_id, CorpusFollowerAssociation.follower_id != corpus.user.id).all()
'corpora/public_corpus.html.j2', # return render_template(
title=corpus.title, # 'corpora/public_corpus.html.j2',
corpus=corpus, # title=corpus.title,
cfrs=cfrs, # corpus=corpus,
cfr=cfr, # cfrs=cfrs,
cfas=cfas, # cfr=cfr,
users=users # cfas=cfas,
) # users = users
# )
abort(403) abort(403)

View File

@ -28,24 +28,24 @@ def _create_build_corpus_service(corpus):
''' ## Command ## ''' ''' ## Command ## '''
command = ['bash', '-c'] command = ['bash', '-c']
command.append( command.append(
f'mkdir /corpora/data/nopaque-{corpus.hashid.lower()}' f'mkdir /corpora/data/nopaque_{corpus.id}'
' && ' ' && '
'cwb-encode' 'cwb-encode'
' -c utf8' ' -c utf8'
f' -d /corpora/data/nopaque-{corpus.hashid.lower()}' f' -d /corpora/data/nopaque_{corpus.id}'
' -f /root/files/corpus.vrt' ' -f /root/files/corpus.vrt'
f' -R /usr/local/share/cwb/registry/nopaque-{corpus.hashid.lower()}' f' -R /usr/local/share/cwb/registry/nopaque_{corpus.id}'
' -P pos -P lemma -P simple_pos' ' -P pos -P lemma -P simple_pos'
' -S ent:0+type -S s:0' ' -S ent:0+type -S s:0'
' -S text:0+address+author+booktitle+chapter+editor+institution+journal+pages+publisher+publishing_year+school+title' ' -S text:0+address+author+booktitle+chapter+editor+institution+journal+pages+publisher+publishing_year+school+title'
' -xsB -9' ' -xsB -9'
' && ' ' && '
f'cwb-make -V NOPAQUE-{corpus.hashid.upper()}' f'cwb-make -V NOPAQUE_{corpus.id}'
) )
''' ## Constraints ## ''' ''' ## Constraints ## '''
constraints = ['node.role==worker'] constraints = ['node.role==worker']
''' ## Image ## ''' ''' ## Image ## '''
image = f'{current_app.config["NOPAQUE_DOCKER_IMAGE_PREFIX"]}cwb:r1879' image = f'{current_app.config["NOPAQUE_DOCKER_IMAGE_PREFIX"]}cwb:r1702'
''' ## Labels ## ''' ''' ## Labels ## '''
labels = { labels = {
'origin': current_app.config['SERVER_NAME'], 'origin': current_app.config['SERVER_NAME'],
@ -139,25 +139,21 @@ def _create_cqpserver_container(corpus):
''' ## Entrypoint ## ''' ''' ## Entrypoint ## '''
entrypoint = ['bash', '-c'] entrypoint = ['bash', '-c']
''' ## Image ## ''' ''' ## Image ## '''
image = f'{current_app.config["NOPAQUE_DOCKER_IMAGE_PREFIX"]}cwb:r1879' image = f'{current_app.config["NOPAQUE_DOCKER_IMAGE_PREFIX"]}cwb:r1702'
''' ## Name ## ''' ''' ## Name ## '''
name = f'cqpserver_{corpus.id}' name = f'cqpserver_{corpus.id}'
''' ## Network ## ''' ''' ## Network ## '''
network = f'{current_app.config["NOPAQUE_DOCKER_NETWORK_NAME"]}' network = f'{current_app.config["DOCKER_NETWORK_NAME"]}'
''' ## Volumes ## ''' ''' ## Volumes ## '''
volumes = [] volumes = []
''' ### Corpus data volume ### ''' ''' ### Corpus data volume ### '''
data_volume_source = os.path.join(corpus.path, 'cwb', 'data') data_volume_source = os.path.join(corpus.path, 'cwb', 'data')
data_volume_target = '/corpora/data' data_volume_target = '/corpora/data'
# data_volume_source = os.path.join(corpus.path, 'cwb', 'data', f'nopaque_{corpus.id}')
# data_volume_target = f'/corpora/data/nopaque_{corpus.hashid.lower()}'
data_volume = f'{data_volume_source}:{data_volume_target}:rw' data_volume = f'{data_volume_source}:{data_volume_target}:rw'
volumes.append(data_volume) volumes.append(data_volume)
''' ### Corpus registry volume ### ''' ''' ### Corpus registry volume ### '''
registry_volume_source = os.path.join(corpus.path, 'cwb', 'registry') registry_volume_source = os.path.join(corpus.path, 'cwb', 'registry')
registry_volume_target = '/usr/local/share/cwb/registry' registry_volume_target = '/usr/local/share/cwb/registry'
# registry_volume_source = os.path.join(corpus.path, 'cwb', 'registry', f'nopaque_{corpus.id}')
# registry_volume_target = f'/usr/local/share/cwb/registry/nopaque_{corpus.hashid.lower()}'
registry_volume = f'{registry_volume_source}:{registry_volume_target}:rw' registry_volume = f'{registry_volume_source}:{registry_volume_target}:rw'
volumes.append(registry_volume) volumes.append(registry_volume)
# Check if a cqpserver container already exists. If this is the case, # Check if a cqpserver container already exists. If this is the case,

View File

@ -42,6 +42,7 @@ def job_log(job_id):
with open(os.path.join(job.path, 'pipeline_data', 'logs', 'pyflow_log.txt')) as log_file: with open(os.path.join(job.path, 'pipeline_data', 'logs', 'pyflow_log.txt')) as log_file:
log = log_file.read() log = log_file.read()
response_data = { response_data = {
'message': '',
'jobLog': log 'jobLog': log
} }
return response_data, 200 return response_data, 200

View File

@ -43,5 +43,3 @@ def deploy():
SpaCyNLPPipelineModel.insert_defaults() SpaCyNLPPipelineModel.insert_defaults()
print('Insert/Update default TesseractOCRPipelineModels') print('Insert/Update default TesseractOCRPipelineModels')
TesseractOCRPipelineModel.insert_defaults() TesseractOCRPipelineModel.insert_defaults()
# TODO: Implement checks for if the nopaque network exists

View File

@ -45,6 +45,12 @@ def dashboard():
) )
# @bp.route('/user_manual')
# @register_breadcrumb(bp, '.user_manual', '<i class="material-icons left">help</i>User manual')
# def user_manual():
# return render_template('main/user_manual.html.j2', title='User manual')
@bp.route('/news') @bp.route('/news')
@register_breadcrumb(bp, '.news', '<i class="material-icons left">email</i>News') @register_breadcrumb(bp, '.news', '<i class="material-icons left">email</i>News')
def news(): def news():
@ -72,17 +78,15 @@ def terms_of_use():
) )
@bp.route('/social-area') # @bp.route('/social-area')
@register_breadcrumb(bp, '.social_area', '<i class="material-icons left">group</i>Social Area') # @register_breadcrumb(bp, '.social_area', '<i class="material-icons left">group</i>Social Area')
@login_required # @login_required
def social_area(): # def social_area():
print('test') # corpora = Corpus.query.filter(Corpus.is_public == True, Corpus.user != current_user).all()
corpora = Corpus.query.filter(Corpus.is_public == True, Corpus.user != current_user).all() # users = User.query.filter(User.is_public == True, User.id != current_user.id).all()
print(corpora) # return render_template(
users = User.query.filter(User.is_public == True, User.id != current_user.id).all() # 'main/social_area.html.j2',
return render_template( # title='Social Area',
'main/social_area.html.j2', # corpora=corpora,
title='Social Area', # users=users
corpora=corpora, # )
users=users
)

View File

@ -853,7 +853,7 @@ class User(HashidMixin, UserMixin, db.Model):
json_serializeable = { json_serializeable = {
'id': self.hashid, 'id': self.hashid,
'confirmed': self.confirmed, 'confirmed': self.confirmed,
'avatar': url_for('users.user_avatar', user_id=self.id), # 'avatar': url_for('users.user_avatar', user_id=self.id),
'email': self.email, 'email': self.email,
'last_seen': ( 'last_seen': (
None if self.last_seen is None None if self.last_seen is None
@ -953,7 +953,7 @@ class TesseractOCRPipelineModel(FileMixin, HashidMixin, db.Model):
return self.user.hashid return self.user.hashid
@staticmethod @staticmethod
def insert_defaults(force_download=False): def insert_defaults():
nopaque_user = User.query.filter_by(username='nopaque').first() nopaque_user = User.query.filter_by(username='nopaque').first()
defaults_file = os.path.join( defaults_file = os.path.join(
os.path.dirname(os.path.abspath(__file__)), os.path.dirname(os.path.abspath(__file__)),
@ -966,7 +966,6 @@ class TesseractOCRPipelineModel(FileMixin, HashidMixin, db.Model):
if model is not None: if model is not None:
model.compatible_service_versions = m['compatible_service_versions'] model.compatible_service_versions = m['compatible_service_versions']
model.description = m['description'] model.description = m['description']
model.filename = f'{model.id}.traineddata'
model.publisher = m['publisher'] model.publisher = m['publisher']
model.publisher_url = m['publisher_url'] model.publisher_url = m['publisher_url']
model.publishing_url = m['publishing_url'] model.publishing_url = m['publishing_url']
@ -974,39 +973,38 @@ class TesseractOCRPipelineModel(FileMixin, HashidMixin, db.Model):
model.is_public = True model.is_public = True
model.title = m['title'] model.title = m['title']
model.version = m['version'] model.version = m['version']
else: continue
model = TesseractOCRPipelineModel( model = TesseractOCRPipelineModel(
compatible_service_versions=m['compatible_service_versions'], compatible_service_versions=m['compatible_service_versions'],
description=m['description'], description=m['description'],
publisher=m['publisher'], publisher=m['publisher'],
publisher_url=m['publisher_url'], publisher_url=m['publisher_url'],
publishing_url=m['publishing_url'], publishing_url=m['publishing_url'],
publishing_year=m['publishing_year'], publishing_year=m['publishing_year'],
is_public=True, is_public=True,
title=m['title'], title=m['title'],
user=nopaque_user, user=nopaque_user,
version=m['version'] version=m['version']
) )
db.session.add(model) db.session.add(model)
db.session.flush(objects=[model]) db.session.flush(objects=[model])
db.session.refresh(model) db.session.refresh(model)
model.filename = f'{model.id}.traineddata' model.filename = f'{model.id}.traineddata'
if not os.path.exists(model.path) or force_download: r = requests.get(m['url'], stream=True)
r = requests.get(m['url'], stream=True) pbar = tqdm(
pbar = tqdm( desc=f'{model.title} ({model.filename})',
desc=f'{model.title} ({model.filename})', unit="B",
unit="B", unit_scale=True,
unit_scale=True, unit_divisor=1024,
unit_divisor=1024, total=int(r.headers['Content-Length'])
total=int(r.headers['Content-Length']) )
) pbar.clear()
pbar.clear() with open(model.path, 'wb') as f:
with open(model.path, 'wb') as f: for chunk in r.iter_content(chunk_size=1024):
for chunk in r.iter_content(chunk_size=1024): if chunk: # filter out keep-alive new chunks
if chunk: # filter out keep-alive new chunks pbar.update(len(chunk))
pbar.update(len(chunk)) f.write(chunk)
f.write(chunk) pbar.close()
pbar.close()
db.session.commit() db.session.commit()
def delete(self): def delete(self):
@ -1082,7 +1080,7 @@ class SpaCyNLPPipelineModel(FileMixin, HashidMixin, db.Model):
return self.user.hashid return self.user.hashid
@staticmethod @staticmethod
def insert_defaults(force_download=False): def insert_defaults():
nopaque_user = User.query.filter_by(username='nopaque').first() nopaque_user = User.query.filter_by(username='nopaque').first()
defaults_file = os.path.join( defaults_file = os.path.join(
os.path.dirname(os.path.abspath(__file__)), os.path.dirname(os.path.abspath(__file__)),
@ -1095,7 +1093,6 @@ class SpaCyNLPPipelineModel(FileMixin, HashidMixin, db.Model):
if model is not None: if model is not None:
model.compatible_service_versions = m['compatible_service_versions'] model.compatible_service_versions = m['compatible_service_versions']
model.description = m['description'] model.description = m['description']
model.filename = m['url'].split('/')[-1]
model.publisher = m['publisher'] model.publisher = m['publisher']
model.publisher_url = m['publisher_url'] model.publisher_url = m['publisher_url']
model.publishing_url = m['publishing_url'] model.publishing_url = m['publishing_url']
@ -1104,40 +1101,39 @@ class SpaCyNLPPipelineModel(FileMixin, HashidMixin, db.Model):
model.title = m['title'] model.title = m['title']
model.version = m['version'] model.version = m['version']
model.pipeline_name = m['pipeline_name'] model.pipeline_name = m['pipeline_name']
else: continue
model = SpaCyNLPPipelineModel( model = SpaCyNLPPipelineModel(
compatible_service_versions=m['compatible_service_versions'], compatible_service_versions=m['compatible_service_versions'],
description=m['description'], description=m['description'],
filename=m['url'].split('/')[-1], publisher=m['publisher'],
publisher=m['publisher'], publisher_url=m['publisher_url'],
publisher_url=m['publisher_url'], publishing_url=m['publishing_url'],
publishing_url=m['publishing_url'], publishing_year=m['publishing_year'],
publishing_year=m['publishing_year'], is_public=True,
is_public=True, title=m['title'],
title=m['title'], user=nopaque_user,
user=nopaque_user, version=m['version'],
version=m['version'], pipeline_name=m['pipeline_name']
pipeline_name=m['pipeline_name'] )
) db.session.add(model)
db.session.add(model) db.session.flush(objects=[model])
db.session.flush(objects=[model]) db.session.refresh(model)
db.session.refresh(model) model.filename = m['url'].split('/')[-1]
if not os.path.exists(model.path) or force_download: r = requests.get(m['url'], stream=True)
r = requests.get(m['url'], stream=True) pbar = tqdm(
pbar = tqdm( desc=f'{model.title} ({model.filename})',
desc=f'{model.title} ({model.filename})', unit="B",
unit="B", unit_scale=True,
unit_scale=True, unit_divisor=1024,
unit_divisor=1024, total=int(r.headers['Content-Length'])
total=int(r.headers['Content-Length']) )
) pbar.clear()
pbar.clear() with open(model.path, 'wb') as f:
with open(model.path, 'wb') as f: for chunk in r.iter_content(chunk_size=1024):
for chunk in r.iter_content(chunk_size=1024): if chunk: # filter out keep-alive new chunks
if chunk: # filter out keep-alive new chunks pbar.update(len(chunk))
pbar.update(len(chunk)) f.write(chunk)
f.write(chunk) pbar.close()
pbar.close()
db.session.commit() db.session.commit()
def delete(self): def delete(self):
@ -1611,14 +1607,9 @@ class Corpus(HashidMixin, db.Model):
return corpus return corpus
def build(self): def build(self):
build_dir = os.path.join(self.path, 'cwb')
shutil.rmtree(build_dir, ignore_errors=True)
os.mkdir(build_dir)
os.mkdir(os.path.join(build_dir, 'data'))
os.mkdir(os.path.join(build_dir, 'registry'))
corpus_element = ET.fromstring('<corpus>\n</corpus>') corpus_element = ET.fromstring('<corpus>\n</corpus>')
for corpus_file in self.files: for corpus_file in self.files:
normalized_vrt_path = os.path.join(build_dir, f'{corpus_file.id}.norm.vrt') normalized_vrt_path = os.path.join(self.path, 'cwb', f'{corpus_file.id}.norm.vrt')
try: try:
normalize_vrt_file(corpus_file.path, normalized_vrt_path) normalize_vrt_file(corpus_file.path, normalized_vrt_path)
except: except:
@ -1645,7 +1636,7 @@ class Corpus(HashidMixin, db.Model):
# corpus_element.insert(1, text_element) # corpus_element.insert(1, text_element)
corpus_element.append(text_element) corpus_element.append(text_element)
ET.ElementTree(corpus_element).write( ET.ElementTree(corpus_element).write(
os.path.join(build_dir, 'corpus.vrt'), os.path.join(self.path, 'cwb', 'corpus.vrt'),
encoding='utf-8' encoding='utf-8'
) )
self.status = CorpusStatus.SUBMITTED self.status = CorpusStatus.SUBMITTED

View File

@ -10,7 +10,7 @@ file-setup-pipeline:
tesseract-ocr-pipeline: tesseract-ocr-pipeline:
name: 'Tesseract OCR Pipeline' name: 'Tesseract OCR Pipeline'
publisher: 'Bielefeld University - CRC 1288 - INF' publisher: 'Bielefeld University - CRC 1288 - INF'
latest_version: '0.1.2' latest_version: '0.1.1'
versions: versions:
0.1.0: 0.1.0:
methods: methods:
@ -23,12 +23,6 @@ tesseract-ocr-pipeline:
- 'ocropus_nlbin_threshold' - 'ocropus_nlbin_threshold'
publishing_year: 2022 publishing_year: 2022
url: 'https://gitlab.ub.uni-bielefeld.de/sfb1288inf/tesseract-ocr-pipeline/-/releases/v0.1.1' url: 'https://gitlab.ub.uni-bielefeld.de/sfb1288inf/tesseract-ocr-pipeline/-/releases/v0.1.1'
0.1.2:
methods:
- 'binarization'
- 'ocropus_nlbin_threshold'
publishing_year: 2023
url: 'https://gitlab.ub.uni-bielefeld.de/sfb1288inf/tesseract-ocr-pipeline/-/releases/v0.1.2'
transkribus-htr-pipeline: transkribus-htr-pipeline:
name: 'Transkribus HTR Pipeline' name: 'Transkribus HTR Pipeline'
publisher: 'Bielefeld University - CRC 1288 - INF' publisher: 'Bielefeld University - CRC 1288 - INF'
@ -47,7 +41,7 @@ transkribus-htr-pipeline:
spacy-nlp-pipeline: spacy-nlp-pipeline:
name: 'SpaCy NLP Pipeline' name: 'SpaCy NLP Pipeline'
publisher: 'Bielefeld University - CRC 1288 - INF' publisher: 'Bielefeld University - CRC 1288 - INF'
latest_version: '0.1.1' latest_version: '0.1.2'
versions: versions:
0.1.0: 0.1.0:
methods: methods:
@ -59,3 +53,8 @@ spacy-nlp-pipeline:
- 'encoding_detection' - 'encoding_detection'
publishing_year: 2022 publishing_year: 2022
url: 'https://gitlab.ub.uni-bielefeld.de/sfb1288inf/spacy-nlp-pipeline/-/releases/v0.1.1' url: 'https://gitlab.ub.uni-bielefeld.de/sfb1288inf/spacy-nlp-pipeline/-/releases/v0.1.1'
0.1.2:
methods:
- 'encoding_detection'
publishing_year: 2022
url: 'https://gitlab.ub.uni-bielefeld.de/sfb1288inf/spacy-nlp-pipeline/-/releases/v0.1.2'

View File

@ -1,108 +1,132 @@
#corpus-analysis-concordance-query-builder-input-field { .modal-conent {
border-bottom: #9E9E9E 1px solid;
min-height: 38px;
margin-top: 23px;
}
#corpus-analysis-concordance-query-builder-input-field-placeholder {
color: #9E9E9E;
}
.modal-content {
overflow-x: hidden; overflow-x: hidden;
} }
#corpus-analysis-concordance-positional-attr-modal, #corpus-analysis-concordance-corpus-analysis-concordance-structural-attr-modal { #concordance-query-builder {
width: 70%; width: 70%;
} }
#corpus-analysis-concordance-general-options-query-builder-tutorial-info-icon { #concordance-query-builder nav {
background-color: #6B3F89;
margin-top: -25px;
margin-left: -25px;
width: 105%;
}
#query-builder-nav{
padding-left: 15px;
}
#close-query-builder {
margin-right: 50px;
cursor: pointer;
}
#general-options-query-builder-tutorial-info-icon {
color: black; color: black;
} }
#corpus-analysis-concordance-insert-query-button { #your-query {
border-bottom-style: solid;
border-bottom-width: 1px;
}
#insert-query-button {
background-color: #00426f; background-color: #00426f;
text-align: center; text-align: center;
} }
.attr-modal-header { #structural-attr h6 {
margin-left: 15px;
}
#add-structural-attribute-tutorial-info-icon {
color: black;
}
#sentence {
background-color:#FD9720;
}
#entity {
background-color: #A6E22D;
}
#text-annotation {
background-color: #2FBBAB;
}
#no-value-metadata-message {
padding-top: 25px;
margin-left: -20px;
}
#token-kind-selector {
background-color: #f2eff7; background-color: #f2eff7;
padding: 15px; padding: 15px;
padding-left: 25px; border-top-style: solid;
border-top: 10px solid #6B3F89; border-color: #6B3F89;
margin-left: -24px;
margin-top: -24px;
margin-right: -24px;
} }
.attr-modal-header h6 { #token-kind-selector.s5 {
margin-top: 15px;
}
#token-kind-selector h6 {
margin-left: 15px; margin-left: 15px;
} }
#corpus-analysis-concordance-add-structural-attribute-tutorial-info-icon { #token-tutorial-info-icon {
color: black; color: black;
} }
[data-structural-attr-modal-action-button="sentence"]{ #no-value-message {
background-color:#FD9720 !important;
}
[data-structural-attr-modal-action-button="entity"]{
background-color: #A6E22D !important;
}
[data-structural-attr-modal-action-button="meta-data"]{
background-color: #2FBBAB !important;
}
#corpus-analysis-concordance-no-value-metadata-message {
padding-top: 25px; padding-top: 25px;
margin-left: -20px; margin-left: -20px;
} }
.attr-modal-header.input-field { #token-edit-options h6 {
margin-left: 41px;
}
#corpus-analysis-concordance-token-attr {
margin-left: 41px;
}
#corpus-analysis-concordance-token-tutorial-info-icon {
color: black;
}
#corpus-analysis-concordance-no-value-message {
padding-top: 25px;
margin-left: -20px;
}
#corpus-analysis-concordance-token-edit-options h6 {
margin-left: 15px; margin-left: 15px;
} }
#corpus-analysis-concordance-edit-options-tutorial-info-icon { #edit-options-tutorial-info-icon {
color: black; color: black;
} }
[data-toggle-area="input-field-options"] a { #incidence-modifiers-button a{
margin-right: 10px; background-color: #2FBBAB;
} }
[data-target="corpus-analysis-concordance-character-incidence-modifiers-dropdown"], [data-target="corpus-analysis-concordance-token-incidence-modifiers-dropdown"] { #incidence-modifiers a{
background-color: #2FBBAB !important; background-color: white;
} }
#corpus-analysis-concordance-exactly-n-token-modal, #corpus-analysis-concordance-between-nm-token-modal { #ignore-case {
width: 30%; margin-left: 5px;
} }
[data-modal-id="corpus-analysis-concordance-exactly-n-token-modal"], [data-modal-id="corpus-analysis-concordance-between-nm-token-modal"] { #or, #and {
margin-top: 15px !important; background-color: #fc0;
} }
[data-options-action="and"], [data-options-action="or"] { #betweenNM {
background-color: #fc0 !important; width: 60%;
}
#query-builder-tutorial-modal {
width: 60%;
}
#query-builder-tutorial-modal ul {
margin-top: 10px;
}
#query-builder-tutorial {
padding:15px;
}
#scroll-up-button-query-builder-tutorial {
background-color: #28B3D1;
} }
[data-type="start-sentence"], [data-type="end-sentence"] { [data-type="start-sentence"], [data-type="end-sentence"] {
@ -110,18 +134,13 @@
} }
[data-type="start-empty-entity"], [data-type="start-entity"], [data-type="end-entity"] { [data-type="start-empty-entity"], [data-type="start-entity"], [data-type="end-entity"] {
background-color: #a6e22d; background-color: #A6E22D;
} }
[data-type="text-annotation"]{ [data-type="start-text-annotation"]{
background-color: #2FBBAB; background-color: #2FBBAB;
} }
[data-type="token"] { [data-type="token"] {
background-color: #28B3D1; background-color: #28B3D1;
} }
[data-type="token-incidence-modifier"] {
background-color: #4db6ac;
color: white;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 402 KiB

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 720 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 589 KiB

After

Width:  |  Height:  |  Size: 854 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 381 KiB

After

Width:  |  Height:  |  Size: 511 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 759 KiB

After

Width:  |  Height:  |  Size: 1009 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 750 KiB

After

Width:  |  Height:  |  Size: 903 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 524 KiB

After

Width:  |  Height:  |  Size: 413 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

104
app/static/js/App.js Normal file
View File

@ -0,0 +1,104 @@
class App {
constructor() {
this.data = {
promises: {getUser: {}, subscribeUser: {}},
users: {},
};
this.socket = io({transports: ['websocket'], upgrade: false});
this.socket.on('PATCH', (patch) => {this.onPatch(patch);});
}
getUser(userId, backrefs=true, relationships=true) {
if (userId in this.data.promises.getUser) {
return this.data.promises.getUser[userId];
}
this.data.promises.getUser[userId] = new Promise((resolve, reject) => {
this.socket.emit('GET /users/<user_id>', userId, backrefs, relationships, (response) => {
if (response.status !== 200) {
reject(response);
return;
}
this.data.users[userId] = response.body;
resolve(this.data.users[userId]);
});
});
return this.data.promises.getUser[userId];
}
subscribeUser(userId) {
if (userId in this.data.promises.subscribeUser) {
return this.data.promises.subscribeUser[userId];
}
this.data.promises.subscribeUser[userId] = new Promise((resolve, reject) => {
this.socket.emit('SUBSCRIBE /users/<user_id>', userId, (response) => {
if (response.status !== 200) {
reject(response);
return;
}
resolve(response);
});
});
return this.data.promises.subscribeUser[userId];
}
flash(message, category) {
let iconPrefix = '';
switch (category) {
case 'corpus': {
iconPrefix = '<i class="left material-icons">book</i>';
break;
}
case 'error': {
iconPrefix = '<i class="error-color-text left material-icons">error</i>';
break;
}
case 'job': {
iconPrefix = '<i class="left nopaque-icons">J</i>';
break;
}
case 'settings': {
iconPrefix = '<i class="left material-icons">settings</i>';
break;
}
default: {
iconPrefix = '<i class="left material-icons">notifications</i>';
break;
}
}
let toast = M.toast(
{
html: `
<span>${iconPrefix}${message}</span>
<button class="action-button btn-flat toast-action white-text" data-action="close">
<i class="material-icons">close</i>
</button>
`.trim()
}
);
let toastCloseActionElement = toast.el.querySelector('.action-button[data-action="close"]');
toastCloseActionElement.addEventListener('click', () => {toast.dismiss();});
}
onPatch(patch) {
// Filter Patch to only include operations on users that are initialized
let regExp = new RegExp(`^/users/(${Object.keys(this.data.users).join('|')})`);
let filteredPatch = patch.filter(operation => regExp.test(operation.path));
// Handle job status updates
let subRegExp = new RegExp(`^/users/([A-Za-z0-9]*)/jobs/([A-Za-z0-9]*)/status$`);
let subFilteredPatch = filteredPatch
.filter((operation) => {return operation.op === 'replace';})
.filter((operation) => {return subRegExp.test(operation.path);});
for (let operation of subFilteredPatch) {
let [match, userId, jobId] = operation.path.match(subRegExp);
this.flash(`[<a href="/jobs/${jobId}">${this.data.users[userId].jobs[jobId].title}</a>] New status: <span class="job-status-text" data-status="${operation.value}"></span>`, 'job');
}
// Apply Patch
jsonpatch.applyPatch(this.data, filteredPatch);
}
}

View File

@ -0,0 +1,485 @@
class CQiClient {
constructor(corpusId) {
this.socket = io(
'/corpora/corpus/corpus_analysis',
{
auth: {corpus_id: corpusId},
transports: ['websocket'],
upgrade: false
}
);
this.connected = false;
this.corpora = new CQiCorpusCollection(this.socket);
}
connect() {
return new Promise((resolve, reject) => {
this.socket.emit('cqi.connect', response => {
if (response.code === 200) {
this.connected = true;
resolve(response.payload);
} else {
reject(response);
}
});
});
}
disconnect() {
return new Promise((resolve, reject) => {
this.socket.emit('cqi.disconnect', response => {
if (response.code === 200) {
this.connected = false;
resolve(response.payload);
} else {
reject(response);
}
});
});
}
ping() {
return new Promise((resolve, reject) => {
this.socket.emit('cqi.ping', response => {
if (response.code === 200) {
resolve(response.payload);
} else {
reject(response);
}
});
});
}
}
class CQiCorpusCollection {
constructor(socket) {
this.socket = socket;
}
get(corpusName) {
return new Promise((resolve, reject) => {
const args = {corpus_name: corpusName};
this.socket.emit('cqi.corpora.get', args, response => {
if (response.code === 200) {
resolve(new CQiCorpus(this.socket, response.payload));
} else {
reject(response);
}
});
});
}
list() {
return new Promise((resolve, reject) => {
this.socket.emit('cqi.corpora.list', response => {
if (response.code === 200) {
resolve(response.payload.map(x => {return new CQiSubcorpus(this.socket, x);}));
} else {
reject(response);
}
});
});
}
}
class CQiCorpus {
constructor(socket, attrs) {
this.socket = socket;
this.charset = attrs.charset;
this.name = attrs.name;
this.properties = attrs.properties;
this.size = attrs.size;
this.alignmentAttributes = new CQiAlignmentAttributeCollection(this.socket, this);
this.positionalAttributes = new CQiPositionalAttributeCollection(this.socket, this);
this.structuralAttributes = new CQiStructuralAttributeCollection(this.socket, this);
this.subcorpora = new CQiSubcorpusCollection(this.socket, this);
}
drop() {
return new Promise((resolve, reject) => {
const args = {corpus_name: this.name};
this.socket.emit('cqi.corpora.corpus.drop', args, response => {
if (response.code === 200) {
resolve(response.payload);
} else {
reject(response);
}
});
});
}
query(subcorpus_name, queryString) {
return new Promise((resolve, reject) => {
const args = {
corpus_name: this.name,
subcorpus_name: subcorpus_name,
query: queryString
};
this.socket.emit('cqi.corpora.corpus.query', args, response => {
if (response.code === 200) {
resolve(response.payload);
} else {
reject(response);
}
});
});
}
// nopaque specific CQi extension
paginate(page=1, perPage=20) {
return new Promise((resolve, reject) => {
const args = {corpus_name: this.name, page: page, per_page: perPage};
this.socket.emit('cqi.corpora.corpus.paginate', args, response => {
if (response.code === 200) {
resolve(response.payload);
} else {
reject(response);
}
});
});
}
updateDb() {
const args = {corpus_name: this.name};
this.socket.emit('cqi.corpora.corpus.update_db', args);
}
}
class CQiAlignmentAttributeCollection {
constructor(socket, corpus) {
this.corpus = corpus;
this.socket = socket;
}
get(alignmentAttributeName) {
return new Promise((resolve, reject) => {
const args = {
corpus_name: this.corpus.name,
alignment_attribute_name: alignmentAttributeName
};
this.socket.emit('cqi.corpora.corpus.alignment_attributes.get', args, response => {
if (response.code === 200) {
resolve(new CQiAlignmentAttribute(this.socket, this.corpus, response.payload));
} else {
reject(response);
}
});
});
}
list() {
return new Promise((resolve, reject) => {
const args = {corpus_name: this.corpus.name};
this.socket.emit('cqi.corpus.alignment_attributes.list', args, response => {
if (response.code === 200) {
resolve(response.payload.map(x => {return new CQiAlignmentAttribute(this.socket, this.corpus, x);}));
} else {
reject(response);
}
});
});
}
}
class CQiAlignmentAttribute {
constructor(socket, corpus, attrs) {
this.socket = socket;
this.corpus = corpus;
this.name = attrs.name;
this.size = attrs.size;
}
}
class CQiPositionalAttributeCollection {
constructor(socket, corpus) {
this.corpus = corpus;
this.socket = socket;
}
get(positionalAttributeName) {
return new Promise((resolve, reject) => {
const args = {
corpus_name: this.corpus.name,
positional_attribute_name: positionalAttributeName
};
this.socket.emit('cqi.corpora.corpus.positional_attributes.get', args, response => {
if (response.code === 200) {
resolve(new CQiPositionalAttribute(this.socket, this.corpus, response.payload));
} else {
reject(response);
}
});
});
}
list() {
return new Promise((resolve, reject) => {
const args = {corpus_name: this.corpus.name};
this.socket.emit('cqi.corpus.positional_attributes.list', args, response => {
if (response.code === 200) {
resolve(response.payload.map(x => {return new CQiPositionalAttribute(this.socket, this.corpus, x);}));
} else {
reject(response);
}
});
});
}
}
class CQiPositionalAttribute {
constructor(socket, corpus, attrs) {
this.socket = socket;
this.corpus = corpus;
this.lexiconSize = attrs.lexicon_size;
this.name = attrs.name;
this.size = attrs.size;
}
}
class CQiStructuralAttributeCollection {
constructor(socket, corpus) {
this.corpus = corpus;
this.socket = socket;
}
get(structuralAttributeName) {
return new Promise((resolve, reject) => {
const args = {
corpus_name: this.corpus.name,
structural_attribute_name: structuralAttributeName
};
this.socket.emit('cqi.corpora.corpus.structural_attributes.get', args, response => {
if (response.code === 200) {
resolve(new CQiStructuralAttribute(this.socket, this.corpus, response.payload));
} else {
reject(response);
}
});
});
}
list() {
return new Promise((resolve, reject) => {
const args = {corpus_name: this.corpus.name};
this.socket.emit('cqi.corpus.structural_attributes.list', args, response => {
if (response.code === 200) {
resolve(response.payload.map(x => {return new CQiStructuralAttribute(this.socket, this.corpus, x);}));
} else {
reject(response);
}
});
});
}
}
class CQiStructuralAttribute {
constructor(socket, corpus, attrs) {
this.socket = socket;
this.corpus = corpus;
this.hasValues = attrs.has_values;
this.name = attrs.name;
this.size = attrs.size;
}
}
class CQiSubcorpusCollection {
constructor(socket, corpus) {
this.corpus = corpus;
this.socket = socket;
}
get(subcorpusName) {
return new Promise((resolve, reject) => {
const args = {
corpus_name: this.corpus.name,
subcorpus_name: subcorpusName
};
this.socket.emit('cqi.corpora.corpus.subcorpora.get', args, response => {
if (response.code === 200) {
resolve(new CQiSubcorpus(this.socket, this.corpus, response.payload));
} else {
reject(response);
}
});
});
}
list() {
return new Promise((resolve, reject) => {
const args = {corpus_name: this.corpus.name};
this.socket.emit('cqi.corpora.corpus.subcorpora.list', args, response => {
if (response.code === 200) {
resolve(response.payload.map(x => {return new CQiSubcorpus(this.socket, this.corpus, x);}));
} else {
reject(response);
}
});
});
}
}
class CQiSubcorpus {
constructor(socket, corpus, attrs) {
this.socket = socket;
this.corpus = corpus;
this.fields = attrs.fields;
this.name = attrs.name;
this.size = attrs.size;
}
drop() {
return new Promise((resolve, reject) => {
const args = {corpus_name: this.corpus.name, subcorpus_name: this.name};
this.socket.emit('cqi.corpora.corpus.subcorpora.subcorpus.drop', args, response => {
if (response.code === 200) {
resolve(response.payload);
} else {
reject(response);
}
});
});
}
dump(field, first, last) {
return new Promise((resolve, reject) => {
const args = {
corpus_name: this.corpus.name,
subcorpus_name: this.name,
field: field,
first: first,
last: last
};
this.socket.emit('cqi.corpora.corpus.subcorpora.subcorpus.dump', args, response => {
if (response.code === 200) {
resolve(response.payload);
} else {
reject(response);
}
});
});
}
export(context=50) {
return new Promise((resolve, reject) => {
const args = {
corpus_name: this.corpus.name,
subcorpus_name: this.name,
context: context
};
this.socket.emit('cqi.corpora.corpus.subcorpora.subcorpus.export', args, response => {
if (response.code === 200) {
resolve(response.payload);
} else {
reject(response);
}
});
});
}
partial_export(matchIdList, context=50) {
return new Promise((resolve, reject) => {
const args = {
corpus_name: this.corpus.name,
subcorpus_name: this.name,
match_id_list: matchIdList,
context: context
};
this.socket.emit('cqi.corpora.corpus.subcorpora.subcorpus.partial_export', args, response => {
if (response.code === 200) {
resolve(response.payload);
} else {
reject(response);
}
});
});
}
fdst_1(cutoff, field, attribute) {
return new Promise((resolve, reject) => {
const args = {
corpus_name: this.corpus.name,
subcorpus_name: this.name,
cutoff: cutoff,
field: field,
attribute: attribute
};
this.socket.emit('cqi.corpora.corpus.subcorpora.subcorpus.fdist_1', args, response => {
if (response.code === 200) {
resolve(response.payload);
} else {
reject(response);
}
});
});
}
fdst_2(cutoff, field1, attribute1, field2, attribute2) {
return new Promise((resolve, reject) => {
const args = {
corpus_name: this.corpus.name,
subcorpus_name: this.name,
cutoff: cutoff,
field1: field1,
attribute1: attribute1,
field2: field2,
attribute2: attribute2
};
this.socket.emit('cqi.corpora.corpus.subcorpora.subcorpus.fdist_1', args, response => {
if (response.code === 200) {
resolve(response.payload);
} else {
reject(response);
}
});
});
}
// nopaque specific CQi extension
paginate(page=1, perPage=20, context=50) {
return new Promise((resolve, reject) => {
const args = {
corpus_name: this.corpus.name,
subcorpus_name: this.name,
page: page,
per_page: perPage,
context: context
};
this.socket.emit('cqi.corpora.corpus.subcorpora.subcorpus.paginate', args, response => {
if (response.code === 200) {
resolve(response.payload);
} else {
reject(response);
}
});
});
}
}

View File

@ -0,0 +1,98 @@
class CorpusAnalysisApp {
constructor(corpusId) {
this.data = {};
// HTML elements
this.elements = {
container: document.querySelector('#corpus-analysis-app-container'),
extensionTabs: document.querySelector('#corpus-analysis-app-extension-tabs'),
initModal: document.querySelector('#corpus-analysis-app-init-modal'),
overview: document.querySelector('#corpus-analysis-app-overview')
};
// Materialize elements
this.elements.m = {
extensionTabs: M.Tabs.init(this.elements.extensionTabs),
initModal: M.Modal.init(this.elements.initModal, {dismissible: false})
};
this.extensions = {};
this.settings = {
corpusId: corpusId
};
}
init() {
this.disableActionElements();
this.elements.m.initModal.open();
// Init data
this.data.cQiClient = new CQiClient(this.settings.corpusId);
this.data.cQiClient.connect()
.then(cQiStatus => {
return this.data.cQiClient.corpora.get(`NOPAQUE_${this.settings.corpusId}`);
})
.then(
cQiCorpus => {
this.data.corpus = {o: cQiCorpus};
// TODO: Don't do this here
cQiCorpus.updateDb();
this.enableActionElements();
for (let extension of Object.values(this.extensions)) {extension.init();}
this.elements.m.initModal.close();
},
cQiError => {
let errorsElement = this.elements.initModal.querySelector('.errors');
let progressElement = this.elements.initModal.querySelector('.progress');
errorsElement.innerText = JSON.stringify(cQiError);
errorsElement.classList.remove('hide');
progressElement.classList.add('hide');
if ('payload' in cQiError && 'code' in cQiError.payload && 'msg' in cQiError.payload) {
app.flash(`${cQiError.payload.code}: ${cQiError.payload.msg}`, 'error');
}
}
);
// Add event listeners
for (let extensionSelectorElement of this.elements.overview.querySelectorAll('.extension-selector')) {
extensionSelectorElement.addEventListener('click', () => {
this.elements.m.extensionTabs.select(extensionSelectorElement.dataset.target);
});
}
}
registerExtension(extension) {
if (extension.name in this.extensions) {
console.error(`Can't register extension ${extension.name}: Already registered`);
return;
}
this.extensions[extension.name] = extension;
if ('cQiClient' in this.data && this.data.cQiClient.connected) {extension.init();}
}
disableActionElements() {
let actionElements = this.elements.container.querySelectorAll('.corpus-analysis-action');
for (let actionElement of actionElements) {
if (actionElement.nodeName === 'INPUT') {
actionElement.disabled = true;
} else if (actionElement.nodeName === 'SELECT') {
actionElement.parentNode.querySelector('input.select-dropdown').disabled = true;
} else {
actionElement.classList.add('disabled');
}
}
}
enableActionElements() {
let actionElements = this.elements.container.querySelectorAll('.corpus-analysis-action');
for (let actionElement of actionElements) {
if (actionElement.nodeName === 'INPUT') {
actionElement.disabled = false;
} else if (actionElement.nodeName === 'SELECT') {
actionElement.parentNode.querySelector('input.select-dropdown').disabled = false;
} else {
actionElement.classList.remove('disabled');
}
}
}
}

View File

@ -1,4 +1,4 @@
nopaque.corpus_analysis.ConcordanceExtension = class ConcordanceExtension { class CorpusAnalysisConcordance {
name = 'Concordance'; name = 'Concordance';
constructor(app) { constructor(app) {
@ -7,99 +7,93 @@ nopaque.corpus_analysis.ConcordanceExtension = class ConcordanceExtension {
this.data = {}; this.data = {};
this.elements = { this.elements = {
container: document.querySelector(`#corpus-analysis-concordance-container`), // TODO: Prefix elements with "corpus-analysis-app-"
error: document.querySelector(`#corpus-analysis-concordance-error`), container: document.querySelector('#concordance-extension-container'),
userInterfaceForm: document.querySelector(`#corpus-analysis-concordance-user-interface-form`), error: document.querySelector('#concordance-extension-error'),
expertModeForm: document.querySelector(`#corpus-analysis-concordance-expert-mode-form`), form: document.querySelector('#concordance-extension-form'),
queryBuilderForm: document.querySelector(`#corpus-analysis-concordance-query-builder-form`), progress: document.querySelector('#concordance-extension-progress'),
progress: document.querySelector(`#corpus-analysis-concordance-progress`), subcorpusInfo: document.querySelector('#concordance-extension-subcorpus-info'),
subcorpusInfo: document.querySelector(`#corpus-analysis-concordance-subcorpus-info`), subcorpusActions: document.querySelector('#concordance-extension-subcorpus-actions'),
subcorpusActions: document.querySelector(`#corpus-analysis-concordance-subcorpus-actions`), subcorpusItems: document.querySelector('#concordance-extension-subcorpus-items'),
subcorpusItems: document.querySelector(`#corpus-analysis-concordance-subcorpus-items`), subcorpusList: document.querySelector('#concordance-extension-subcorpus-list'),
subcorpusList: document.querySelector(`#corpus-analysis-concordance-subcorpus-list`), subcorpusPagination: document.querySelector('#concordance-extension-subcorpus-pagination')
subcorpusPagination: document.querySelector(`#corpus-analysis-concordance-subcorpus-pagination`)
}; };
this.settings = { this.settings = {
context: parseInt(this.elements.userInterfaceForm['context'].value), context: parseInt(this.elements.form['context'].value),
perPage: parseInt(this.elements.userInterfaceForm['per-page'].value), perPage: parseInt(this.elements.form['per-page'].value),
selectedSubcorpus: undefined, selectedSubcorpus: undefined,
textStyle: parseInt(this.elements.userInterfaceForm['text-style'].value), textStyle: parseInt(this.elements.form['text-style'].value),
tokenRepresentation: this.elements.userInterfaceForm['token-representation'].value tokenRepresentation: this.elements.form['token-representation'].value
}; };
this.app.registerExtension(this); this.app.registerExtension(this);
} }
async submitForm(queryModeId) { init() {
this.app.disableActionElements();
let queryBuilderQuery = nopaque.Utils.unescape(document.querySelector('#corpus-analysis-concordance-query-preview').innerHTML.trim());
let expertModeQuery = this.elements.expertModeForm.query.value.trim();
let query = queryModeId === 'corpus-analysis-concordance-expert-mode-form' ? expertModeQuery : queryBuilderQuery;
let form = queryModeId === 'corpus-analysis-concordance-expert-mode-form' ? this.elements.expertModeForm : this.elements.queryBuilderForm;
let subcorpusName = form['subcorpus-name'].value;
this.elements.error.innerText = '';
this.elements.error.classList.add('hide');
this.elements.progress.classList.remove('hide');
try {
const subcorpus = {};
subcorpus.q = query;
subcorpus.selectedItems = new Set();
await this.data.corpus.o.query(subcorpusName, query);
if (subcorpusName !== 'Last') {this.data.subcorpora.Last = subcorpus;}
const cqiSubcorpus = await this.data.corpus.o.subcorpora.get(subcorpusName);
subcorpus.o = cqiSubcorpus;
const paginatedSubcorpus = await cqiSubcorpus.paginate(this.settings.context, 1, this.settings.perPage);
subcorpus.p = paginatedSubcorpus;
this.data.subcorpora[subcorpusName] = subcorpus;
this.settings.selectedSubcorpus = subcorpusName;
this.renderSubcorpusList();
this.renderSubcorpusInfo();
this.renderSubcorpusActions();
this.renderSubcorpusItems();
this.renderSubcorpusPagination();
this.elements.progress.classList.add('hide');
} catch (error) {
let errorString = '';
if ('code' in error) {errorString += `[${error.code}] `;}
errorString += `${error.constructor.name}`;
this.elements.error.innerText = errorString;
this.elements.error.classList.remove('hide');
app.flash(errorString, 'error');
this.elements.progress.classList.add('hide');
}
this.app.enableActionElements();
}
async init() {
// Init data // Init data
this.data.corpus = this.app.data.corpus; this.data.corpus = this.app.data.corpus;
this.data.subcorpora = {}; this.data.subcorpora = {};
// Add event listeners // Add event listeners
this.elements.expertModeForm.addEventListener('submit', (event) => { this.elements.form.addEventListener('submit', event => {
event.preventDefault(); event.preventDefault();
this.submitForm(this.elements.expertModeForm.id); this.app.disableActionElements();
let query = this.elements.form.query.value.trim();
let subcorpusName = this.elements.form['subcorpus-name'].value;
this.elements.error.innerText = '';
this.elements.error.classList.add('hide');
this.elements.progress.classList.remove('hide');
let subcorpus = {};
this.data.corpus.o.query(subcorpusName, query)
.then(cQiStatus => {
subcorpus.q = query;
subcorpus.selectedItems = new Set();
if (subcorpusName !== 'Last') {this.data.subcorpora.Last = subcorpus;}
return this.data.corpus.o.subcorpora.get(subcorpusName);
})
.then(cQiSubcorpus => {
subcorpus.o = cQiSubcorpus;
return cQiSubcorpus.paginate(1, this.settings.perPage, this.settings.context);
})
.then(
paginatedSubcorpus => {
subcorpus.p = paginatedSubcorpus;
this.data.subcorpora[subcorpusName] = subcorpus;
this.settings.selectedSubcorpus = subcorpusName;
this.renderSubcorpusList();
this.renderSubcorpusInfo();
this.renderSubcorpusActions();
this.renderSubcorpusItems();
this.renderSubcorpusPagination();
this.elements.progress.classList.add('hide');
this.app.enableActionElements();
},
cQiError => {
this.elements.error.innerText = JSON.stringify(cQiError);
this.elements.error.classList.remove('hide');
if ('payload' in cQiError && 'code' in cQiError.payload && 'msg' in cQiError.payload) {
app.flash(`${cQiError.payload.code}: ${cQiError.payload.msg}`, 'error');
}
this.elements.progress.classList.add('hide');
this.app.enableActionElements();
}
);
}); });
this.elements.queryBuilderForm.addEventListener('submit', (event) => { this.elements.form.addEventListener('change', event => {
event.preventDefault(); if (event.target === this.elements.form['context']) {
this.submitForm(this.elements.queryBuilderForm.id); this.settings.context = parseInt(this.elements.form['context'].value);
}); this.elements.form.submit.click();
this.elements.userInterfaceForm.addEventListener('change', (event) => {
if (event.target === this.elements.userInterfaceForm['context']) {
this.settings.context = parseInt(this.elements.userInterfaceForm['context'].value);
this.submitForm();
} }
if (event.target === this.elements.userInterfaceForm['per-page']) { if (event.target === this.elements.form['per-page']) {
this.settings.perPage = parseInt(this.elements.userInterfaceForm['per-page'].value); this.settings.perPage = parseInt(this.elements.form['per-page'].value);
this.submitForm(); this.elements.form.submit.click();
} }
if (event.target === this.elements.userInterfaceForm['text-style']) { if (event.target === this.elements.form['text-style']) {
this.settings.textStyle = parseInt(this.elements.userInterfaceForm['text-style'].value); this.settings.textStyle = parseInt(this.elements.form['text-style'].value);
this.setTextStyle(); this.setTextStyle();
} }
if (event.target === this.elements.userInterfaceForm['token-representation']) { if (event.target === this.elements.form['token-representation']) {
this.settings.tokenRepresentation = this.elements.userInterfaceForm['token-representation'].value; this.settings.tokenRepresentation = this.elements.form['token-representation'].value;
this.setTokenRepresentation(); this.setTokenRepresentation();
} }
}); });
@ -168,14 +162,14 @@ nopaque.corpus_analysis.ConcordanceExtension = class ConcordanceExtension {
</a> </a>
`.trim(); `.trim();
M.Tooltip.init(this.elements.subcorpusActions.querySelectorAll('.tooltipped')); M.Tooltip.init(this.elements.subcorpusActions.querySelectorAll('.tooltipped'));
this.elements.subcorpusActions.querySelector('.subcorpus-export-trigger').addEventListener('click', (event) => { this.elements.subcorpusActions.querySelector('.subcorpus-export-trigger').addEventListener('click', event => {
event.preventDefault(); event.preventDefault();
let subcorpus = this.data.subcorpora[this.settings.selectedSubcorpus]; let subcorpus = this.data.subcorpora[this.settings.selectedSubcorpus];
let modalElementId = nopaque.Utils.generateElementId('export-subcorpus-modal-'); let modalElementId = Utils.generateElementId('export-subcorpus-modal-');
let exportFormatSelectElementId = nopaque.Utils.generateElementId('export-format-select-'); let exportFormatSelectElementId = Utils.generateElementId('export-format-select-');
let exportSelectedMatchesOnlyCheckboxElementId = nopaque.Utils.generateElementId('export-selected-matches-only-checkbox-'); let exportSelectedMatchesOnlyCheckboxElementId = Utils.generateElementId('export-selected-matches-only-checkbox-');
let exportFileNameInputElementId = nopaque.Utils.generateElementId('export-file-name-input-'); let exportFileNameInputElementId = Utils.generateElementId('export-file-name-input-');
let modalElement = nopaque.Utils.HTMLToElement( let modalElement = Utils.HTMLToElement(
` `
<div class="modal" id="${modalElementId}"> <div class="modal" id="${modalElementId}">
<div class="modal-content"> <div class="modal-content">
@ -225,7 +219,7 @@ nopaque.corpus_analysis.ConcordanceExtension = class ConcordanceExtension {
} }
} }
); );
exportButton.addEventListener('click', (event) => { exportButton.addEventListener('click', event => {
event.preventDefault(); event.preventDefault();
this.app.disableActionElements(); this.app.disableActionElements();
this.elements.progress.classList.remove('hide'); this.elements.progress.classList.remove('hide');
@ -242,12 +236,12 @@ nopaque.corpus_analysis.ConcordanceExtension = class ConcordanceExtension {
app.flash('No matches selected', 'error'); app.flash('No matches selected', 'error');
return; return;
} }
promise = subcorpus.o.partialExport([...subcorpus.selectedItems], 50); promise = subcorpus.o.partial_export([...subcorpus.selectedItems], 50);
} else { } else {
promise = subcorpus.o.export(50); promise = subcorpus.o.export(50);
} }
promise.then( promise.then(
(data) => { data => {
let blob; let blob;
if (exportFormat === 'csv') { if (exportFormat === 'csv') {
let csvContent = 'sep=,\r\n'; let csvContent = 'sep=,\r\n';
@ -293,11 +287,11 @@ nopaque.corpus_analysis.ConcordanceExtension = class ConcordanceExtension {
}); });
modal.open(); modal.open();
}); });
this.elements.subcorpusActions.querySelector('.subcorpus-delete-trigger').addEventListener('click', (event) => { this.elements.subcorpusActions.querySelector('.subcorpus-delete-trigger').addEventListener('click', event => {
event.preventDefault(); event.preventDefault();
let subcorpus = this.data.subcorpora[this.settings.selectedSubcorpus]; let subcorpus = this.data.subcorpora[this.settings.selectedSubcorpus];
subcorpus.o.drop().then( subcorpus.o.drop().then(
(cQiStatus) => { cQiStatus => {
app.flash(`${subcorpus.o.name} deleted`, 'corpus'); app.flash(`${subcorpus.o.name} deleted`, 'corpus');
delete this.data.subcorpora[subcorpus.o.name]; delete this.data.subcorpora[subcorpus.o.name];
this.settings.selectedSubcorpus = undefined; this.settings.selectedSubcorpus = undefined;
@ -318,9 +312,8 @@ nopaque.corpus_analysis.ConcordanceExtension = class ConcordanceExtension {
this.clearSubcorpusPagination(); this.clearSubcorpusPagination();
} }
}, },
(cqiError) => { cQiError => {
let errorString = `${cqiError.code}: ${cqiError.constructor.name}`; app.flash(`${cQiError.payload.code}: ${cQiError.payload.msg}`, 'error');
app.flash(errorString, 'error');
} }
); );
}); });
@ -369,7 +362,7 @@ nopaque.corpus_analysis.ConcordanceExtension = class ConcordanceExtension {
this.setTextStyle(); this.setTextStyle();
this.setTokenRepresentation(); this.setTokenRepresentation();
for (let gotoReaderTriggerElement of this.elements.subcorpusItems.querySelectorAll('.goto-reader-trigger')) { for (let gotoReaderTriggerElement of this.elements.subcorpusItems.querySelectorAll('.goto-reader-trigger')) {
gotoReaderTriggerElement.addEventListener('click', (event) => { gotoReaderTriggerElement.addEventListener('click', event => {
event.preventDefault(); event.preventDefault();
let corpusAnalysisReader = this.app.extensions.Reader; let corpusAnalysisReader = this.app.extensions.Reader;
let itemId = parseInt(gotoReaderTriggerElement.closest('.item').dataset.id); let itemId = parseInt(gotoReaderTriggerElement.closest('.item').dataset.id);
@ -387,13 +380,11 @@ nopaque.corpus_analysis.ConcordanceExtension = class ConcordanceExtension {
document.getSelection().removeAllRanges(); document.getSelection().removeAllRanges();
document.getSelection().addRange(range); document.getSelection().addRange(range);
}); });
this.app.elements.m.extensionTabs.select( this.app.elements.m.extensionTabs.select('reader-extension-container');
this.app.extensions.Reader.elements.container.id
);
}); });
} }
for (let selectTriggerElement of this.elements.subcorpusItems.querySelectorAll('.select-trigger')) { for (let selectTriggerElement of this.elements.subcorpusItems.querySelectorAll('.select-trigger')) {
selectTriggerElement.addEventListener('click', (event) => { selectTriggerElement.addEventListener('click', event => {
event.preventDefault(); event.preventDefault();
let itemElement = selectTriggerElement.closest('.item'); let itemElement = selectTriggerElement.closest('.item');
let itemId = parseInt(itemElement.dataset.id); let itemId = parseInt(itemElement.dataset.id);
@ -455,14 +446,14 @@ nopaque.corpus_analysis.ConcordanceExtension = class ConcordanceExtension {
</li> </li>
`.trim(); `.trim();
for (let paginationTriggerElement of this.elements.subcorpusPagination.querySelectorAll('.pagination-trigger[data-target]')) { for (let paginationTriggerElement of this.elements.subcorpusPagination.querySelectorAll('.pagination-trigger[data-target]')) {
paginationTriggerElement.addEventListener('click', (event) => { paginationTriggerElement.addEventListener('click', event => {
event.preventDefault(); event.preventDefault();
this.app.disableActionElements(); this.app.disableActionElements();
this.elements.progress.classList.remove('hide'); this.elements.progress.classList.remove('hide');
let page = parseInt(paginationTriggerElement.dataset.target); let page = parseInt(paginationTriggerElement.dataset.target);
subcorpus.o.paginate(this.settings.context, page, this.settings.perPage) subcorpus.o.paginate(page, this.settings.perPage, this.settings.context)
.then( .then(
(paginatedSubcorpus) => { paginatedSubcorpus => {
subcorpus.p = paginatedSubcorpus; subcorpus.p = paginatedSubcorpus;
this.renderSubcorpusItems(); this.renderSubcorpusItems();
this.renderSubcorpusPagination(); this.renderSubcorpusPagination();

View File

@ -1,4 +1,4 @@
nopaque.corpus_analysis.ReaderExtension = class ReaderExtension { class CorpusAnalysisReader {
name = 'Reader'; name = 'Reader';
constructor(app) { constructor(app) {
@ -7,75 +7,71 @@ nopaque.corpus_analysis.ReaderExtension = class ReaderExtension {
this.data = {}; this.data = {};
this.elements = { this.elements = {
container: document.querySelector(`#corpus-analysis-reader-container`), // TODO: Prefix elements with "corpus-analysis-app-"
corpus: document.querySelector(`#corpus-analysis-reader-corpus`), container: document.querySelector('#reader-extension-container'),
corpusPagination: document.querySelector(`#corpus-analysis-reader-corpus-pagination`), error: document.querySelector('#reader-extension-error'),
error: document.querySelector(`#corpus-analysis-reader-error`), form: document.querySelector('#reader-extension-form'),
progress: document.querySelector(`#corpus-analysis-reader-progress`), progress: document.querySelector('#reader-extension-progress'),
userInterfaceForm: document.querySelector(`#corpus-analysis-reader-user-interface-form`) corpus: document.querySelector('#reader-extension-corpus'),
corpusPagination: document.querySelector('#reader-extension-corpus-pagination')
}; };
this.settings = { this.settings = {
perPage: parseInt(this.elements.userInterfaceForm['per-page'].value), perPage: parseInt(this.elements.form['per-page'].value),
textStyle: parseInt(this.elements.userInterfaceForm['text-style'].value), textStyle: parseInt(this.elements.form['text-style'].value),
tokenRepresentation: this.elements.userInterfaceForm['token-representation'].value, tokenRepresentation: this.elements.form['token-representation'].value
pagination: {
innerWindow: 5,
outerWindow: 1
}
} }
this.app.registerExtension(this); this.app.registerExtension(this);
} }
async submitForm() { init() {
this.app.disableActionElements();
this.elements.error.innerText = '';
this.elements.error.classList.add('hide');
this.elements.progress.classList.remove('hide');
try {
const paginatedCorpus = await this.data.corpus.o.paginate(1, this.settings.perPage);
this.data.corpus.p = paginatedCorpus;
this.renderCorpus();
this.renderCorpusPagination();
this.elements.progress.classList.add('hide');
} catch (error) {
let errorString = '';
if ('code' in error) {errorString += `[${error.code}] `;}
errorString += `${error.constructor.name}`;
if ('description' in error) {errorString += `: ${error.description}`;}
this.elements.error.innerText = errorString;
this.elements.error.classList.remove('hide');
app.flash(errorString, 'error');
this.elements.progress.classList.add('hide');
}
this.app.enableActionElements();
}
async init() {
// Init data // Init data
this.data.corpus = this.app.data.corpus; this.data.corpus = this.app.data.corpus;
this.data.subcorpora = {};
// Add event listeners // Add event listeners
this.elements.userInterfaceForm.addEventListener('submit', (event) => { this.elements.form.addEventListener('submit', (event) => {
event.preventDefault(); event.preventDefault();
this.submitForm(); this.app.disableActionElements();
this.elements.error.innerText = '';
this.elements.error.classList.add('hide');
this.elements.progress.classList.remove('hide');
this.data.corpus.o.paginate(1, this.settings.perPage)
.then(
paginatedCorpus => {
this.data.corpus.p = paginatedCorpus;
this.renderCorpus();
this.renderCorpusPagination();
this.elements.progress.classList.add('hide');
this.app.enableActionElements();
},
error => {
this.elements.error.innerText = JSON.stringify(error);
this.elements.error.classList.remove('hide');
if ('payload' in error && 'code' in error.payload && 'msg' in error.payload) {
app.flash(`${error.payload.code}: ${error.payload.msg}`, 'error');
}
this.elements.progress.classList.add('hide');
this.app.enableActionElements();
}
);
}); });
this.elements.userInterfaceForm.addEventListener('change', (event) => { this.elements.form.addEventListener('change', event => {
if (event.target === this.elements.userInterfaceForm['per-page']) { if (event.target === this.elements.form['per-page']) {
this.settings.perPage = parseInt(this.elements.userInterfaceForm['per-page'].value); this.settings.perPage = parseInt(this.elements.form['per-page'].value);
this.submitForm(); this.elements.form.submit.click();
} }
if (event.target === this.elements.userInterfaceForm['text-style']) { if (event.target === this.elements.form['text-style']) {
this.settings.textStyle = parseInt(this.elements.userInterfaceForm['text-style'].value); this.settings.textStyle = parseInt(this.elements.form['text-style'].value);
this.setTextStyle(); this.setTextStyle();
} }
if (event.target === this.elements.userInterfaceForm['token-representation']) { if (event.target === this.elements.form['token-representation']) {
this.settings.tokenRepresentation = this.elements.userInterfaceForm['token-representation'].value; this.settings.tokenRepresentation = this.elements.form['token-representation'].value;
this.setTokenRepresentation(); this.setTokenRepresentation();
} }
}); });
// Load initial data // Load initial data
await this.submitForm(); this.elements.form.submit.click();
} }
clearCorpus() { clearCorpus() {
@ -112,7 +108,7 @@ nopaque.corpus_analysis.ReaderExtension = class ReaderExtension {
if (this.data.corpus.p.pages === 0) {return;} if (this.data.corpus.p.pages === 0) {return;}
let pageElement; let pageElement;
// First page button. Disables first page button if on first page // First page button. Disables first page button if on first page
pageElement = nopaque.Utils.HTMLToElement( pageElement = Utils.HTMLToElement(
` `
<li class="${this.data.corpus.p.page === 1 ? 'disabled' : 'waves-effect'}"> <li class="${this.data.corpus.p.page === 1 ? 'disabled' : 'waves-effect'}">
<a class="corpus-analysis-action pagination-trigger" ${this.data.corpus.p.page === 1 ? '' : 'data-target="1"'}> <a class="corpus-analysis-action pagination-trigger" ${this.data.corpus.p.page === 1 ? '' : 'data-target="1"'}>
@ -123,7 +119,7 @@ nopaque.corpus_analysis.ReaderExtension = class ReaderExtension {
); );
this.elements.corpusPagination.appendChild(pageElement); this.elements.corpusPagination.appendChild(pageElement);
// Previous page button. Disables previous page button if on first page // Previous page button. Disables previous page button if on first page
pageElement = nopaque.Utils.HTMLToElement( pageElement = Utils.HTMLToElement(
` `
<li class="${this.data.corpus.p.has_prev ? 'waves-effect' : 'disabled'}"> <li class="${this.data.corpus.p.has_prev ? 'waves-effect' : 'disabled'}">
<a class="corpus-analysis-action pagination-trigger" ${this.data.corpus.p.has_prev ? 'data-target="' + this.data.corpus.p.prev_num + '"' : ''}> <a class="corpus-analysis-action pagination-trigger" ${this.data.corpus.p.has_prev ? 'data-target="' + this.data.corpus.p.prev_num + '"' : ''}>
@ -135,7 +131,7 @@ nopaque.corpus_analysis.ReaderExtension = class ReaderExtension {
this.elements.corpusPagination.appendChild(pageElement); this.elements.corpusPagination.appendChild(pageElement);
// First page as number. Hides first page button if on first page // First page as number. Hides first page button if on first page
if (this.data.corpus.p.page > 6) { if (this.data.corpus.p.page > 6) {
pageElement = nopaque.Utils.HTMLToElement( pageElement = Utils.HTMLToElement(
` `
<li class="waves-effect"> <li class="waves-effect">
<a class="corpus-analysis-action pagination-trigger" data-target="1">1</a> <a class="corpus-analysis-action pagination-trigger" data-target="1">1</a>
@ -143,14 +139,14 @@ nopaque.corpus_analysis.ReaderExtension = class ReaderExtension {
` `
); );
this.elements.corpusPagination.appendChild(pageElement); this.elements.corpusPagination.appendChild(pageElement);
pageElement = nopaque.Utils.HTMLToElement("<li style='margin-top: 5px;'>&hellip;</li>"); pageElement = Utils.HTMLToElement("<li style='margin-top: 5px;'>&hellip;</li>");
this.elements.corpusPagination.appendChild(pageElement); this.elements.corpusPagination.appendChild(pageElement);
} }
// render page buttons (5 before and 5 after current page) // render page buttons (5 before and 5 after current page)
for (let i = this.data.corpus.p.page - this.settings.pagination.innerWindow; i <= this.data.corpus.p.page; i++) { for (let i = this.data.corpus.p.page -5; i <= this.data.corpus.p.page; i++) {
if (i <= 0) {continue;} if (i <= 0) {continue;}
pageElement = nopaque.Utils.HTMLToElement( pageElement = Utils.HTMLToElement(
` `
<li class="${i === this.data.corpus.p.page ? 'active' : 'waves-effect'}"> <li class="${i === this.data.corpus.p.page ? 'active' : 'waves-effect'}">
<a class="corpus-analysis-action pagination-trigger" ${i === this.data.corpus.p.page ? '' : 'data-target="' + i + '"'}>${i}</a> <a class="corpus-analysis-action pagination-trigger" ${i === this.data.corpus.p.page ? '' : 'data-target="' + i + '"'}>${i}</a>
@ -159,9 +155,9 @@ nopaque.corpus_analysis.ReaderExtension = class ReaderExtension {
); );
this.elements.corpusPagination.appendChild(pageElement); this.elements.corpusPagination.appendChild(pageElement);
}; };
for (let i = this.data.corpus.p.page +1; i <= this.data.corpus.p.page + this.settings.pagination.innerWindow; i++) { for (let i = this.data.corpus.p.page +1; i <= this.data.corpus.p.page +5; i++) {
if (i > this.data.corpus.p.pages) {break;} if (i > this.data.corpus.p.pages) {break;}
pageElement = nopaque.Utils.HTMLToElement( pageElement = Utils.HTMLToElement(
` `
<li class="${i === this.data.corpus.p.page ? 'active' : 'waves-effect'}"> <li class="${i === this.data.corpus.p.page ? 'active' : 'waves-effect'}">
<a class="corpus-analysis-action pagination-trigger" ${i === this.data.corpus.p.page ? '' : 'data-target="' + i + '"'}>${i}</a> <a class="corpus-analysis-action pagination-trigger" ${i === this.data.corpus.p.page ? '' : 'data-target="' + i + '"'}>${i}</a>
@ -172,9 +168,9 @@ nopaque.corpus_analysis.ReaderExtension = class ReaderExtension {
}; };
// Last page as number. Hides last page button if on last page // Last page as number. Hides last page button if on last page
if (this.data.corpus.p.page < this.data.corpus.p.pages - 6) { if (this.data.corpus.p.page < this.data.corpus.p.pages - 6) {
pageElement = nopaque.Utils.HTMLToElement("<li style='margin-top: 5px;'>&hellip;</li>"); pageElement = Utils.HTMLToElement("<li style='margin-top: 5px;'>&hellip;</li>");
this.elements.corpusPagination.appendChild(pageElement); this.elements.corpusPagination.appendChild(pageElement);
pageElement = nopaque.Utils.HTMLToElement( pageElement = Utils.HTMLToElement(
` `
<li class="waves-effect"> <li class="waves-effect">
<a class="corpus-analysis-action pagination-trigger" data-target="${this.data.corpus.p.pages}">${this.data.corpus.p.pages}</a> <a class="corpus-analysis-action pagination-trigger" data-target="${this.data.corpus.p.pages}">${this.data.corpus.p.pages}</a>
@ -184,7 +180,7 @@ nopaque.corpus_analysis.ReaderExtension = class ReaderExtension {
this.elements.corpusPagination.appendChild(pageElement); this.elements.corpusPagination.appendChild(pageElement);
} }
// Next page button. Disables next page button if on last page // Next page button. Disables next page button if on last page
pageElement = nopaque.Utils.HTMLToElement( pageElement = Utils.HTMLToElement(
` `
<li class="${this.data.corpus.p.has_next ? 'waves-effect' : 'disabled'}"> <li class="${this.data.corpus.p.has_next ? 'waves-effect' : 'disabled'}">
<a class="corpus-analysis-action pagination-trigger" ${this.data.corpus.p.has_next ? 'data-target="' + this.data.corpus.p.next_num + '"' : ''}> <a class="corpus-analysis-action pagination-trigger" ${this.data.corpus.p.has_next ? 'data-target="' + this.data.corpus.p.next_num + '"' : ''}>
@ -195,7 +191,7 @@ nopaque.corpus_analysis.ReaderExtension = class ReaderExtension {
); );
this.elements.corpusPagination.appendChild(pageElement); this.elements.corpusPagination.appendChild(pageElement);
// Last page button. Disables last page button if on last page // Last page button. Disables last page button if on last page
pageElement = nopaque.Utils.HTMLToElement( pageElement = Utils.HTMLToElement(
` `
<li class="${this.data.corpus.p.page === this.data.corpus.p.pages ? 'disabled' : 'waves-effect'}"> <li class="${this.data.corpus.p.page === this.data.corpus.p.pages ? 'disabled' : 'waves-effect'}">
<a class="corpus-analysis-action pagination-trigger" ${this.data.corpus.p.page === this.data.corpus.p.pages ? '' : 'data-target="' + this.data.corpus.p.pages + '"'}> <a class="corpus-analysis-action pagination-trigger" ${this.data.corpus.p.page === this.data.corpus.p.pages ? '' : 'data-target="' + this.data.corpus.p.pages + '"'}>
@ -207,7 +203,7 @@ nopaque.corpus_analysis.ReaderExtension = class ReaderExtension {
this.elements.corpusPagination.appendChild(pageElement); this.elements.corpusPagination.appendChild(pageElement);
for (let paginateTriggerElement of this.elements.corpusPagination.querySelectorAll('.pagination-trigger[data-target]')) { for (let paginateTriggerElement of this.elements.corpusPagination.querySelectorAll('.pagination-trigger[data-target]')) {
paginateTriggerElement.addEventListener('click', (event) => { paginateTriggerElement.addEventListener('click', event => {
event.preventDefault(); event.preventDefault();
let page = parseInt(paginateTriggerElement.dataset.target); let page = parseInt(paginateTriggerElement.dataset.target);
this.page(page); this.page(page);
@ -251,7 +247,7 @@ nopaque.corpus_analysis.ReaderExtension = class ReaderExtension {
this.elements.progress.classList.remove('hide'); this.elements.progress.classList.remove('hide');
this.data.corpus.o.paginate(pageNum, this.settings.perPage) this.data.corpus.o.paginate(pageNum, this.settings.perPage)
.then( .then(
(paginatedCorpus) => { paginatedCorpus => {
this.data.corpus.p = paginatedCorpus; this.data.corpus.p = paginatedCorpus;
this.renderCorpus(); this.renderCorpus();
this.renderCorpusPagination(); this.renderCorpusPagination();

View File

@ -0,0 +1,952 @@
class ConcordanceQueryBuilder {
constructor() {
this.elements = {
counter: 0,
yourQueryContent: [],
queryContent:[],
concordanceQueryBuilder: document.querySelector('#concordance-query-builder'),
concordanceQueryBuilderButton: document.querySelector('#concordance-query-builder-button'),
closeQueryBuilder: document.querySelector('#close-query-builder'),
queryBuilderTutorialModal: document.querySelector('#query-builder-tutorial-modal'),
valueValidator: true,
//#region QueryBuilder Elements
positionalAttrButton: document.querySelector('#positional-attr-button'),
positionalAttrArea: document.querySelector('#positional-attr'),
positionalAttr: document.querySelector('#token-attr'),
structuralAttrButton: document.querySelector('#structural-attr-button'),
structuralAttrArea: document.querySelector('#structural-attr'),
queryContainer: document.querySelector('#query-container'),
buttonPreparer: document.querySelector('#button-preparer'),
yourQuery: document.querySelector('#your-query'),
insertQueryButton: document.querySelector('#insert-query-button'),
queryPreview: document.querySelector('#query-preview'),
tokenQuery: document.querySelector('#token-query'),
tokenBuilderContent: document.querySelector('#token-builder-content'),
tokenSubmitButton: document.querySelector('#token-submit'),
extFormQuery: document.querySelector('#concordance-extension-form-query'),
dropButton: '',
queryBuilderTutorialInfoIcon: document.querySelector('#query-builder-tutorial-info-icon'),
tokenTutorialInfoIcon: document.querySelector('#token-tutorial-info-icon'),
editTokenTutorialInfoIcon: document.querySelector('#edit-options-tutorial-info-icon'),
structuralAttributeTutorialInfoIcon: document.querySelector('#add-structural-attribute-tutorial-info-icon'),
generalOptionsQueryBuilderTutorialInfoIcon: document.querySelector('#general-options-query-builder-tutorial-info-icon'),
//#endregion QueryBuilder Elements
//#region Strucutral Attributes
sentence:document.querySelector('#sentence'),
entity: document.querySelector('#entity'),
textAnnotation: document.querySelector('#text-annotation'),
entityBuilder: document.querySelector('#entity-builder'),
englishEntType: document.querySelector('#english-ent-type'),
germanEntType: document.querySelector('#german-ent-type'),
emptyEntity: document.querySelector('#empty-entity'),
entityAnyType: false,
textAnnotationBuilder: document.querySelector('#text-annotation-builder'),
textAnnotationOptions: document.querySelector('#text-annotation-options'),
textAnnotationInput: document.querySelector('#text-annotation-input'),
textAnnotationSubmit: document.querySelector('#text-annotation-submit'),
noValueMetadataMessage: document.querySelector('#no-value-metadata-message'),
//#endregion Structural Attributes
//#region Token Attributes
tokenQueryFilled: false,
lemma: document.querySelector('#lemma'),
emptyToken: document.querySelector('#empty-token'),
word: document.querySelector('#word'),
lemma: document.querySelector('#lemma'),
pos: document.querySelector('#pos'),
simplePosButton: document.querySelector('#simple-pos-button'),
incidenceModifiers: document.querySelector('[data-target="incidence-modifiers"]'),
or: document.querySelector('#or'),
and: document.querySelector('#and'),
//#region Word and Lemma Elements
wordBuilder: document.querySelector('#word-builder'),
lemmaBuilder: document.querySelector('#lemma-builder'),
inputOptions: document.querySelector('#input-options'),
incidenceModifiersButton: document.querySelector('#incidence-modifiers-button'),
conditionContainer: document.querySelector('#condition-container'),
wordInput: document.querySelector('#word-input'),
lemmaInput: document.querySelector('#lemma-input'),
ignoreCaseCheckbox : document.querySelector('#ignore-case-checkbox'),
ignoreCase: document.querySelector('input[type="checkbox"]'),
wildcardChar: document.querySelector('#wildcard-char'),
optionGroup: document.querySelector('#option-group'),
//#endregion Word and Lemma Elements
//#region posBuilder Elements
englishPosBuilder: document.querySelector('#english-pos-builder'),
englishPos: document.querySelector('#english-pos'),
germanPosBuilder: document.querySelector('#german-pos-builder'),
germanPos: document.querySelector('#german-pos'),
//#endregion posBuilder Elements
//#region simple_posBuilder Elements
simplePosBuilder: document.querySelector('#simplepos-builder'),
simplePos: document.querySelector('#simple-pos'),
//#endregion simple_posBuilder Elements
//#region incidence modifiers
oneOrMore: document.querySelector('#one-or-more'),
zeroOrMore: document.querySelector('#zero-or-more'),
zeroOrOne: document.querySelector('#zero-or-one'),
exactlyN: document.querySelector('#exactlyN'),
betweenNM: document.querySelector('#betweenNM'),
nInput: document.querySelector('#n-input'),
nSubmit: document.querySelector('#n-submit'),
nmInput: document.querySelector('#n-m-input'),
mInput: document.querySelector('#m-input'),
nmSubmit: document.querySelector('#n-m-submit'),
//#endregion incidence modifiers
cancelBool: false,
noValueMessage: document.querySelector('#no-value-message'),
//#endregion Token Attributes
}
this.elements.closeQueryBuilder.addEventListener('click', () => {this.closeQueryBuilderModal(this.elements.concordanceQueryBuilder);});
this.elements.concordanceQueryBuilderButton.addEventListener('click', () => {this.clearAll();});
this.elements.insertQueryButton.addEventListener('click', () => {this.insertQuery();});
this.elements.positionalAttrButton.addEventListener('click', () => {this.showPositionalAttrArea();});
this.elements.structuralAttrButton.addEventListener('click', () => {this.showStructuralAttrArea();});
//#region Structural Attribute Event Listeners
this.elements.sentence.addEventListener('click', () => {this.addSentence();});
this.elements.entity.addEventListener('click', () => {this.addEntity();});
this.elements.textAnnotation.addEventListener('click', () => {this.addTextAnnotation();});
this.elements.englishEntType.addEventListener('change', () => {this.englishEntTypeHandler();});
this.elements.germanEntType.addEventListener('change', () => {this.germanEntTypeHandler();});
this.elements.emptyEntity.addEventListener('click', () => {this.emptyEntityButton();});
this.elements.textAnnotationSubmit.addEventListener('click', () => {this.textAnnotationSubmitHandler();});
//#endregion
//#region Token Attribute Event Listeners
this.elements.queryBuilderTutorialInfoIcon.addEventListener('click', () => {this.tutorialIconHandler('#query-builder-tutorial-start');});
this.elements.tokenTutorialInfoIcon.addEventListener('click', () => {this.tutorialIconHandler('#add-new-token-tutorial');});
this.elements.editTokenTutorialInfoIcon.addEventListener('click', () => {this.tutorialIconHandler('#edit-options-tutorial');});
this.elements.structuralAttributeTutorialInfoIcon.addEventListener('click', () => {this.tutorialIconHandler('#add-structural-attribute-tutorial');});
this.elements.generalOptionsQueryBuilderTutorialInfoIcon.addEventListener('click', () => {this.tutorialIconHandler('#general-options-query-builder');});
this.elements.positionalAttr.addEventListener('change', () => {this.tokenTypeSelector();});
this.elements.tokenSubmitButton.addEventListener('click', () => {this.addTokenToQuery();});
this.elements.ignoreCase.addEventListener('change', () => {this.inputOptionHandler(this.elements.ignoreCase);});
this.elements.wildcardChar.addEventListener('click', () => {this.inputOptionHandler(this.elements.wildcardChar);});
this.elements.optionGroup.addEventListener('click', () => {this.inputOptionHandler(this.elements.optionGroup);});
this.elements.oneOrMore.addEventListener('click', () => {this.incidenceModifiersHandler(this.elements.oneOrMore);});
this.elements.zeroOrMore.addEventListener('click', () => {this.incidenceModifiersHandler(this.elements.zeroOrMore);});
this.elements.zeroOrOne.addEventListener('click', () => {this.incidenceModifiersHandler(this.elements.zeroOrOne);});
this.elements.nSubmit.addEventListener('click', () => {this.nSubmitHandler();});
this.elements.nmSubmit.addEventListener('click', () => {this.nmSubmitHandler();});
this.elements.or.addEventListener('click', () => {this.orHandler();});
this.elements.and.addEventListener('click', () => {this.andHandler();});
//#endregion Token Attribute Event Listeners
}
// ##########################################################################
// #################### General Functions ###################################
// ##########################################################################
//#region General Functions
closeQueryBuilderModal(closeInstance) {
let instance = M.Modal.getInstance(closeInstance);
instance.close();
}
showPositionalAttrArea() {
this.elements.positionalAttrArea.classList.remove('hide');
this.elements.wordBuilder.classList.remove('hide');
this.elements.inputOptions.classList.remove('hide');
this.elements.incidenceModifiersButton.classList.remove('hide');
this.elements.conditionContainer.classList.remove('hide');
this.elements.ignoreCaseCheckbox.classList.remove('hide');
this.elements.structuralAttrArea.classList.add('hide');
this.elements.lemmaBuilder.classList.add('hide');
this.elements.englishPosBuilder.classList.add('hide');
this.elements.germanPosBuilder.classList.add('hide');
this.elements.simplePosBuilder.classList.add('hide');
this.elements.tokenQueryFilled = false;
window.location.href = '#token-builder-content';
// Resets materialize select field to default value
let SelectInstance = M.FormSelect.getInstance(this.elements.positionalAttr);
SelectInstance.input.value = 'word';
this.elements.positionalAttr.value = 'word';
}
showStructuralAttrArea() {
this.elements.positionalAttrArea.classList.add('hide');
this.elements.structuralAttrArea.classList.remove('hide');
}
queryChipFactory(dataType, prettyQueryText, queryText) {
window.location.href = '#query-container';
queryText = Utils.escape(queryText);
prettyQueryText = Utils.escape(prettyQueryText);
let queryChipElement = Utils.HTMLToElement(
`
<span class="chip query-component" data-type="${dataType}" data-query="${queryText}" draggable="true">
${prettyQueryText}
<i class="material-icons close">close</i>
</span>
`
);
queryChipElement.addEventListener('click', () => {this.deleteAttr(queryChipElement);});
queryChipElement.addEventListener('dragstart', (event) => {
// selects all nodes without target class
let queryChips = this.elements.yourQuery.querySelectorAll('.query-component');
// Adds a target chip in front of all draggable childnodes
setTimeout(() => {
let targetChipElement = Utils.HTMLToElement('<span class="chip drop-target">Drop here</span>');
for (let element of queryChips) {
if (element === queryChipElement.nextSibling) {continue;}
let targetChipClone = targetChipElement.cloneNode(true);
if (element === queryChipElement) {
// If the dragged element is not at the very end, a target chip is also inserted at the end
if (queryChips[queryChips.length - 1] !== element) {
queryChips[queryChips.length - 1].insertAdjacentElement('afterend', targetChipClone);
}
} else {
element.insertAdjacentElement('beforebegin', targetChipClone);
}
targetChipClone.addEventListener('dragover', (event) => {
event.preventDefault();
});
targetChipClone.addEventListener('dragenter', (event) => {
event.preventDefault();
event.target.style.borderStyle = 'solid dotted';
});
targetChipClone.addEventListener('dragleave', (event) => {
event.preventDefault();
event.target.style.borderStyle = 'hidden';
});
targetChipClone.addEventListener('drop', (event) => {
let dropzone = event.target;
dropzone.parentElement.replaceChild(queryChipElement, dropzone);
this.queryPreviewBuilder();
});
}
}, 0);
});
queryChipElement.addEventListener('dragend', (event) => {
let targets = document.querySelectorAll('.drop-target');
for (let target of targets) {
target.remove();
}
});
// Ensures that metadata is always at the end of the query:
if (this.elements.yourQuery.lastChild === null || this.elements.yourQuery.lastChild.dataset.type !== 'text-annotation') {
this.elements.yourQuery.appendChild(queryChipElement);
} else if (this.elements.yourQuery.lastChild.dataset.type === 'text-annotation') {
this.elements.yourQuery.insertBefore(queryChipElement, this.elements.yourQuery.lastChild);
}
this.elements.queryContainer.classList.remove('hide');
this.queryPreviewBuilder();
// Shows a hint about possible functions for editing the query at the first added element in the query
if (this.elements.yourQuery.childNodes.length === 1) {
app.flash('You can edit your query by deleting individual elements or moving them via drag and drop.');
}
}
queryPreviewBuilder() {
this.elements.yourQueryContent = [];
for (let element of this.elements.yourQuery.childNodes) {
let queryElement = decodeURI(element.dataset.query);
queryElement = Utils.escape(queryElement);
if (queryElement !== 'undefined') {
this.elements.yourQueryContent.push(queryElement);
}
}
let queryString = this.elements.yourQueryContent.join(' ');
queryString += ';';
this.elements.queryPreview.innerHTML = queryString;
}
deleteAttr(attr) {
this.elements.yourQuery.removeChild(attr);
if (attr.dataset.type === "start-sentence") {
this.elements.sentence.innerHTML = 'Sentence';
} else if (attr.dataset.type === "start-entity" || attr.dataset.type === "start-empty-entity") {
this.elements.entity.innerHTML = 'Entity';
}
this.elements.counter -= 1;
if (this.elements.counter === 0) {
this.elements.queryContainer.classList.add('hide');
}
this.queryPreviewBuilder();
}
insertQuery() {
this.elements.yourQueryContent = [];
this.validateValue();
if (this.elements.valueValidator === true) {
for (let element of this.elements.yourQuery.childNodes) {
let queryElement = decodeURI(element.dataset.query);
if (queryElement !== 'undefined') {
this.elements.yourQueryContent.push(queryElement);
}
}
let queryString = this.elements.yourQueryContent.join(' ');
queryString += ';';
this.elements.concordanceQueryBuilder.classList.add('modal-close');
this.elements.extFormQuery.value = queryString;
}
}
validateValue() {
this.elements.valueValidator = true;
let sentenceCounter = 0;
let sentenceEndCounter = 0;
let entityCounter = 0;
let entityEndCounter = 0;
for (let element of this.elements.yourQuery.childNodes) {
if (element.dataset.type === 'start-sentence') {
sentenceCounter += 1;
}else if (element.dataset.type === 'end-sentence') {
sentenceEndCounter += 1;
}else if (element.dataset.type === 'start-entity' || element.dataset.type === 'start-empty-entity') {
entityCounter += 1;
}else if (element.dataset.type === 'end-entity') {
entityEndCounter += 1;
}
}
// Checks if the same number of opening and closing tags (entity and sentence) are present. Depending on what is missing, the corresponding error message is ejected
if (sentenceCounter > sentenceEndCounter) {
app.flash('Please add the closing sentence tag', 'error');
this.elements.valueValidator = false;
} else if (sentenceCounter < sentenceEndCounter) {
app.flash('Please remove the closing sentence tag', 'error');
this.elements.valueValidator = false;
}
if (entityCounter > entityEndCounter) {
app.flash('Please add the closing entity tag', 'error');
this.elements.valueValidator = false;
} else if (entityCounter < entityEndCounter) {
app.flash('Please remove the closing entity tag', 'error');
this.elements.valueValidator = false;
}
}
clearAll() {
// Everything is reset.
let instance = M.Tooltip.getInstance(this.elements.queryBuilderTutorialInfoIcon);
this.hideEverything();
this.elements.counter = 0;
this.elements.concordanceQueryBuilder.classList.remove('modal-close');
this.elements.positionalAttrArea.classList.add('hide');
this.elements.structuralAttrArea.classList.add('hide');
this.elements.yourQuery.innerHTML = '';
this.elements.queryContainer.classList.add('hide');
this.elements.entity.innerHTML = 'Entity';
this.elements.sentence.innerHTML = 'Sentence';
// If the Modal is open after 5 seconds for 5 seconds (with 'instance'), a message is displayed indicating that further information can be obtained via the question mark icon
instance.tooltipEl.style.background = '#98ACD2';
instance.tooltipEl.style.borderTop = 'solid 4px #0064A3';
instance.tooltipEl.style.padding = '10px';
instance.tooltipEl.style.color = 'black';
setTimeout(() => {
let modalInstance = M.Modal.getInstance(this.elements.concordanceQueryBuilder);
if (modalInstance.isOpen) {
instance.open();
setTimeout(() => {
instance.close();
}, 5000);
}
}, 5000);
}
tutorialIconHandler(id) {
setTimeout(() => {
window.location.href= id;
}, 0);
}
//#endregion General Functions
// ##########################################################################
// ############## Token Attribute Builder Functions #########################
// ##########################################################################
//#region Token Attribute Builder Functions
//#region General functions of the Token Builder
tokenTypeSelector() {
this.hideEverything();
switch (this.elements.positionalAttr.value) {
case 'word':
this.wordBuilder();
break;
case 'lemma':
this.lemmaBuilder();
break;
case 'english-pos':
this.englishPosHandler();
break;
case 'german-pos':
this.germanPosHandler();
break;
case 'simple-pos-button':
this.simplePosBuilder();
break;
case 'empty-token':
this.emptyTokenHandler();
break;
default:
this.wordBuilder();
break;
}
}
hideEverything() {
this.elements.wordBuilder.classList.add('hide');
this.elements.lemmaBuilder.classList.add('hide');
this.elements.ignoreCaseCheckbox.classList.add('hide');
this.elements.inputOptions.classList.add('hide');
this.elements.incidenceModifiersButton.classList.add('hide');
this.elements.conditionContainer.classList.add('hide');
this.elements.englishPosBuilder.classList.add('hide');
this.elements.germanPosBuilder.classList.add('hide');
this.elements.simplePosBuilder.classList.add('hide');
this.elements.entityBuilder.classList.add('hide');
this.elements.textAnnotationBuilder.classList.add('hide');
}
tokenChipFactory(prettyQueryText, tokenText) {
tokenText = encodeURI(tokenText);
let builderElement;
let queryChipElement;
builderElement = document.createElement('div');
builderElement.innerHTML = `
<div class='chip col s2 l2' style='margin-top:20px;' data-tokentext='${tokenText}'>
${prettyQueryText}
<i class='material-icons close'>close</i>
</div>`;
queryChipElement = builderElement.firstElementChild;
queryChipElement.addEventListener('click', () => {this.deleteTokenAttr(queryChipElement);});
this.elements.tokenQuery.appendChild(queryChipElement);
}
deleteTokenAttr(attr) {
if (this.elements.tokenQuery.childNodes.length < 2) {
this.elements.tokenQuery.removeChild(attr);
this.wordBuilder();
} else {
this.elements.tokenQuery.removeChild(attr);
}
}
addTokenToQuery() {
let c;
let tokenQueryContent = ''; //for ButtonFactory(prettyQueryText)
let tokenQueryText = ''; //for ButtonFactory(queryText)
this.elements.cancelBool = false;
let tokenIsEmpty = false;
if (this.elements.ignoreCase.checked) {
c = ' %c';
} else {
c = '';
}
for (let element of this.elements.tokenQuery.childNodes) {
tokenQueryContent += ' ' + element.firstChild.data + ' ';
tokenQueryText += decodeURI(element.dataset.tokentext);
if (element.innerText.indexOf('empty token') !== -1) {
tokenIsEmpty = true;
}
}
if (this.elements.tokenQueryFilled === false) {
switch (this.elements.positionalAttr.value) {
case 'word':
if (this.elements.wordInput.value === '') {
this.disableTokenSubmit();
} else {
tokenQueryContent += `word=${this.elements.wordInput.value}${c}`;
tokenQueryText += `word="${this.elements.wordInput.value}"${c}`;
this.elements.wordInput.value = '';
}
break;
case 'lemma':
if (this.elements.lemmaInput.value === '') {
this.disableTokenSubmit();
} else {
tokenQueryContent += `lemma=${this.elements.lemmaInput.value}${c}`;
tokenQueryText += `lemma="${this.elements.lemmaInput.value}"${c}`;
this.elements.lemmaInput.value = '';
}
break;
case 'english-pos':
if (this.elements.englishPos.value === 'default') {
this.disableTokenSubmit();
} else {
tokenQueryContent += `pos=${this.elements.englishPos.value}`;
tokenQueryText += `pos="${this.elements.englishPos.value}"`;
this.elements.englishPos.value = '';
}
break;
case 'german-pos':
if (this.elements.germanPos.value === 'default') {
this.disableTokenSubmit();
} else {
tokenQueryContent += `pos=${this.elements.germanPos.value}`;
tokenQueryText += `pos="${this.elements.germanPos.value}"`;
this.elements.germanPos.value = '';
}
break;
case 'simple-pos-button':
if (this.elements.simplePos.value === 'default') {
this.disableTokenSubmit();
} else {
tokenQueryContent += `simple_pos=${this.elements.simplePos.value}`;
tokenQueryText += `simple_pos="${this.elements.simplePos.value}"`;
this.elements.simplePos.value = '';
}
break;
default:
this.wordBuilder();
break;
}
}
// cancelBool looks in disableTokenSubmit() whether a value is passed. If the input fields/dropdowns are empty (cancelBool === true), no token is added.
if (this.elements.cancelBool === false) {
// Square brackets are added only if it is not an empty token (where they are already present).
if (tokenIsEmpty === false) {
tokenQueryText = '[' + tokenQueryText + ']';
}
this.queryChipFactory('token', tokenQueryContent, tokenQueryText);
this.hideEverything();
this.elements.positionalAttrArea.classList.add('hide');
this.elements.tokenQuery.innerHTML = '';
}
}
disableTokenSubmit() {
this.elements.cancelBool = true;
this.elements.tokenSubmitButton.classList.add('red');
this.elements.noValueMessage.classList.remove('hide');
setTimeout(() => {
this.elements.tokenSubmitButton.classList.remove('red');
}, 500);
setTimeout(() => {
this.elements.noValueMessage.classList.add('hide');
}, 3000);
}
//#endregion General functions of the Token Builder
//#region Dropdown Select Handler
wordBuilder() {
this.hideEverything();
this.elements.wordInput.value = '';
this.elements.wordBuilder.classList.remove('hide');
this.elements.inputOptions.classList.remove('hide');
this.elements.incidenceModifiersButton.classList.remove('hide');
this.elements.conditionContainer.classList.remove('hide');
this.elements.ignoreCaseCheckbox.classList.remove('hide');
// Resets materialize select field to default value
let SelectInstance = M.FormSelect.getInstance(this.elements.positionalAttr);
SelectInstance.input.value = 'word';
this.elements.positionalAttr.value = 'word';
}
lemmaBuilder() {
this.hideEverything();
this.elements.lemmaBuilder.classList.remove('hide');
this.elements.inputOptions.classList.remove('hide');
this.elements.incidenceModifiersButton.classList.remove('hide');
this.elements.conditionContainer.classList.remove('hide');
this.elements.ignoreCaseCheckbox.classList.remove('hide');
}
englishPosHandler() {
this.hideEverything();
this.elements.englishPosBuilder.classList.remove('hide');
this.elements.incidenceModifiersButton.classList.remove('hide');
this.elements.conditionContainer.classList.remove('hide');
// Resets materialize select dropdown
let selectInstance = M.FormSelect.getInstance(this.elements.englishPos);
selectInstance.input.value = 'English pos tagset';
this.elements.englishPos.value = 'default';
}
germanPosHandler() {
this.hideEverything();
this.elements.germanPosBuilder.classList.remove('hide');
this.elements.incidenceModifiersButton.classList.remove('hide');
this.elements.conditionContainer.classList.remove('hide');
// Resets materialize select dropdown
let selectInstance = M.FormSelect.getInstance(this.elements.germanPos);
selectInstance.input.value = 'German pos tagset';
this.elements.germanPos.value = 'default';
}
simplePosBuilder() {
this.hideEverything();
this.elements.simplePosBuilder.classList.remove('hide');
this.elements.incidenceModifiersButton.classList.remove('hide');
this.elements.conditionContainer.classList.remove('hide');
this.elements.simplePos.selectedIndex = 0;
// Resets materialize select dropdown
let selectInstance = M.FormSelect.getInstance(this.elements.simplePos);
selectInstance.input.value = 'simple_pos tagset';
this.elements.simplePos.value = 'default';
}
emptyTokenHandler() {
this.tokenChipFactory('empty token', '[]');
this.elements.tokenQueryFilled = true;
this.hideEverything();
this.elements.incidenceModifiersButton.classList.remove('hide');
}
//#endregion Dropdown Select Handler
//#region Options to edit your token - Wildcard Charakter, Option Group, Incidence Modifiers, Ignore Case, 'and', 'or'
inputOptionHandler(elem) {
let input;
if (this.elements.wordBuilder.classList.contains('hide') === false) {
input = this.elements.wordInput;
} else {
input = this.elements.lemmaInput;
}
if (elem === this.elements.optionGroup) {
input.value += '( option1 | option2 )';
let firstIndex = input.value.indexOf('option1');
let lastIndex = firstIndex + 'option1'.length;
input.focus();
input.setSelectionRange(firstIndex, lastIndex);
} else if (elem === this.elements.wildcardChar) {
input.value += '.';
}
}
nSubmitHandler() {
let instance = M.Modal.getInstance(this.elements.exactlyN);
instance.close();
switch (this.elements.positionalAttr.value) {
case 'word':
this.elements.wordInput.value += ' {' + this.elements.nInput.value + '}';
break;
case 'lemma':
this.elements.lemmaInput.value += ' {' + this.elements.nInput.value + '}';
break;
case 'english-pos':
this.elements.tokenQueryFilled = true;
this.tokenChipFactory(`pos=${this.elements.englishPos.value}`, `pos="${this.elements.englishPos.value}"`);
this.tokenChipFactory('{' + this.elements.nInput.value + '}', '{' + this.elements.nInput.value + '}');
this.elements.englishPosBuilder.classList.add('hide');
this.elements.incidenceModifiersButton.classList.add('hide');
break;
case 'german-pos':
this.elements.tokenQueryFilled = true;
this.tokenChipFactory(`pos=${this.elements.germanPos.value}`, `pos="${this.elements.germanPos.value}"`);
this.tokenChipFactory('{' + this.elements.nInput.value + '}', '{' + this.elements.nInput.value + '}');
this.elements.germanPosBuilder.classList.add('hide');
this.elements.incidenceModifiersButton.classList.add('hide');
break;
case 'simple-pos-button':
this.elements.tokenQueryFilled = true;
this.tokenChipFactory(`simple_pos=${this.elements.simplePos.value}`, `simple_pos="${this.elements.simplePos.value}"`);
this.tokenChipFactory('{' + this.elements.nInput.value + '}', '{' + this.elements.nInput.value + '}');
this.elements.simplePosBuilder.classList.add('hide');
this.elements.incidenceModifiersButton.classList.add('hide');
break;
case 'empty-token':
this.tokenChipFactory('{' + this.elements.nInput.value + '}', '{' + this.elements.nInput.value + '}');
break;
default:
break;
}
}
nmSubmitHandler() {
let instance = M.Modal.getInstance(this.elements.betweenNM);
instance.close();
switch (this.elements.positionalAttr.value) {
case 'word':
this.elements.wordInput.value += `{${this.elements.nmInput.value}, ${this.elements.mInput.value}}`;
break;
case 'lemma':
this.elements.lemmaInput.value += `{${this.elements.nmInput.value}, ${this.elements.mInput.value}}`;
break;
case 'english-pos':
this.elements.tokenQueryFilled = true;
this.tokenChipFactory(`pos=${this.elements.englishPos.value}`, `pos="${this.elements.englishPos.value}"`);
this.tokenChipFactory(`{${this.elements.nmInput.value}, ${this.elements.mInput.value}}`, `{${this.elements.nmInput.value}, ${this.elements.mInput.value}}`);
this.elements.englishPosBuilder.classList.add('hide');
this.elements.incidenceModifiersButton.classList.add('hide');
break;
case 'german-pos':
this.elements.tokenQueryFilled = true;
this.tokenChipFactory(`pos=${this.elements.germanPos.value}`, `pos="${this.elements.germanPos.value}"`);
this.tokenChipFactory(`{${this.elements.nmInput.value}, ${this.elements.mInput.value}}`, `{${this.elements.nmInput.value}, ${this.elements.mInput.value}}`);
this.elements.germanPosBuilder.classList.add('hide');
this.elements.incidenceModifiersButton.classList.add('hide');
break;
case 'simple-pos-button':
this.elements.tokenQueryFilled = true;
this.tokenChipFactory(`simple_pos=${this.elements.simplePos.value}`, `simple_pos="${this.elements.simplePos.value}"`);
this.tokenChipFactory(`{${this.elements.nmInput.value}, ${this.elements.mInput.value}}`, `{${this.elements.nmInput.value}, ${this.elements.mInput.value}}`);
this.elements.simplePosBuilder.classList.add('hide');
this.elements.incidenceModifiersButton.classList.add('hide');
break;
case 'empty-token':
this.tokenChipFactory(`{${this.elements.nmInput.value}, ${this.elements.mInput.value}}`, `{${this.elements.nmInput.value}, ${this.elements.mInput.value}}`);
break;
default:
break;
}
}
incidenceModifiersHandler(elem) {
// For word and lemma, the incidence modifiers are inserted in the input field. For the others, one or two chips are created which contain the respective value of the token and the incidence modifier.
if (this.elements.positionalAttr.value === 'empty-token') {
this.tokenChipFactory(elem.innerText, elem.dataset.token);
} else if (this.elements.positionalAttr.value === 'english-pos') {
this.tokenChipFactory(`pos=${this.elements.englishPos.value}`, `pos="${this.elements.englishPos.value}"`);
this.tokenChipFactory(elem.innerText, elem.dataset.token);
this.elements.englishPosBuilder.classList.add('hide');
this.elements.incidenceModifiersButton.classList.add('hide');
this.elements.tokenQueryFilled = true;
} else if (this.elements.positionalAttr.value === 'german-pos') {
this.tokenChipFactory(`pos=${this.elements.germanPos.value}`, `pos="${this.elements.germanPos.value}"`);
this.tokenChipFactory(elem.innerText, elem.dataset.token);
this.elements.germanPosBuilder.classList.add('hide');
this.elements.incidenceModifiersButton.classList.add('hide');
this.elements.tokenQueryFilled = true;
} else if (this.elements.positionalAttr.value === 'simple-pos-button') {
this.tokenChipFactory(`simple_pos=${this.elements.simplePos.value}`, `simple_pos="${this.elements.simplePos.value}"`);
this.tokenChipFactory(elem.innerText, elem.dataset.token);
this.elements.simplePosBuilder.classList.add('hide');
this.elements.incidenceModifiersButton.classList.add('hide');
this.elements.tokenQueryFilled = true;
} else {
let input;
if (this.elements.wordBuilder.classList.contains('hide') === false) {
input = this.elements.wordInput;
} else {
input = this.elements.lemmaInput;
}
input.value += ' ' + elem.dataset.token;
}
}
orHandler() {
this.conditionHandler('or', ' | ');
}
andHandler() {
this.conditionHandler('and', ' & ');
}
conditionHandler(conditionText, conditionQueryContent) {
this.hideEverything();
let tokenQueryContent;
let tokenQueryText;
let c;
if (this.elements.ignoreCase.checked) {
c = ' %c';
} else {
c = '';
}
switch (this.elements.positionalAttr.value) {
case 'word':
tokenQueryContent = `word=${this.elements.wordInput.value}${c}`;
tokenQueryText = `word="${this.elements.wordInput.value}"${c}`;
this.elements.wordInput.value = '';
break;
case 'lemma':
tokenQueryContent = `lemma=${this.elements.lemmaInput.value}${c}`;
tokenQueryText = `lemma="${this.elements.lemmaInput.value}"${c}`;
this.elements.lemmaInput.value = '';
break;
case 'english-pos':
tokenQueryContent = `pos=${this.elements.englishPos.value}`;
tokenQueryText = `pos="${this.elements.englishPos.value}"`;
this.elements.englishPos.value = '';
break;
case 'german-pos':
tokenQueryContent = `pos=${this.elements.germanPos.value}`;
tokenQueryText = `pos="${this.elements.germanPos.value}"`;
this.elements.germanPos.value = '';
break;
case 'simple-pos-button':
tokenQueryContent = `simple_pos=${this.elements.simplePos.value}`;
tokenQueryText = `simple_pos="${this.elements.simplePos.value}"`;
this.elements.simplePos.value = '';
break;
default:
this.wordBuilder();
break;
}
this.tokenChipFactory(tokenQueryContent, tokenQueryText);
this.tokenChipFactory(conditionText, conditionQueryContent);
this.wordBuilder();
}
//#endregion Options to edit your token - Wildcard Charakter, Option Group, Incidence Modifiers, Ignore Case, 'and', 'or'
//#endregion Token Attribute Builder Functions
// ##########################################################################
// ############ Structural Attribute Builder Functions ######################
// ##########################################################################
//#region Structural Attribute Builder Functions
addSentence() {
this.hideEverything();
if (this.elements.sentence.text === 'End Sentence') {
this.queryChipFactory('end-sentence', 'Sentence End', '</s>');
this.elements.sentence.innerHTML = 'Sentence';
} else {
this.queryChipFactory('start-sentence', 'Sentence Start', '<s>');
this.elements.queryContent.push('sentence');
this.elements.sentence.innerHTML = 'End Sentence';
}
}
addEntity() {
if (this.elements.entity.text === 'End Entity') {
let queryText;
if (this.elements.entityAnyType === false) {
queryText = '</ent_type>';
} else {
queryText = '</ent>';
}
this.queryChipFactory('end-entity', 'Entity End', queryText);
this.elements.entity.innerHTML = 'Entity';
} else {
this.hideEverything();
this.elements.entityBuilder.classList.remove('hide');
window.location.href = '#entity-builder';
}
}
englishEntTypeHandler() {
this.queryChipFactory('start-entity', 'Entity Type=' + this.elements.englishEntType.value, '<ent_type="' + this.elements.englishEntType.value + '">');
this.elements.entity.innerHTML = 'End Entity';
this.hideEverything();
this.elements.entityAnyType = false;
// Resets materialize select dropdown
let SelectInstance = M.FormSelect.getInstance(this.elements.englishEntType);
SelectInstance.input.value = 'English ent_type';
this.elements.englishEntType.value = 'default';
}
germanEntTypeHandler() {
this.queryChipFactory('start-entity', 'Entity Type=' + this.elements.germanEntType.value, '<ent_type="' + this.elements.germanEntType.value + '">');
this.elements.entity.innerHTML = 'End Entity';
this.hideEverything();
this.elements.entityAnyType = false;
// Resets materialize select dropdown
let SelectInstance = M.FormSelect.getInstance(this.elements.germanEntType);
SelectInstance.input.value = 'German ent_type';
this.elements.germanEntType.value = 'default';
}
emptyEntityButton() {
this.queryChipFactory('start-empty-entity', 'Entity Start', '<ent>');
this.elements.entity.innerHTML = 'End Entity';
this.hideEverything();
this.elements.entityAnyType = true;
}
addTextAnnotation() {
this.hideEverything();
this.elements.textAnnotationBuilder.classList.remove('hide');
window.location.href = '#text-annotation-builder';
// Resets materialize select dropdown
let SelectInstance = M.FormSelect.getInstance(this.elements.textAnnotationOptions);
SelectInstance.input.value = 'address';
this.elements.textAnnotationOptions.value = 'address';
this.elements.textAnnotationInput.value= '';
}
textAnnotationSubmitHandler() {
if (this.elements.textAnnotationInput.value === '') {
this.elements.textAnnotationSubmit.classList.add('red');
this.elements.noValueMetadataMessage.classList.remove('hide');
setTimeout(() => {
this.elements.textAnnotationSubmit.classList.remove('red');
}, 500);
setTimeout(() => {
this.elements.noValueMetadataMessage.classList.add('hide');
}, 3000);
} else {
let queryText = `:: match.text_${this.elements.textAnnotationOptions.value}="${this.elements.textAnnotationInput.value}"`;
this.queryChipFactory('text-annotation', `${this.elements.textAnnotationOptions.value}=${this.elements.textAnnotationInput.value}`, queryText);
this.hideEverything();
}
}
//#endregion Structural Attribute Builder Functions
}

View File

@ -0,0 +1,18 @@
class CreateContributionForm extends Form {
static autoInit() {
let createContributionFormElements = document.querySelectorAll('.create-contribution-form');
for (let createContributionFormElement of createContributionFormElements) {
new CreateContributionForm(createContributionFormElement);
}
}
constructor(formElement) {
super(formElement);
this.addEventListener('requestLoad', (event) => {
if (event.target.status === 201) {
window.location.href = event.target.getResponseHeader('Location');
}
});
}
}

View File

@ -0,0 +1,18 @@
class CreateCorpusFileForm extends Form {
static autoInit() {
let createCorpusFileFormElements = document.querySelectorAll('.create-corpus-file-form');
for (let createCorpusFileFormElement of createCorpusFileFormElements) {
new CreateCorpusFileForm(createCorpusFileFormElement);
}
}
constructor(formElement) {
super(formElement);
this.addEventListener('requestLoad', (event) => {
if (event.target.status === 201) {
window.location.href = event.target.getResponseHeader('Location');
}
});
}
}

View File

@ -1,5 +1,10 @@
nopaque.forms.CreateJobForm = class CreateJobForm extends nopaque.forms.BaseForm { class CreateJobForm extends Form {
static htmlClass = 'create-job-form'; static autoInit() {
let createJobFormElements = document.querySelectorAll('.create-job-form');
for (let createJobFormElement of createJobFormElements) {
new CreateJobForm(createJobFormElement);
}
}
constructor(formElement) { constructor(formElement) {
super(formElement); super(formElement);
@ -17,4 +22,4 @@ nopaque.forms.CreateJobForm = class CreateJobForm extends nopaque.forms.BaseForm
} }
}); });
} }
}; }

View File

@ -1,5 +1,9 @@
nopaque.forms.BaseForm = class BaseForm { class Form {
static htmlClass; static autoInit() {
CreateContributionForm.autoInit();
CreateCorpusFileForm.autoInit();
CreateJobForm.autoInit();
}
constructor(formElement) { constructor(formElement) {
this.formElement = formElement; this.formElement = formElement;
@ -28,7 +32,7 @@ nopaque.forms.BaseForm = class BaseForm {
submit(event) { submit(event) {
let request = new XMLHttpRequest(); let request = new XMLHttpRequest();
let modalElement = nopaque.Utils.HTMLToElement( let modalElement = Utils.HTMLToElement(
` `
<div class="modal"> <div class="modal">
<div class="modal-content"> <div class="modal-content">
@ -67,7 +71,7 @@ nopaque.forms.BaseForm = class BaseForm {
for (let selectElement of this.formElement.querySelectorAll('select')) { for (let selectElement of this.formElement.querySelectorAll('select')) {
if (selectElement.value === '') { if (selectElement.value === '') {
let inputFieldElement = selectElement.closest('.input-field'); let inputFieldElement = selectElement.closest('.input-field');
let errorHelperTextElement = nopaque.Utils.HTMLToElement( let errorHelperTextElement = Utils.HTMLToElement(
'<span class="helper-text error-color-text" data-helper-text-type="error">Please select an option.</span>' '<span class="helper-text error-color-text" data-helper-text-type="error">Please select an option.</span>'
); );
inputFieldElement.appendChild(errorHelperTextElement); inputFieldElement.appendChild(errorHelperTextElement);
@ -93,7 +97,7 @@ nopaque.forms.BaseForm = class BaseForm {
.querySelector(`input[name$="${inputName}"], select[name$="${inputName}"]`) .querySelector(`input[name$="${inputName}"], select[name$="${inputName}"]`)
.closest('.input-field'); .closest('.input-field');
for (let inputError of inputErrors) { for (let inputError of inputErrors) {
let errorHelperTextElement = nopaque.Utils.HTMLToElement( let errorHelperTextElement = Utils.HTMLToElement(
`<span class="helper-text error-color-text" data-helper-type="error">${inputError}</span>` `<span class="helper-text error-color-text" data-helper-type="error">${inputError}</span>`
); );
inputFieldElement.appendChild(errorHelperTextElement); inputFieldElement.appendChild(errorHelperTextElement);
@ -135,4 +139,4 @@ nopaque.forms.BaseForm = class BaseForm {
} }
} }
} }
}; }

View File

@ -1,6 +1,6 @@
nopaque.requests = {}; let Requests = {};
nopaque.requests.JSONfetch = (input, init={}) => { Requests.JSONfetch = (input, init={}) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let fixedInit = {}; let fixedInit = {};
fixedInit.headers = {}; fixedInit.headers = {};
@ -8,7 +8,7 @@ nopaque.requests.JSONfetch = (input, init={}) => {
if (init.hasOwnProperty('body')) { if (init.hasOwnProperty('body')) {
fixedInit.headers['Content-Type'] = 'application/json'; fixedInit.headers['Content-Type'] = 'application/json';
} }
fetch(input, nopaque.Utils.mergeObjectsDeep(init, fixedInit)) fetch(input, Utils.mergeObjectsDeep(init, fixedInit))
.then( .then(
(response) => { (response) => {
if (response.ok) { if (response.ok) {
@ -22,11 +22,9 @@ nopaque.requests.JSONfetch = (input, init={}) => {
response.json() response.json()
.then( .then(
(json) => { (json) => {
let message = json.message; let message = json.message || json;
let category = json.category || 'message'; let category = json.category || 'message';
if (message) { app.flash(message, category);
app.flash(message, category);
}
}, },
(error) => { (error) => {
app.flash(`[${response.status}]: ${response.statusText}`, 'error'); app.flash(`[${response.status}]: ${response.statusText}`, 'error');

View File

@ -0,0 +1,20 @@
/*****************************************************************************
* Admin *
* Fetch requests for /admin routes *
*****************************************************************************/
Requests.admin = {};
Requests.admin.users = {};
Requests.admin.users.entity = {};
Requests.admin.users.entity.confirmed = {};
Requests.admin.users.entity.confirmed.update = (userId, value) => {
let input = `/admin/users/${userId}/confirmed`;
let init = {
method: 'PUT',
body: JSON.stringify(value)
};
return Requests.JSONfetch(input, init);
};

View File

@ -0,0 +1,5 @@
/*****************************************************************************
* Contributions *
* Fetch requests for /contributions routes *
*****************************************************************************/
Requests.contributions = {};

View File

@ -0,0 +1,26 @@
/*****************************************************************************
* SpaCy NLP Pipeline Models *
* Fetch requests for /contributions/spacy-nlp-pipeline-models routes *
*****************************************************************************/
Requests.contributions.spacy_nlp_pipeline_models = {};
Requests.contributions.spacy_nlp_pipeline_models.entity = {};
Requests.contributions.spacy_nlp_pipeline_models.entity.delete = (spacyNlpPipelineModelId) => {
let input = `/contributions/spacy-nlp-pipeline-models/${spacyNlpPipelineModelId}`;
let init = {
method: 'DELETE'
};
return Requests.JSONfetch(input, init);
};
Requests.contributions.spacy_nlp_pipeline_models.entity.isPublic = {};
Requests.contributions.spacy_nlp_pipeline_models.entity.isPublic.update = (spacyNlpPipelineModelId, value) => {
let input = `/contributions/spacy-nlp-pipeline-models/${spacyNlpPipelineModelId}/is_public`;
let init = {
method: 'PUT',
body: JSON.stringify(value)
};
return Requests.JSONfetch(input, init);
};

View File

@ -0,0 +1,26 @@
/*****************************************************************************
* Tesseract OCR Pipeline Models *
* Fetch requests for /contributions/tesseract-ocr-pipeline-models routes *
*****************************************************************************/
Requests.contributions.tesseract_ocr_pipeline_models = {};
Requests.contributions.tesseract_ocr_pipeline_models.entity = {};
Requests.contributions.tesseract_ocr_pipeline_models.entity.delete = (tesseractOcrPipelineModelId) => {
let input = `/contributions/tesseract-ocr-pipeline-models/${tesseractOcrPipelineModelId}`;
let init = {
method: 'DELETE'
};
return Requests.JSONfetch(input, init);
};
Requests.contributions.tesseract_ocr_pipeline_models.entity.isPublic = {};
Requests.contributions.tesseract_ocr_pipeline_models.entity.isPublic.update = (tesseractOcrPipelineModelId, value) => {
let input = `/contributions/tesseract-ocr-pipeline-models/${tesseractOcrPipelineModelId}/is_public`;
let init = {
method: 'PUT',
body: JSON.stringify(value)
};
return Requests.JSONfetch(input, init);
};

View File

@ -0,0 +1,46 @@
/*****************************************************************************
* Corpora *
* Fetch requests for /corpora routes *
*****************************************************************************/
Requests.corpora = {};
Requests.corpora.entity = {};
Requests.corpora.entity.delete = (corpusId) => {
let input = `/corpora/${corpusId}`;
let init = {
method: 'DELETE'
};
return Requests.JSONfetch(input, init);
};
Requests.corpora.entity.build = (corpusId) => {
let input = `/corpora/${corpusId}/build`;
let init = {
method: 'POST',
};
return Requests.JSONfetch(input, init);
};
Requests.corpora.entity.generateShareLink = (corpusId, role, expiration) => {
let input = `/corpora/${corpusId}/generate-share-link`;
let init = {
method: 'POST',
body: JSON.stringify({role: role, expiration: expiration})
};
return Requests.JSONfetch(input, init);
};
Requests.corpora.entity.isPublic = {};
Requests.corpora.entity.isPublic.update = (corpusId, isPublic) => {
let input = `/corpora/${corpusId}/is_public`;
let init = {
method: 'PUT',
body: JSON.stringify(isPublic)
};
return Requests.JSONfetch(input, init);
};

View File

@ -0,0 +1,15 @@
/*****************************************************************************
* Corpora *
* Fetch requests for /corpora/<entity>/files routes *
*****************************************************************************/
Requests.corpora.entity.files = {};
Requests.corpora.entity.files.ent = {};
Requests.corpora.entity.files.ent.delete = (corpusId, corpusFileId) => {
let input = `/corpora/${corpusId}/files/${corpusFileId}`;
let init = {
method: 'DELETE',
};
return Requests.JSONfetch(input, init);
};

View File

@ -0,0 +1,35 @@
/*****************************************************************************
* Corpora *
* Fetch requests for /corpora/<entity>/followers routes *
*****************************************************************************/
Requests.corpora.entity.followers = {};
Requests.corpora.entity.followers.add = (corpusId, usernames) => {
let input = `/corpora/${corpusId}/followers`;
let init = {
method: 'POST',
body: JSON.stringify(usernames)
};
return Requests.JSONfetch(input, init);
};
Requests.corpora.entity.followers.entity = {};
Requests.corpora.entity.followers.entity.delete = (corpusId, followerId) => {
let input = `/corpora/${corpusId}/followers/${followerId}`;
let init = {
method: 'DELETE',
};
return Requests.JSONfetch(input, init);
};
Requests.corpora.entity.followers.entity.role = {};
Requests.corpora.entity.followers.entity.role.update = (corpusId, followerId, value) => {
let input = `/corpora/${corpusId}/followers/${followerId}/role`;
let init = {
method: 'PUT',
body: JSON.stringify(value)
};
return Requests.JSONfetch(input, init);
};

View File

@ -0,0 +1,31 @@
/*****************************************************************************
* Jobs *
* Fetch requests for /jobs routes *
*****************************************************************************/
Requests.jobs = {};
Requests.jobs.entity = {};
Requests.jobs.entity.delete = (jobId) => {
let input = `/jobs/${jobId}`;
let init = {
method: 'DELETE'
};
return Requests.JSONfetch(input, init);
}
Requests.jobs.entity.log = (jobId) => {
let input = `/jobs/${jobId}/log`;
let init = {
method: 'GET'
};
return Requests.JSONfetch(input, init);
}
Requests.jobs.entity.restart = (jobId) => {
let input = `/jobs/${jobId}/restart`;
let init = {
method: 'POST'
};
return Requests.JSONfetch(input, init);
}

View File

@ -0,0 +1,17 @@
/*****************************************************************************
* Settings *
* Fetch requests for /users/<entity>/settings routes *
*****************************************************************************/
Requests.users.entity.settings = {};
Requests.users.entity.settings.profilePrivacy = {};
Requests.users.entity.settings.profilePrivacy.update = (userId, profilePrivacySetting, enabled) => {
let input = `/users/${userId}/settings/profile-privacy/${profilePrivacySetting}`;
let init = {
method: 'PUT',
body: JSON.stringify(enabled)
};
return Requests.JSONfetch(input, init);
};

View File

@ -0,0 +1,35 @@
/*****************************************************************************
* Users *
* Fetch requests for /users routes *
*****************************************************************************/
Requests.users = {};
Requests.users.entity = {};
Requests.users.entity.delete = (userId) => {
let input = `/users/${userId}`;
let init = {
method: 'DELETE'
};
return Requests.JSONfetch(input, init);
};
Requests.users.entity.acceptTermsOfUse = () => {
let input = `/users/accept-terms-of-use`;
let init = {
method: 'POST'
};
return Requests.JSONfetch(input, init);
};
Requests.users.entity.avatar = {};
Requests.users.entity.avatar.delete = (userId) => {
let input = `/users/${userId}/avatar`;
let init = {
method: 'DELETE'
};
return Requests.JSONfetch(input, init);
}

View File

@ -1,13 +1,11 @@
nopaque.resource_displays.CorpusDisplay = class CorpusDisplay extends nopaque.resource_displays.ResourceDisplay { class CorpusDisplay extends ResourceDisplay {
static htmlClass = 'corpus-display';
constructor(displayElement) { constructor(displayElement) {
super(displayElement); super(displayElement);
this.corpusId = displayElement.dataset.corpusId; this.corpusId = displayElement.dataset.corpusId;
this.displayElement this.displayElement
.querySelector('.action-button[data-action="build-request"]') .querySelector('.action-button[data-action="build-request"]')
.addEventListener('click', (event) => { .addEventListener('click', (event) => {
nopaque.requests.corpora.entity.build(this.corpusId); Requests.corpora.entity.build(this.corpusId);
}); });
} }
@ -104,4 +102,4 @@ nopaque.resource_displays.CorpusDisplay = class CorpusDisplay extends nopaque.re
new Date(creationDate).toLocaleString("en-US") new Date(creationDate).toLocaleString("en-US")
); );
} }
}; }

View File

@ -1,6 +1,4 @@
nopaque.resource_displays.JobDisplay = class JobDisplay extends nopaque.resource_displays.ResourceDisplay { class JobDisplay extends ResourceDisplay {
static htmlClass = 'job-display';
constructor(displayElement) { constructor(displayElement) {
super(displayElement); super(displayElement);
this.jobId = this.displayElement.dataset.jobId; this.jobId = this.displayElement.dataset.jobId;
@ -125,4 +123,4 @@ nopaque.resource_displays.JobDisplay = class JobDisplay extends nopaque.resource
setServiceVersion(serviceVersion) { setServiceVersion(serviceVersion) {
this.setElements(this.displayElement.querySelectorAll('.job-service-version'), serviceVersion); this.setElements(this.displayElement.querySelectorAll('.job-service-version'), serviceVersion);
} }
}; }

View File

@ -1,6 +1,4 @@
nopaque.resource_displays.ResourceDisplay = class ResourceDisplay { class ResourceDisplay {
static htmlClass;
constructor(displayElement) { constructor(displayElement) {
this.displayElement = displayElement; this.displayElement = displayElement;
this.userId = this.displayElement.dataset.userId; this.userId = this.displayElement.dataset.userId;
@ -43,4 +41,4 @@ nopaque.resource_displays.ResourceDisplay = class ResourceDisplay {
this.setElement(element, value); this.setElement(element, value);
} }
} }
}; }

View File

@ -1,5 +1,9 @@
nopaque.resource_lists.AdminUserList = class AdminUserList extends nopaque.resource_lists.ResourceList { class AdminUserList extends ResourceList {
static htmlClass = 'admin-user-list'; static autoInit() {
for (let adminUserListElement of document.querySelectorAll('.admin-user-list:not(.no-autoinit)')) {
new AdminUserList(adminUserListElement);
}
}
constructor(listContainerElement, options = {}) { constructor(listContainerElement, options = {}) {
super(listContainerElement, options); super(listContainerElement, options);
@ -37,9 +41,9 @@ nopaque.resource_lists.AdminUserList = class AdminUserList extends nopaque.resou
initListContainerElement() { initListContainerElement() {
if (!this.listContainerElement.hasAttribute('id')) { if (!this.listContainerElement.hasAttribute('id')) {
this.listContainerElement.id = nopaque.Utils.generateElementId('user-list-'); this.listContainerElement.id = Utils.generateElementId('user-list-');
} }
let listSearchElementId = nopaque.Utils.generateElementId(`${this.listContainerElement.id}-search-`); let listSearchElementId = Utils.generateElementId(`${this.listContainerElement.id}-search-`);
this.listContainerElement.innerHTML = ` this.listContainerElement.innerHTML = `
<div class="input-field"> <div class="input-field">
<i class="material-icons prefix">search</i> <i class="material-icons prefix">search</i>
@ -87,7 +91,8 @@ nopaque.resource_lists.AdminUserList = class AdminUserList extends nopaque.resou
let listAction = listActionElement === null ? 'view' : listActionElement.dataset.listAction; let listAction = listActionElement === null ? 'view' : listActionElement.dataset.listAction;
switch (listAction) { switch (listAction) {
case 'delete': { case 'delete': {
nopaque.requests.users.entity.delete(itemId); console.log('delete', itemId);
Utils.deleteUserRequest(itemId);
if (itemId === currentUserId) {window.location.href = '/';} if (itemId === currentUserId) {window.location.href = '/';}
break; break;
} }
@ -104,4 +109,4 @@ nopaque.resource_lists.AdminUserList = class AdminUserList extends nopaque.resou
} }
} }
} }
}; }

View File

@ -1,5 +1,9 @@
nopaque.resource_lists.CorpusFileList = class CorpusFileList extends nopaque.resource_lists.ResourceList { class CorpusFileList extends ResourceList {
static htmlClass = 'corpus-file-list'; static autoInit() {
for (let corpusFileListElement of document.querySelectorAll('.corpus-file-list:not(.no-autoinit)')) {
new CorpusFileList(corpusFileListElement);
}
}
constructor(listContainerElement, options = {}) { constructor(listContainerElement, options = {}) {
super(listContainerElement, options); super(listContainerElement, options);
@ -62,9 +66,9 @@ nopaque.resource_lists.CorpusFileList = class CorpusFileList extends nopaque.res
initListContainerElement() { initListContainerElement() {
if (!this.listContainerElement.hasAttribute('id')) { if (!this.listContainerElement.hasAttribute('id')) {
this.listContainerElement.id = nopaque.Utils.generateElementId('corpus-file-list-'); this.listContainerElement.id = Utils.generateElementId('corpus-file-list-');
} }
let listSearchElementId = nopaque.Utils.generateElementId(`${this.listContainerElement.id}-search-`); let listSearchElementId = Utils.generateElementId(`${this.listContainerElement.id}-search-`);
this.listContainerElement.innerHTML = ` this.listContainerElement.innerHTML = `
<div class="input-field"> <div class="input-field">
<i class="material-icons prefix">search</i> <i class="material-icons prefix">search</i>
@ -121,7 +125,7 @@ nopaque.resource_lists.CorpusFileList = class CorpusFileList extends nopaque.res
switch (listAction) { switch (listAction) {
case 'delete': { case 'delete': {
let values = this.listjs.get('id', itemId)[0].values(); let values = this.listjs.get('id', itemId)[0].values();
let modalElement = nopaque.Utils.HTMLToElement( let modalElement = Utils.HTMLToElement(
` `
<div class="modal"> <div class="modal">
<div class="modal-content"> <div class="modal-content">
@ -149,12 +153,12 @@ nopaque.resource_lists.CorpusFileList = class CorpusFileList extends nopaque.res
let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
confirmElement.addEventListener('click', (event) => { confirmElement.addEventListener('click', (event) => {
if (currentUserId != this.userId) { if (currentUserId != this.userId) {
nopaque.requests.corpora.entity.files.ent.delete(this.corpusId, itemId) Requests.corpora.entity.files.ent.delete(this.corpusId, itemId)
.then(() => { .then(() => {
window.location.reload(); window.location.reload();
}); });
} else { } else {
nopaque.requests.corpora.entity.files.ent.delete(this.corpusId, itemId) Requests.corpora.entity.files.ent.delete(this.corpusId, itemId)
} }
}); });
modal.open(); modal.open();
@ -208,7 +212,7 @@ nopaque.resource_lists.CorpusFileList = class CorpusFileList extends nopaque.res
break; break;
} }
case 'delete': { case 'delete': {
let modalElement = nopaque.Utils.HTMLToElement( let modalElement = Utils.HTMLToElement(
` `
<div class="modal"> <div class="modal">
<div class="modal-content"> <div class="modal-content">
@ -229,7 +233,7 @@ nopaque.resource_lists.CorpusFileList = class CorpusFileList extends nopaque.res
this.selectedItemIds.forEach(selectedItemId => { this.selectedItemIds.forEach(selectedItemId => {
let listItem = this.listjs.get('id', selectedItemId)[0].elm; let listItem = this.listjs.get('id', selectedItemId)[0].elm;
let values = this.listjs.get('id', listItem.dataset.id)[0].values(); let values = this.listjs.get('id', listItem.dataset.id)[0].values();
let itemElement = nopaque.Utils.HTMLToElement(`<li> - ${values.title}</li>`); let itemElement = Utils.HTMLToElement(`<li> - ${values.title}</li>`);
itemList.appendChild(itemElement); itemList.appendChild(itemElement);
}); });
let modal = M.Modal.init( let modal = M.Modal.init(
@ -246,12 +250,12 @@ nopaque.resource_lists.CorpusFileList = class CorpusFileList extends nopaque.res
confirmElement.addEventListener('click', (event) => { confirmElement.addEventListener('click', (event) => {
this.selectedItemIds.forEach(selectedItemId => { this.selectedItemIds.forEach(selectedItemId => {
if (currentUserId != this.userId) { if (currentUserId != this.userId) {
nopaque.requests.corpora.entity.files.ent.delete(this.corpusId, selectedItemId) Requests.corpora.entity.files.ent.delete(this.corpusId, selectedItemId)
.then(() => { .then(() => {
window.location.reload(); window.location.reload();
}); });
} else { } else {
nopaque.requests.corpora.entity.files.ent.delete(this.corpusId, selectedItemId); Requests.corpora.entity.files.ent.delete(this.corpusId, selectedItemId);
} }
}); });
this.selectedItemIds.clear(); this.selectedItemIds.clear();
@ -365,4 +369,4 @@ nopaque.resource_lists.CorpusFileList = class CorpusFileList extends nopaque.res
} }
} }
} }
}; }

View File

@ -1,5 +1,9 @@
nopaque.resource_lists.CorpusFollowerList = class CorpusFollowerList extends nopaque.resource_lists.ResourceList { class CorpusFollowerList extends ResourceList {
static htmlClass = 'corpus-follower-list'; static autoInit() {
for (let corpusFollowerListElement of document.querySelectorAll('.corpus-follower-list:not(.no-autoinit)')) {
new CorpusFollowerList(corpusFollowerListElement);
}
}
constructor(listContainerElement, options = {}) { constructor(listContainerElement, options = {}) {
super(listContainerElement, options); super(listContainerElement, options);
@ -18,7 +22,7 @@ nopaque.resource_lists.CorpusFollowerList = class CorpusFollowerList extends nop
}); });
}); });
app.getUser(this.userId).then((user) => { app.getUser(this.userId).then((user) => {
// let corpusFollowerAssociations = Object.values(user.corpora[this.corpusId].corpus_follower_associations); let corpusFollowerAssociations = Object.values(user.corpora[this.corpusId].corpus_follower_associations);
// let filteredList = corpusFollowerAssociations.filter(association => association.follower.id != currentUserId); // let filteredList = corpusFollowerAssociations.filter(association => association.follower.id != currentUserId);
// this.add(filteredList); // this.add(filteredList);
this.add(Object.values(user.corpora[this.corpusId].corpus_follower_associations)); this.add(Object.values(user.corpora[this.corpusId].corpus_follower_associations));
@ -68,9 +72,9 @@ nopaque.resource_lists.CorpusFollowerList = class CorpusFollowerList extends nop
initListContainerElement() { initListContainerElement() {
if (!this.listContainerElement.hasAttribute('id')) { if (!this.listContainerElement.hasAttribute('id')) {
this.listContainerElement.id = nopaque.Utils.generateElementId('corpus-follower-list-'); this.listContainerElement.id = Utils.generateElementId('corpus-follower-list-');
} }
let listSearchElementId = nopaque.Utils.generateElementId(`${this.listContainerElement.id}-search-`); let listSearchElementId = Utils.generateElementId(`${this.listContainerElement.id}-search-`);
this.listContainerElement.innerHTML = ` this.listContainerElement.innerHTML = `
<div class="input-field"> <div class="input-field">
<i class="material-icons prefix">search</i> <i class="material-icons prefix">search</i>
@ -120,7 +124,7 @@ nopaque.resource_lists.CorpusFollowerList = class CorpusFollowerList extends nop
case 'update-role': { case 'update-role': {
let followerId = listItemElement.dataset.followerId; let followerId = listItemElement.dataset.followerId;
let roleName = event.target.value; let roleName = event.target.value;
nopaque.requests.corpora.entity.followers.entity.role.update(this.corpusId, followerId, roleName); Requests.corpora.entity.followers.entity.role.update(this.corpusId, followerId, roleName);
break; break;
} }
default: { default: {
@ -140,12 +144,12 @@ nopaque.resource_lists.CorpusFollowerList = class CorpusFollowerList extends nop
case 'unfollow-request': { case 'unfollow-request': {
let followerId = listItemElement.dataset.followerId; let followerId = listItemElement.dataset.followerId;
if (currentUserId != this.userId) { if (currentUserId != this.userId) {
nopaque.requests.corpora.entity.followers.entity.delete(this.corpusId, followerId) Requests.corpora.entity.followers.entity.delete(this.corpusId, followerId)
.then(() => { .then(() => {
window.location.reload(); window.location.reload();
}); });
} else { } else {
nopaque.requests.corpora.entity.followers.entity.delete(this.corpusId, followerId); Requests.corpora.entity.followers.entity.delete(this.corpusId, followerId);
} }
break; break;
} }
@ -192,4 +196,4 @@ nopaque.resource_lists.CorpusFollowerList = class CorpusFollowerList extends nop
} }
} }
} }
}; }

View File

@ -1,5 +1,9 @@
nopaque.resource_lists.CorpusList = class CorpusList extends nopaque.resource_lists.ResourceList { class CorpusList extends ResourceList {
static htmlClass = 'corpus-list'; static autoInit() {
for (let corpusListElement of document.querySelectorAll('.corpus-list:not(.no-autoinit)')) {
new CorpusList(corpusListElement);
}
}
constructor(listContainerElement, options = {}) { constructor(listContainerElement, options = {}) {
super(listContainerElement, options); super(listContainerElement, options);
@ -93,9 +97,9 @@ nopaque.resource_lists.CorpusList = class CorpusList extends nopaque.resource_li
initListContainerElement() { initListContainerElement() {
if (!this.listContainerElement.hasAttribute('id')) { if (!this.listContainerElement.hasAttribute('id')) {
this.listContainerElement.id = nopaque.Utils.generateElementId('corpus-list-'); this.listContainerElement.id = Utils.generateElementId('corpus-list-');
} }
let listSearchElementId = nopaque.Utils.generateElementId(`${this.listContainerElement.id}-search-`); let listSearchElementId = Utils.generateElementId(`${this.listContainerElement.id}-search-`);
this.listContainerElement.innerHTML = ` this.listContainerElement.innerHTML = `
<div class="input-field"> <div class="input-field">
<i class="material-icons prefix">search</i> <i class="material-icons prefix">search</i>
@ -139,7 +143,7 @@ nopaque.resource_lists.CorpusList = class CorpusList extends nopaque.resource_li
switch (listAction) { switch (listAction) {
case 'delete-request': { case 'delete-request': {
let values = this.listjs.get('id', itemId)[0].values(); let values = this.listjs.get('id', itemId)[0].values();
let modalElement = nopaque.Utils.HTMLToElement( let modalElement = Utils.HTMLToElement(
` `
<div class="modal"> <div class="modal">
<div class="modal-content"> <div class="modal-content">
@ -167,12 +171,12 @@ nopaque.resource_lists.CorpusList = class CorpusList extends nopaque.resource_li
let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
confirmElement.addEventListener('click', (event) => { confirmElement.addEventListener('click', (event) => {
if (!values['is-owner']) { if (!values['is-owner']) {
nopaque.requests.corpora.entity.followers.entity.delete(itemId, currentUserId) Requests.corpora.entity.followers.entity.delete(itemId, currentUserId)
.then((response) => { .then((response) => {
window.location.reload(); window.location.reload();
}); });
} else { } else {
nopaque.requests.corpora.entity.delete(itemId); Requests.corpora.entity.delete(itemId);
} }
}); });
modal.open(); modal.open();
@ -224,7 +228,7 @@ nopaque.resource_lists.CorpusList = class CorpusList extends nopaque.resource_li
// Saved for future use: // Saved for future use:
// <p class="hide">Do you really want to unfollow this Corpora?</p> // <p class="hide">Do you really want to unfollow this Corpora?</p>
// <ul id="selected-unfollow-items-list"></ul> // <ul id="selected-unfollow-items-list"></ul>
let modalElement = nopaque.Utils.HTMLToElement( let modalElement = Utils.HTMLToElement(
` `
<div class="modal"> <div class="modal">
<div class="modal-content"> <div class="modal-content">
@ -245,7 +249,7 @@ nopaque.resource_lists.CorpusList = class CorpusList extends nopaque.resource_li
this.selectedItemIds.forEach(selectedItemId => { this.selectedItemIds.forEach(selectedItemId => {
let listItem = this.listjs.get('id', selectedItemId)[0].elm; let listItem = this.listjs.get('id', selectedItemId)[0].elm;
let values = this.listjs.get('id', listItem.dataset.id)[0].values(); let values = this.listjs.get('id', listItem.dataset.id)[0].values();
let itemElement = nopaque.Utils.HTMLToElement(`<li> - ${values.title}</li>`); let itemElement = Utils.HTMLToElement(`<li> - ${values.title}</li>`);
// if (!values['is-owner']) { // if (!values['is-owner']) {
// itemUnfollowList.appendChild(itemElement); // itemUnfollowList.appendChild(itemElement);
// } else { // } else {
@ -268,9 +272,9 @@ nopaque.resource_lists.CorpusList = class CorpusList extends nopaque.resource_li
let listItem = this.listjs.get('id', selectedItemId)[0].elm; let listItem = this.listjs.get('id', selectedItemId)[0].elm;
let values = this.listjs.get('id', listItem.dataset.id)[0].values(); let values = this.listjs.get('id', listItem.dataset.id)[0].values();
if (values['is-owner']) { if (values['is-owner']) {
nopaque.requests.corpora.entity.delete(selectedItemId); Requests.corpora.entity.delete(selectedItemId);
} else { } else {
nopaque.requests.corpora.entity.followers.entity.delete(selectedItemId, currentUserId); Requests.corpora.entity.followers.entity.delete(selectedItemId, currentUserId);
setTimeout(() => { setTimeout(() => {
window.location.reload(); window.location.reload();
}, 1000); }, 1000);
@ -366,4 +370,4 @@ nopaque.resource_lists.CorpusList = class CorpusList extends nopaque.resource_li
} }
} }
} }
}; }

View File

@ -1,6 +1,4 @@
nopaque.resource_lists.DetailedPublicCorpusList = class DetailedPublicCorpusList extends nopaque.resource_lists.ResourceList { class DetailledPublicCorpusList extends CorpusList {
static htmlClass = 'detailed-public-corpus-list';
get item() { get item() {
return (values) => { return (values) => {
return ` return `
@ -32,9 +30,9 @@ nopaque.resource_lists.DetailedPublicCorpusList = class DetailedPublicCorpusList
initListContainerElement() { initListContainerElement() {
if (!this.listContainerElement.hasAttribute('id')) { if (!this.listContainerElement.hasAttribute('id')) {
this.listContainerElement.id = nopaque.Utils.generateElementId('corpus-list-'); this.listContainerElement.id = Utils.generateElementId('corpus-list-');
} }
let listSearchElementId = nopaque.Utils.generateElementId(`${this.listContainerElement.id}-search-`); let listSearchElementId = Utils.generateElementId(`${this.listContainerElement.id}-search-`);
this.listContainerElement.innerHTML = ` this.listContainerElement.innerHTML = `
<div class="input-field"> <div class="input-field">
<i class="material-icons prefix">search</i> <i class="material-icons prefix">search</i>
@ -70,4 +68,4 @@ nopaque.resource_lists.DetailedPublicCorpusList = class DetailedPublicCorpusList
'current-user-is-following': Object.values(corpus.corpus_follower_associations).some(association => association.follower.id === currentUserId) 'current-user-is-following': Object.values(corpus.corpus_follower_associations).some(association => association.follower.id === currentUserId)
}; };
} }
}; }

View File

@ -1,5 +1,9 @@
nopaque.resource_lists.JobInputList = class JobInputList extends nopaque.resource_lists.ResourceList { class JobInputList extends ResourceList {
static htmlClass = 'job-input-list'; static autoInit() {
for (let jobInputListElement of document.querySelectorAll('.job-input-list:not(.no-autoinit)')) {
new JobInputList(jobInputListElement);
}
}
constructor(listContainerElement, options = {}) { constructor(listContainerElement, options = {}) {
super(listContainerElement, options); super(listContainerElement, options);
@ -36,9 +40,9 @@ nopaque.resource_lists.JobInputList = class JobInputList extends nopaque.resourc
initListContainerElement() { initListContainerElement() {
if (!this.listContainerElement.hasAttribute('id')) { if (!this.listContainerElement.hasAttribute('id')) {
this.listContainerElement.id = nopaque.Utils.generateElementId('job-input-list-'); this.listContainerElement.id = Utils.generateElementId('job-input-list-');
} }
let listSearchElementId = nopaque.Utils.generateElementId(`${this.listContainerElement.id}-search-`); let listSearchElementId = Utils.generateElementId(`${this.listContainerElement.id}-search-`);
this.listContainerElement.innerHTML = ` this.listContainerElement.innerHTML = `
<div class="input-field"> <div class="input-field">
<i class="material-icons prefix">search</i> <i class="material-icons prefix">search</i>
@ -86,4 +90,4 @@ nopaque.resource_lists.JobInputList = class JobInputList extends nopaque.resourc
} }
} }
} }
}; }

View File

@ -1,5 +1,9 @@
nopaque.resource_lists.JobList = class JobList extends nopaque.resource_lists.ResourceList { class JobList extends ResourceList {
static htmlClass = 'job-list'; static autoInit() {
for (let jobListElement of document.querySelectorAll('.job-list:not(.no-autoinit)')) {
new JobList(jobListElement);
}
}
constructor(listContainerElement, options = {}) { constructor(listContainerElement, options = {}) {
super(listContainerElement, options); super(listContainerElement, options);
@ -25,7 +29,7 @@ nopaque.resource_lists.JobList = class JobList extends nopaque.resource_lists.Re
get item() { get item() {
return ` return `
<tr class="list-item service-scheme clickable hoverable"> <tr class="list-item service-scheme">
<td> <td>
<label class="list-action-trigger" data-list-action="select"> <label class="list-action-trigger" data-list-action="select">
<input class="select-checkbox" type="checkbox"> <input class="select-checkbox" type="checkbox">
@ -56,9 +60,9 @@ nopaque.resource_lists.JobList = class JobList extends nopaque.resource_lists.Re
initListContainerElement() { initListContainerElement() {
if (!this.listContainerElement.hasAttribute('id')) { if (!this.listContainerElement.hasAttribute('id')) {
this.listContainerElement.id = nopaque.Utils.generateElementId('job-list-'); this.listContainerElement.id = Utils.generateElementId('job-list-');
} }
let listSearchElementId = nopaque.Utils.generateElementId(`${this.listContainerElement.id}-search-`); let listSearchElementId = Utils.generateElementId(`${this.listContainerElement.id}-search-`);
this.listContainerElement.innerHTML = ` this.listContainerElement.innerHTML = `
<div class="input-field"> <div class="input-field">
<i class="material-icons prefix">search</i> <i class="material-icons prefix">search</i>
@ -108,11 +112,11 @@ nopaque.resource_lists.JobList = class JobList extends nopaque.resource_lists.Re
if (listItemElement === null) {return;} if (listItemElement === null) {return;}
let itemId = listItemElement.dataset.id; let itemId = listItemElement.dataset.id;
let listActionElement = event.target.closest('.list-action-trigger[data-list-action]'); let listActionElement = event.target.closest('.list-action-trigger[data-list-action]');
let listAction = listActionElement === null ? 'view' : listActionElement.dataset.listAction; let listAction = listActionElement === null ? '' : listActionElement.dataset.listAction;
switch (listAction) { switch (listAction) {
case 'delete-request': { case 'delete-request': {
let values = this.listjs.get('id', itemId)[0].values(); let values = this.listjs.get('id', itemId)[0].values();
let modalElement = nopaque.Utils.HTMLToElement( let modalElement = Utils.HTMLToElement(
` `
<div class="modal"> <div class="modal">
<div class="modal-content"> <div class="modal-content">
@ -139,7 +143,7 @@ nopaque.resource_lists.JobList = class JobList extends nopaque.resource_lists.Re
); );
let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
confirmElement.addEventListener('click', (event) => { confirmElement.addEventListener('click', (event) => {
nopaque.requests.jobs.entity.delete(itemId); Requests.jobs.entity.delete(itemId);
}); });
modal.open(); modal.open();
break; break;
@ -187,7 +191,7 @@ nopaque.resource_lists.JobList = class JobList extends nopaque.resource_lists.Re
break; break;
} }
case 'delete': { case 'delete': {
let modalElement = nopaque.Utils.HTMLToElement( let modalElement = Utils.HTMLToElement(
` `
<div class="modal"> <div class="modal">
<div class="modal-content"> <div class="modal-content">
@ -208,7 +212,7 @@ nopaque.resource_lists.JobList = class JobList extends nopaque.resource_lists.Re
this.selectedItemIds.forEach(selectedItemId => { this.selectedItemIds.forEach(selectedItemId => {
let listItem = this.listjs.get('id', selectedItemId)[0].elm; let listItem = this.listjs.get('id', selectedItemId)[0].elm;
let values = this.listjs.get('id', listItem.dataset.id)[0].values(); let values = this.listjs.get('id', listItem.dataset.id)[0].values();
let itemElement = nopaque.Utils.HTMLToElement(`<li> - ${values.title}</li>`); let itemElement = Utils.HTMLToElement(`<li> - ${values.title}</li>`);
itemList.appendChild(itemElement); itemList.appendChild(itemElement);
}); });
let modal = M.Modal.init( let modal = M.Modal.init(
@ -224,7 +228,7 @@ nopaque.resource_lists.JobList = class JobList extends nopaque.resource_lists.Re
let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
confirmElement.addEventListener('click', (event) => { confirmElement.addEventListener('click', (event) => {
this.selectedItemIds.forEach(selectedItemId => { this.selectedItemIds.forEach(selectedItemId => {
nopaque.requests.jobs.entity.delete(selectedItemId); Requests.jobs.entity.delete(selectedItemId);
}); });
this.selectedItemIds.clear(); this.selectedItemIds.clear();
this.renderingItemSelection(); this.renderingItemSelection();
@ -319,4 +323,4 @@ nopaque.resource_lists.JobList = class JobList extends nopaque.resource_lists.Re
} }
} }
} }
}; }

View File

@ -1,5 +1,9 @@
nopaque.resource_lists.JobResultList = class JobResultList extends nopaque.resource_lists.ResourceList { class JobResultList extends ResourceList {
static htmlClass = 'job-result-list'; static autoInit() {
for (let jobResultListElement of document.querySelectorAll('.job-result-list:not(.no-autoinit)')) {
new JobResultList(jobResultListElement);
}
}
constructor(listContainerElement, options = {}) { constructor(listContainerElement, options = {}) {
super(listContainerElement, options); super(listContainerElement, options);
@ -42,9 +46,9 @@ nopaque.resource_lists.JobResultList = class JobResultList extends nopaque.resou
initListContainerElement() { initListContainerElement() {
if (!this.listContainerElement.hasAttribute('id')) { if (!this.listContainerElement.hasAttribute('id')) {
this.listContainerElement.id = nopaque.Utils.generateElementId('job-result-list-'); this.listContainerElement.id = Utils.generateElementId('job-result-list-');
} }
let listSearchElementId = nopaque.Utils.generateElementId(`${this.listContainerElement.id}-search-`); let listSearchElementId = Utils.generateElementId(`${this.listContainerElement.id}-search-`);
this.listContainerElement.innerHTML = ` this.listContainerElement.innerHTML = `
<div class="input-field"> <div class="input-field">
<i class="material-icons prefix">search</i> <i class="material-icons prefix">search</i>
@ -111,4 +115,4 @@ nopaque.resource_lists.JobResultList = class JobResultList extends nopaque.resou
} }
} }
} }
}; }

View File

@ -1,11 +1,4 @@
nopaque.resource_lists.PublicCorpusList = class PublicCorpusList extends nopaque.resource_lists.ResourceList { class PublicCorpusList extends CorpusList {
static htmlClass = 'public-corpus-list';
constructor(listContainerElement, options = {}) {
super(listContainerElement, options);
this.listjs.list.addEventListener('click', (event) => {this.onClick(event)});
}
get item() { get item() {
return (values) => { return (values) => {
return ` return `
@ -21,19 +14,6 @@ nopaque.resource_lists.PublicCorpusList = class PublicCorpusList extends nopaque
}; };
} }
get valueNames() {
return [
{data: ['id']},
{data: ['creation-date']},
{name: 'status', attr: 'data-status'},
'description',
'title',
'owner',
'is-owner',
'current-user-is-following'
];
}
mapResourceToValue(corpus) { mapResourceToValue(corpus) {
return { return {
'id': corpus.id, 'id': corpus.id,
@ -49,9 +29,9 @@ nopaque.resource_lists.PublicCorpusList = class PublicCorpusList extends nopaque
initListContainerElement() { initListContainerElement() {
if (!this.listContainerElement.hasAttribute('id')) { if (!this.listContainerElement.hasAttribute('id')) {
this.listContainerElement.id = nopaque.Utils.generateElementId('corpus-list-'); this.listContainerElement.id = Utils.generateElementId('corpus-list-');
} }
let listSearchElementId = nopaque.Utils.generateElementId(`${this.listContainerElement.id}-search-`); let listSearchElementId = Utils.generateElementId(`${this.listContainerElement.id}-search-`);
this.listContainerElement.innerHTML = ` this.listContainerElement.innerHTML = `
<div class="input-field"> <div class="input-field">
<i class="material-icons prefix">search</i> <i class="material-icons prefix">search</i>
@ -72,21 +52,4 @@ nopaque.resource_lists.PublicCorpusList = class PublicCorpusList extends nopaque
<ul class="pagination"></ul> <ul class="pagination"></ul>
`.trim(); `.trim();
} }
}
onClick(event) {
let listItemElement = event.target.closest('.list-item[data-id]');
if (listItemElement === null) {return;}
let itemId = listItemElement.dataset.id;
let listActionElement = event.target.closest('.list-action-trigger[data-list-action]');
let listAction = listActionElement === null ? 'view' : listActionElement.dataset.listAction;
switch (listAction) {
case 'view': {
window.location.href = `/corpora/${itemId}`;
break;
}
default: {
break;
}
}
}
};

View File

@ -1,10 +1,21 @@
nopaque.resource_lists.ResourceList = class ResourceList { class ResourceList {
/* A wrapper class for the list.js list. /* A wrapper class for the list.js list.
* This class is not meant to be used directly, instead it should be used as * This class is not meant to be used directly, instead it should be used as
* a base class for concrete resource list implementations. * a base class for concrete resource list implementations.
*/ */
static htmlClass; static autoInit() {
CorpusList.autoInit();
CorpusFileList.autoInit();
JobList.autoInit();
JobInputList.autoInit();
JobResultList.autoInit();
SpaCyNLPPipelineModelList.autoInit();
TesseractOCRPipelineModelList.autoInit();
UserList.autoInit();
AdminUserList.autoInit();
CorpusFollowerList.autoInit();
}
static defaultOptions = { static defaultOptions = {
page: 5, page: 5,
@ -21,9 +32,9 @@ nopaque.resource_lists.ResourceList = class ResourceList {
if ('valueNames' in options) { if ('valueNames' in options) {
throw '"valueNames" is not supported as an option, define it as a getter in the list class'; throw '"valueNames" is not supported as an option, define it as a getter in the list class';
} }
let _options = nopaque.Utils.mergeObjectsDeep( let _options = Utils.mergeObjectsDeep(
{item: this.item, valueNames: this.valueNames}, {item: this.item, valueNames: this.valueNames},
nopaque.resource_lists.ResourceList.defaultOptions, ResourceList.defaultOptions,
options options
); );
this.listContainerElement = listContainerElement; this.listContainerElement = listContainerElement;

View File

@ -1,5 +1,9 @@
nopaque.resource_lists.SpaCyNLPPipelineModelList = class SpaCyNLPPipelineModelList extends nopaque.resource_lists.ResourceList { class SpaCyNLPPipelineModelList extends ResourceList {
static htmlClass = 'spacy-nlp-pipeline-model-list'; static autoInit() {
for (let spaCyNLPPipelineModelListElement of document.querySelectorAll('.spacy-nlp-pipeline-model-list:not(.no-autoinit)')) {
new SpaCyNLPPipelineModelList(spaCyNLPPipelineModelListElement);
}
}
constructor(listContainerElement, options = {}) { constructor(listContainerElement, options = {}) {
super(listContainerElement, options); super(listContainerElement, options);
@ -60,9 +64,9 @@ nopaque.resource_lists.SpaCyNLPPipelineModelList = class SpaCyNLPPipelineModelLi
initListContainerElement() { initListContainerElement() {
if (!this.listContainerElement.hasAttribute('id')) { if (!this.listContainerElement.hasAttribute('id')) {
this.listContainerElement.id = nopaque.Utils.generateElementId('spacy-nlp-pipeline-model-list-'); this.listContainerElement.id = Utils.generateElementId('spacy-nlp-pipeline-model-list-');
} }
let listSearchElementId = nopaque.Utils.generateElementId(`${this.listContainerElement.id}-search-`); let listSearchElementId = Utils.generateElementId(`${this.listContainerElement.id}-search-`);
this.listContainerElement.innerHTML = ` this.listContainerElement.innerHTML = `
<div class="input-field"> <div class="input-field">
<i class="material-icons prefix">search</i> <i class="material-icons prefix">search</i>
@ -116,7 +120,7 @@ nopaque.resource_lists.SpaCyNLPPipelineModelList = class SpaCyNLPPipelineModelLi
switch (listAction) { switch (listAction) {
case 'toggle-is-public': { case 'toggle-is-public': {
let newIsPublicValue = listActionElement.checked; let newIsPublicValue = listActionElement.checked;
nopaque.requests.contributions.spacy_nlp_pipeline_models.entity.isPublic.update(itemId, newIsPublicValue) Requests.contributions.spacy_nlp_pipeline_models.entity.isPublic.update(itemId, newIsPublicValue)
.catch((response) => { .catch((response) => {
listActionElement.checked = !newIsPublicValue; listActionElement.checked = !newIsPublicValue;
}); });
@ -138,7 +142,7 @@ nopaque.resource_lists.SpaCyNLPPipelineModelList = class SpaCyNLPPipelineModelLi
switch (listAction) { switch (listAction) {
case 'delete-request': { case 'delete-request': {
let values = this.listjs.get('id', itemId)[0].values(); let values = this.listjs.get('id', itemId)[0].values();
let modalElement = nopaque.Utils.HTMLToElement( let modalElement = Utils.HTMLToElement(
` `
<div class="modal"> <div class="modal">
<div class="modal-content"> <div class="modal-content">
@ -165,7 +169,7 @@ nopaque.resource_lists.SpaCyNLPPipelineModelList = class SpaCyNLPPipelineModelLi
); );
let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
confirmElement.addEventListener('click', (event) => { confirmElement.addEventListener('click', (event) => {
nopaque.requests.contributions.spacy_nlp_pipeline_models.entity.delete(itemId); Requests.contributions.spacy_nlp_pipeline_models.entity.delete(itemId);
}); });
modal.open(); modal.open();
break; break;
@ -216,4 +220,4 @@ nopaque.resource_lists.SpaCyNLPPipelineModelList = class SpaCyNLPPipelineModelLi
} }
} }
} }
}; }

View File

@ -1,5 +1,9 @@
nopaque.resource_lists.TesseractOCRPipelineModelList = class TesseractOCRPipelineModelList extends nopaque.resource_lists.ResourceList { class TesseractOCRPipelineModelList extends ResourceList {
static htmlClass = 'tesseract-ocr-pipeline-model-list'; static autoInit() {
for (let tesseractOCRPipelineModelListElement of document.querySelectorAll('.tesseract-ocr-pipeline-model-list:not(.no-autoinit)')) {
new TesseractOCRPipelineModelList(tesseractOCRPipelineModelListElement);
}
}
constructor(listContainerElement, options = {}) { constructor(listContainerElement, options = {}) {
super(listContainerElement, options); super(listContainerElement, options);
@ -69,9 +73,9 @@ nopaque.resource_lists.TesseractOCRPipelineModelList = class TesseractOCRPipelin
initListContainerElement() { initListContainerElement() {
if (!this.listContainerElement.hasAttribute('id')) { if (!this.listContainerElement.hasAttribute('id')) {
this.listContainerElement.id = nopaque.Utils.generateElementId('tesseract-ocr-pipeline-model-list-'); this.listContainerElement.id = Utils.generateElementId('tesseract-ocr-pipeline-model-list-');
} }
let listSearchElementId = nopaque.Utils.generateElementId(`${this.listContainerElement.id}-search-`); let listSearchElementId = Utils.generateElementId(`${this.listContainerElement.id}-search-`);
this.listContainerElement.innerHTML = ` this.listContainerElement.innerHTML = `
<div class="input-field"> <div class="input-field">
<i class="material-icons prefix">search</i> <i class="material-icons prefix">search</i>
@ -125,7 +129,7 @@ nopaque.resource_lists.TesseractOCRPipelineModelList = class TesseractOCRPipelin
switch (listAction) { switch (listAction) {
case 'toggle-is-public': { case 'toggle-is-public': {
let newIsPublicValue = listActionElement.checked; let newIsPublicValue = listActionElement.checked;
nopaque.requests.contributions.tesseract_ocr_pipeline_models.entity.isPublic.update(itemId, newIsPublicValue) Requests.contributions.tesseract_ocr_pipeline_models.entity.isPublic.update(itemId, newIsPublicValue)
.catch((response) => { .catch((response) => {
listActionElement.checked = !newIsPublicValue; listActionElement.checked = !newIsPublicValue;
}); });
@ -147,7 +151,7 @@ nopaque.resource_lists.TesseractOCRPipelineModelList = class TesseractOCRPipelin
switch (listAction) { switch (listAction) {
case 'delete-request': { case 'delete-request': {
let values = this.listjs.get('id', itemId)[0].values(); let values = this.listjs.get('id', itemId)[0].values();
let modalElement = nopaque.Utils.HTMLToElement( let modalElement = Utils.HTMLToElement(
` `
<div class="modal"> <div class="modal">
<div class="modal-content"> <div class="modal-content">
@ -174,7 +178,7 @@ nopaque.resource_lists.TesseractOCRPipelineModelList = class TesseractOCRPipelin
); );
let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
confirmElement.addEventListener('click', (event) => { confirmElement.addEventListener('click', (event) => {
nopaque.requests.contributions.tesseract_ocr_pipeline_models.entity.delete(itemId); Requests.contributions.tesseract_ocr_pipeline_models.entity.delete(itemId);
}); });
modal.open(); modal.open();
break; break;
@ -225,4 +229,4 @@ nopaque.resource_lists.TesseractOCRPipelineModelList = class TesseractOCRPipelin
} }
} }
} }
}; }

View File

@ -1,5 +1,9 @@
nopaque.resource_lists.PublicUserList = class PublicUserList extends nopaque.resource_lists.ResourceList { class UserList extends ResourceList {
static htmlClass = 'public-user-list'; static autoInit() {
for (let userListElement of document.querySelectorAll('.user-list:not(.no-autoinit)')) {
new UserList(userListElement);
}
}
constructor(listContainerElement, options = {}) { constructor(listContainerElement, options = {}) {
super(listContainerElement, options); super(listContainerElement, options);
@ -37,9 +41,9 @@ nopaque.resource_lists.PublicUserList = class PublicUserList extends nopaque.res
initListContainerElement() { initListContainerElement() {
if (!this.listContainerElement.hasAttribute('id')) { if (!this.listContainerElement.hasAttribute('id')) {
this.listContainerElement.id = nopaque.Utils.generateElementId('user-list-'); this.listContainerElement.id = Utils.generateElementId('user-list-');
} }
let listSearchElementId = nopaque.Utils.generateElementId(`${this.listContainerElement.id}-search-`); let listSearchElementId = Utils.generateElementId(`${this.listContainerElement.id}-search-`);
this.listContainerElement.innerHTML = ` this.listContainerElement.innerHTML = `
<div class="input-field"> <div class="input-field">
<i class="material-icons prefix">search</i> <i class="material-icons prefix">search</i>
@ -97,4 +101,4 @@ nopaque.resource_lists.PublicUserList = class PublicUserList extends nopaque.res
} }
} }
} }
}; }

View File

@ -1,7 +1,7 @@
nopaque.Utils = class Utils { class Utils {
static escape(text) { static escape(text) {
// https://codereview.stackexchange.com/a/126722 // https://codereview.stackexchange.com/a/126722
let lookup = { var table = {
'<': 'lt', '<': 'lt',
'>': 'gt', '>': 'gt',
'"': 'quot', '"': 'quot',
@ -10,31 +10,11 @@ nopaque.Utils = class Utils {
'\r': '#10', '\r': '#10',
'\n': '#13' '\n': '#13'
}; };
return text.toString().replace(/[<>"'\r\n&]/g, (chr) => {
return '&' + lookup[chr] + ';';
});
}
static unescape(escapedText) {
let lookup = {
'lt': '<',
'gt': '>',
'quot': '"',
'apos': "'",
'amp': '&',
'#10': '\r',
'#13': '\n'
};
return escapedText.replace(/&(#?\w+);/g, (match, entity) => { return text.toString().replace(/[<>"'\r\n&]/g, (chr) => {
if (lookup.hasOwnProperty(entity)) { return '&' + table[chr] + ';';
return lookup[entity];
}
return match;
}); });
} };
static HTMLToElement(HTMLString) { static HTMLToElement(HTMLString) {
let templateElement = document.createElement('template'); let templateElement = document.createElement('template');
@ -58,16 +38,16 @@ nopaque.Utils = class Utils {
if (objects.length === 0) { if (objects.length === 0) {
return mergedObject; return mergedObject;
} }
if (!this.isObject(objects[0])) {throw 'Cannot merge non-object';} if (!Utils.isObject(objects[0])) {throw 'Cannot merge non-object';}
if (objects.length === 1) { if (objects.length === 1) {
return this.mergeObjectsDeep(mergedObject, objects[0]); return Utils.mergeObjectsDeep(mergedObject, objects[0]);
} }
if (!this.isObject(objects[1])) {throw 'Cannot merge non-object';} if (!Utils.isObject(objects[1])) {throw 'Cannot merge non-object';}
for (let key in objects[0]) { for (let key in objects[0]) {
if (objects[0].hasOwnProperty(key)) { if (objects[0].hasOwnProperty(key)) {
if (objects[1].hasOwnProperty(key)) { if (objects[1].hasOwnProperty(key)) {
if (this.isObject(objects[0][key]) && this.isObject(objects[1][key])) { if (Utils.isObject(objects[0][key]) && Utils.isObject(objects[1][key])) {
mergedObject[key] = this.mergeObjectsDeep(objects[0][key], objects[1][key]); mergedObject[key] = Utils.mergeObjectsDeep(objects[0][key], objects[1][key]);
} else { } else {
mergedObject[key] = objects[1][key]; mergedObject[key] = objects[1][key];
} }
@ -86,6 +66,7 @@ nopaque.Utils = class Utils {
if (objects.length === 2) { if (objects.length === 2) {
return mergedObject; return mergedObject;
} }
return this.mergeObjectsDeep(mergedObject, ...objects.slice(2)); return Utils.mergeObjectsDeep(mergedObject, ...objects.slice(2));
} }
} }

View File

@ -1,204 +0,0 @@
nopaque.App = class App {
constructor() {
this.data = {
promises: {getUser: {}, subscribeUser: {}},
users: {},
};
this.socket = io({transports: ['websocket'], upgrade: false});
this.socket.on('PATCH', (patch) => {this.onPatch(patch);});
}
getUser(userId) {
if (userId in this.data.promises.getUser) {
return this.data.promises.getUser[userId];
}
this.data.promises.getUser[userId] = new Promise((resolve, reject) => {
this.socket.emit('GET /users/<user_id>', userId, (response) => {
if (response.status === 200) {
this.data.users[userId] = response.body;
resolve(this.data.users[userId]);
} else {
reject(`[${response.status}] ${response.statusText}`);
}
});
});
return this.data.promises.getUser[userId];
}
subscribeUser(userId) {
if (userId in this.data.promises.subscribeUser) {
return this.data.promises.subscribeUser[userId];
}
this.data.promises.subscribeUser[userId] = new Promise((resolve, reject) => {
this.socket.emit('SUBSCRIBE /users/<user_id>', userId, (response) => {
if (response.status !== 200) {
reject(response);
return;
}
resolve(response);
});
});
return this.data.promises.subscribeUser[userId];
}
flash(message, category) {
let iconPrefix = '';
switch (category) {
case 'corpus': {
iconPrefix = '<i class="left material-icons">book</i>';
break;
}
case 'error': {
iconPrefix = '<i class="error-color-text left material-icons">error</i>';
break;
}
case 'job': {
iconPrefix = '<i class="left nopaque-icons">J</i>';
break;
}
case 'settings': {
iconPrefix = '<i class="left material-icons">settings</i>';
break;
}
default: {
iconPrefix = '<i class="left material-icons">notifications</i>';
break;
}
}
let toast = M.toast(
{
html: `
<span>${iconPrefix}${message}</span>
<button class="action-button btn-flat toast-action white-text" data-action="close">
<i class="material-icons">close</i>
</button>
`.trim()
}
);
let toastCloseActionElement = toast.el.querySelector('.action-button[data-action="close"]');
toastCloseActionElement.addEventListener('click', () => {toast.dismiss();});
}
onPatch(patch) {
// Filter Patch to only include operations on users that are initialized
let regExp = new RegExp(`^/users/(${Object.keys(this.data.users).join('|')})`);
let filteredPatch = patch.filter(operation => regExp.test(operation.path));
// Handle job status updates
let subRegExp = new RegExp(`^/users/([A-Za-z0-9]*)/jobs/([A-Za-z0-9]*)/status$`);
let subFilteredPatch = filteredPatch
.filter((operation) => {return operation.op === 'replace';})
.filter((operation) => {return subRegExp.test(operation.path);});
for (let operation of subFilteredPatch) {
let [match, userId, jobId] = operation.path.match(subRegExp);
this.flash(`[<a href="/jobs/${jobId}">${this.data.users[userId].jobs[jobId].title}</a>] New status: <span class="job-status-text" data-status="${operation.value}"></span>`, 'job');
}
// Apply Patch
jsonpatch.applyPatch(this.data, filteredPatch);
}
init() {
this.initUi();
}
initUi() {
/* Pre-Initialization fixes */
// #region
// Flask-WTF sets the standard HTML maxlength Attribute on input/textarea
// elements to specify their maximum length (in characters). Unfortunatly
// Materialize won't recognize the maxlength Attribute, instead it uses
// the data-length Attribute. It's conversion time :)
for (let elem of document.querySelectorAll('input[maxlength], textarea[maxlength]')) {
elem.dataset.length = elem.getAttribute('maxlength');
elem.removeAttribute('maxlength');
}
// To work around some limitations with the Form setup of Flask-WTF.
// HTML option elements with an empty value are considered as placeholder
// elements. The user should not be able to actively select these options.
// So they get the disabled attribute.
for (let optionElement of document.querySelectorAll('option[value=""]')) {
optionElement.disabled = true;
}
// TODO: Check why we are doing this.
for (let optgroupElement of document.querySelectorAll('optgroup[label=""]')) {
for (let c of optgroupElement.children) {
optgroupElement.parentElement.insertAdjacentElement('afterbegin', c);
}
optgroupElement.remove();
}
// #endregion
/* Initialize Materialize Components */
// #region
// Automatically initialize Materialize Components that do not require
// additional configuration.
M.AutoInit();
// CharacterCounters
// Materialize didn't include the CharacterCounter plugin within the
// AutoInit method (maybe they forgot it?). Anyway... We do it here. :)
M.CharacterCounter.init(document.querySelectorAll('input[data-length]:not(.no-autoinit), textarea[data-length]:not(.no-autoinit)'));
// Header navigation "more" Dropdown.
M.Dropdown.init(
document.querySelector('#nav-more-dropdown-trigger'),
{
alignment: 'right',
constrainWidth: false,
coverTrigger: false
}
);
// Manual modal
M.Modal.init(
document.querySelector('#manual-modal'),
{
onOpenStart: (modalElement, modalTriggerElement) => {
if ('manualModalChapter' in modalTriggerElement.dataset) {
let manualModalTocElement = document.querySelector('#manual-modal-toc');
let manualModalToc = M.Tabs.getInstance(manualModalTocElement);
manualModalToc.select(modalTriggerElement.dataset.manualModalChapter);
// TODO: Make this work.
// if ('manualModalChapterAnchor' in modalTriggerElement.dataset) {
// let manualModalChapterAnchor = document.querySelector(`#${modalTriggerElement.dataset.manualModalChapterAnchor}`);
// let xCoord = manualModalChapterAnchor.getBoundingClientRect().left;
// let yCoord = manualModalChapterAnchor.getBoundingClientRect().top;
// let modalContentElement = modalElement.querySelector('.modal-content');
// modalContentElement.scroll(xCoord, yCoord);
// }
}
}
}
);
// Terms of use modal
M.Modal.init(
document.querySelector('#terms-of-use-modal'),
{
dismissible: false,
onCloseEnd: (modalElement) => {
nopaque.requests.users.entity.acceptTermsOfUse();
}
}
);
// #endregion
/* Initialize nopaque Components */
// #region
nopaque.resource_displays.AutoInit();
nopaque.resource_lists.AutoInit();
nopaque.forms.AutoInit();
// #endregion
}
};

View File

@ -1,119 +0,0 @@
nopaque.corpus_analysis.App = class App {
constructor(corpusId) {
this.corpusId = corpusId;
this.data = {};
// HTML elements
this.elements = {
container: document.querySelector('#corpus-analysis-container'),
extensionCards: document.querySelector('#corpus-analysis-extension-cards'),
extensionTabs: document.querySelector('#corpus-analysis-extension-tabs'),
initModal: document.querySelector('#corpus-analysis-init-modal')
};
// Materialize elements
this.elements.m = {
extensionTabs: M.Tabs.init(this.elements.extensionTabs),
initModal: M.Modal.init(this.elements.initModal, {dismissible: false})
};
this.extensions = {};
this.settings = {};
}
async init() {
this.disableActionElements();
this.elements.m.initModal.open();
try {
// Setup CQi over SocketIO connection and gather data from the CQPServer
const statusTextElement = this.elements.initModal.querySelector('.status-text');
statusTextElement.innerText = 'Creating CQi over SocketIO client...';
const cqiClient = new nopaque.corpus_analysis.cqi.Client('/cqi_over_sio');
statusTextElement.innerText += ' Done';
statusTextElement.innerHTML = 'Waiting for the CQP server...';
const response = await cqiClient.api.socket.emitWithAck('init', this.corpusId);
if (response.code !== 200) {throw new Error();}
statusTextElement.innerText += ' Done';
statusTextElement.innerHTML = 'Connecting to the CQP server...';
await cqiClient.connect('anonymous', '');
statusTextElement.innerText += ' Done';
statusTextElement.innerHTML = 'Building and receiving corpus data cache from the server (This may take a while)...';
const cqiCorpus = await cqiClient.corpora.get(`NOPAQUE-${this.corpusId.toUpperCase()}`);
statusTextElement.innerText += ' Done';
// TODO: Don't do this hgere
await cqiCorpus.updateDb();
this.data.cqiClient = cqiClient;
this.data.cqiCorpus = cqiCorpus;
this.data.corpus = {o: cqiCorpus}; // legacy
// Initialize extensions
for (const extension of Object.values(this.extensions)) {
statusTextElement.innerHTML = `Initializing ${extension.name} extension...`;
await extension.init();
statusTextElement.innerText += ' Done'
}
} catch (error) {
let errorString = '';
if ('code' in error && error.code !== undefined && error.code !== null) {
errorString += `[${error.code}] `;
}
errorString += `${error.constructor.name}`;
if ('description' in error && error.description !== undefined && error.description !== null) {
errorString += `: ${error.description}`;
}
const errorsElement = this.elements.initModal.querySelector('.errors');
const progressElement = this.elements.initModal.querySelector('.progress');
errorsElement.innerText = errorString;
errorsElement.classList.remove('hide');
progressElement.classList.add('hide');
return;
}
for (const extensionSelectorElement of this.elements.extensionCards.querySelectorAll('.extension-selector')) {
extensionSelectorElement.addEventListener('click', () => {
this.elements.m.extensionTabs.select(extensionSelectorElement.dataset.target);
});
}
this.enableActionElements();
this.elements.m.initModal.close();
}
registerExtension(extension) {
if (extension.name in this.extensions) {return;}
this.extensions[extension.name] = extension;
}
disableActionElements() {
const actionElements = this.elements.container.querySelectorAll('.corpus-analysis-action');
for (const actionElement of actionElements) {
switch(actionElement.nodeName) {
case 'INPUT':
actionElement.disabled = true;
break;
case 'SELECT':
actionElement.parentNode.querySelector('input.select-dropdown').disabled = true;
break;
default:
actionElement.classList.add('disabled');
}
}
}
enableActionElements() {
const actionElements = this.elements.container.querySelectorAll('.corpus-analysis-action');
for (const actionElement of actionElements) {
switch(actionElement.nodeName) {
case 'INPUT':
actionElement.disabled = false;
break;
case 'SELECT':
actionElement.parentNode.querySelector('input.select-dropdown').disabled = false;
break;
default:
actionElement.classList.remove('disabled');
}
}
}
}

View File

@ -1,688 +0,0 @@
nopaque.corpus_analysis.cqi.api.Client = class Client {
/**
* @param {string} host
* @param {number} [timeout=60] timeout
* @param {string} [version=0.1] version
*/
constructor(host, timeout = 60, version = '0.1') {
this.host = host;
this.timeout = timeout * 1000; // convert seconds to milliseconds
this.version = version;
this.socket = io(
this.host,
{
transports: ['websocket'],
upgrade: false
}
);
}
/**
* @param {string} fn_name
* @param {object} [fn_args={}]
* @returns {Promise}
*/
async #request(fn_name, fn_args = {}) {
// TODO: implement timeout
let response = await this.socket.emitWithAck('exec', fn_name, fn_args);
if (response.code === 200) {
return response.payload;
} else if (response.code === 500) {
throw new Error(`[${response.code}] ${response.msg}`);
} else if (response.code === 502) {
if (response.payload.code in nopaque.corpus_analysis.cqi.errors.lookup) {
throw new nopaque.corpus_analysis.cqi.errors.lookup[response.payload.code]();
} else {
throw new nopaque.corpus_analysis.cqi.errors.CQiError();
}
}
}
/**
* @param {string} username
* @param {string} password
* @returns {Promise<nopaque.corpus_analysis.cqi.status.StatusConnectOk>}
*/
async ctrl_connect(username, password) {
const fn_name = 'ctrl_connect';
const fn_args = {username: username, password: password};
let payload = await this.#request(fn_name, fn_args);
return new nopaque.corpus_analysis.cqi.status.lookup[payload.code]();
}
/**
* @returns {Promise<nopaque.corpus_analysis.cqi.status.StatusByeOk>}
*/
async ctrl_bye() {
const fn_name = 'ctrl_bye';
let payload = await this.#request(fn_name);
return new nopaque.corpus_analysis.cqi.status.lookup[payload.code]();
}
/**
* @returns {Promise<null>}
*/
async ctrl_user_abort() {
const fn_name = 'ctrl_user_abort';
return await this.#request(fn_name);
}
/**
* @returns {Promise<nopaque.corpus_analysis.cqi.status.StatusPingOk>}
*/
async ctrl_ping() {
const fn_name = 'ctrl_ping';
let payload = await this.#request(fn_name);
return new nopaque.corpus_analysis.cqi.status.lookup[payload.code]();
}
/**
* Full-text error message for the last general error reported
* by the CQi server
*
* @returns {Promise<string>}
*/
async ctrl_last_general_error() {
const fn_name = 'ctrl_last_general_error';
return await this.#request(fn_name);
}
/**
* @returns {Promise<boolean>}
*/
async ask_feature_cqi_1_0() {
const fn_name = 'ask_feature_cqi_1_0';
return await this.#request(fn_name);
}
/**
* @returns {Promise<boolean>}
*/
async ask_feature_cl_2_3() {
const fn_name = 'ask_feature_cl_2_3';
return await this.#request(fn_name);
}
/**
* @returns {Promise<boolean>}
*/
async ask_feature_cqp_2_3() {
const fn_name = 'ask_feature_cqp_2_3';
return await this.#request(fn_name);
}
/**
* @returns {Promise<string[]>}
*/
async corpus_list_corpora() {
const fn_name = 'corpus_list_corpora';
return await this.#request(fn_name);
}
/**
* @param {string} corpus
* @returns {Promise<string>}
*/
async corpus_charset(corpus) {
const fn_name = 'corpus_charset';
const fn_args = {corpus: corpus};
return await this.#request(fn_name, fn_args);
}
/**
* @param {string} corpus
* @returns {Promise<string[]>}
*/
async corpus_properties(corpus) {
const fn_name = 'corpus_properties';
const fn_args = {corpus: corpus};
return await this.#request(fn_name, fn_args);
}
/**
* @param {string} corpus
* @returns {Promise<string[]>}
*/
async corpus_positional_attributes(corpus) {
const fn_name = 'corpus_positional_attributes';
const fn_args = {corpus: corpus};
return await this.#request(fn_name, fn_args);
}
/**
* @param {string} corpus
* @returns {Promise<string[]>}
*/
async corpus_structural_attributes(corpus) {
const fn_name = 'corpus_structural_attributes';
const fn_args = {corpus: corpus};
return await this.#request(fn_name, fn_args);
}
/**
* @param {string} corpus
* @param {string} attribute
* @returns {Promise<boolean>}
*/
async corpus_structural_attribute_has_values(corpus, attribute) {
const fn_name = 'corpus_structural_attribute_has_values';
const fn_args = {corpus: corpus, attribute: attribute};
return await this.#request(fn_name, fn_args);
}
/**
* @param {string} corpus
* @returns {Promise<string[]>}
*/
async corpus_alignment_attributes(corpus) {
const fn_name = 'corpus_alignment_attributes';
const fn_args = {corpus: corpus};
return await this.#request(fn_name, fn_args);
}
/**
* the full name of <corpus> as specified in its registry entry
*
* @param {string} corpus
* @returns {Promise<string>}
*/
async corpus_full_name(corpus) {
const fn_name = 'corpus_full_name';
const fn_args = {corpus: corpus};
return await this.#request(fn_name, fn_args);
}
/**
* returns the contents of the .info file of <corpus> as a list of lines
*
* @param {string} corpus
* @returns {Promise<string[]>}
*/
async corpus_info(corpus) {
const fn_name = 'corpus_info';
const fn_args = {corpus: corpus};
return await this.#request(fn_name, fn_args);
}
/**
* try to unload a corpus and all its attributes from memory
*
* @param {string} corpus
* @returns {Promise<nopaque.corpus_analysis.cqi.status.StatusOk>}
*/
async corpus_drop_corpus(corpus) {
const fn_name = 'corpus_drop_corpus';
const fn_args = {corpus: corpus};
let payload = await this.#request(fn_name, fn_args);
return new nopaque.corpus_analysis.cqi.status.lookup[payload.code]();
}
/**
* returns the size of <attribute>:
* - number of tokens (positional)
* - number of regions (structural)
* - number of alignments (alignment)
*
* @param {string} attribute
* @returns {Promise<number>}
*/
async cl_attribute_size(attribute) {
const fn_name = 'cl_attribute_size';
const fn_args = {attribute: attribute};
return await this.#request(fn_name, fn_args);
}
/**
* returns the number of entries in the lexicon of a positional attribute;
*
* valid lexicon IDs range from 0 .. (lexicon_size - 1)
*
* @param {string} attribute
* @returns {Promise<number>}
*/
async cl_lexicon_size(attribute) {
const fn_name = 'cl_lexicon_size';
const fn_args = {attribute: attribute};
return await this.#request(fn_name, fn_args);
}
/**
* unload attribute from memory
*
* @param {string} attribute
* @returns {Promise<nopaque.corpus_analysis.cqi.status.StatusOk>}
*/
async cl_drop_attribute(attribute) {
const fn_name = 'cl_drop_attribute';
const fn_args = {attribute: attribute};
let payload = await this.#request(fn_name, fn_args);
return new nopaque.corpus_analysis.cqi.status.lookup[payload.code]();
}
/**
* NOTE: simple (scalar) mappings are applied to lists (the returned list
* has exactly the same length as the list passed as an argument)
*/
/**
* returns -1 for every string in <strings> that is not found in the lexicon
*
* @param {string} attribute
* @param {strings[]} string
* @returns {Promise<number[]>}
*/
async cl_str2id(attribute, strings) {
const fn_name = 'cl_str2id';
const fn_args = {attribute: attribute, strings: strings};
return await this.#request(fn_name, fn_args);
}
/**
* returns "" for every ID in <id> that is out of range
*
* @param {string} attribute
* @param {number[]} id
* @returns {Promise<string[]>}
*/
async cl_id2str(attribute, id) {
const fn_name = 'cl_id2str';
const fn_args = {attribute: attribute, id: id};
return await this.#request(fn_name, fn_args);
}
/**
* returns 0 for every ID in <id> that is out of range
*
* @param {string} attribute
* @param {number[]} id
* @returns {Promise<number[]>}
*/
async cl_id2freq(attribute, id) {
const fn_name = 'cl_id2freq';
const fn_args = {attribute: attribute, id: id};
return await this.#request(fn_name, fn_args);
}
/**
* returns -1 for every corpus position in <cpos> that is out of range
*
* @param {string} attribute
* @param {number[]} cpos
* @returns {Promise<number[]>}
*/
async cl_cpos2id(attribute, cpos) {
const fn_name = 'cl_cpos2id';
const fn_args = {attribute: attribute, cpos: cpos};
return await this.#request(fn_name, fn_args);
}
/**
* returns "" for every corpus position in <cpos> that is out of range
*
* @param {string} attribute
* @param {number[]} cpos
* @returns {Promise<string[]>}
*/
async cl_cpos2str(attribute, cpos) {
const fn_name = 'cl_cpos2str';
const fn_args = {attribute: attribute, cpos: cpos};
return await this.#request(fn_name, fn_args);
}
/**
* returns -1 for every corpus position not inside a structure region
*
* @param {string} attribute
* @param {number[]} cpos
* @returns {Promise<number[]>}
*/
async cl_cpos2struc(attribute, cpos) {
const fn_name = 'cl_cpos2struc';
const fn_args = {attribute: attribute, cpos: cpos};
return await this.#request(fn_name, fn_args);
}
/**
* NOTE: temporary addition for the Euralex2000 tutorial, but should
* probably be included in CQi specs
*/
/**
* returns left boundary of s-attribute region enclosing cpos,
* -1 if not in region
*
* @param {string} attribute
* @param {number[]} cpos
* @returns {Promise<number[]>}
*/
async cl_cpos2lbound(attribute, cpos) {
const fn_name = 'cl_cpos2lbound';
const fn_args = {attribute: attribute, cpos: cpos};
return await this.#request(fn_name, fn_args);
}
/**
* returns right boundary of s-attribute region enclosing cpos,
* -1 if not in region
*
* @param {string} attribute
* @param {number[]} cpos
* @returns {Promise<number[]>}
*/
async cl_cpos2rbound(attribute, cpos) {
const fn_name = 'cl_cpos2rbound';
const fn_args = {attribute: attribute, cpos: cpos};
return await this.#request(fn_name, fn_args);
}
/**
* returns -1 for every corpus position not inside an alignment
*
* @param {string} attribute
* @param {number[]} cpos
* @returns {Promise<number[]>}
*/
async cl_cpos2alg(attribute, cpos) {
const fn_name = 'cl_cpos2alg';
const fn_args = {attribute: attribute, cpos: cpos};
return await this.#request(fn_name, fn_args);
}
/**
* returns annotated string values of structure regions in <strucs>;
* "" if out of range
*
* check corpus_structural_attribute_has_values(<attribute>) first
*
* @param {string} attribute
* @param {number[]} strucs
* @returns {Promise<string[]>}
*/
async cl_struc2str(attribute, strucs) {
const fn_name = 'cl_struc2str';
const fn_args = {attribute: attribute, strucs: strucs};
return await this.#request(fn_name, fn_args);
}
/**
* NOTE: the following mappings take a single argument and return multiple
* values, including lists of arbitrary size
*/
/**
* returns all corpus positions where the given token occurs
*
* @param {string} attribute
* @param {number} id
* @returns {Promise<number[]>}
*/
async cl_id2cpos(attribute, id) {
const fn_name = 'cl_id2cpos';
const fn_args = {attribute: attribute, id: id};
return await this.#request(fn_name, fn_args);
}
/**
* returns all corpus positions where one of the tokens in <id_list> occurs;
* the returned list is sorted as a whole, not per token id
*
* @param {string} attribute
* @param {number[]} id_list
* @returns {Promise<number[]>}
*/
async cl_idlist2cpos(attribute, id_list) {
const fn_name = 'cl_idlist2cpos';
const fn_args = {attribute: attribute, id_list: id_list};
return await this.#request(fn_name, fn_args);
}
/**
* returns lexicon IDs of all tokens that match <regex>;
* the returned list may be empty (size 0);
*
* @param {string} attribute
* @param {string} regex
* @returns {Promise<number[]>}
*/
async cl_regex2id(attribute, regex) {
const fn_name = 'cl_regex2id';
const fn_args = {attribute: attribute, regex: regex};
return await this.#request(fn_name, fn_args);
}
/**
* returns start and end corpus positions of structure region <struc>
*
* @param {string} attribute
* @param {number} struc
* @returns {Promise<[number, number]>}
*/
async cl_struc2cpos(attribute, struc) {
const fn_name = 'cl_struc2cpos';
const fn_args = {attribute: attribute, struc: struc};
return await this.#request(fn_name, fn_args);
}
/**
* returns (src_start, src_end, target_start, target_end)
*
* @param {string} attribute
* @param {number} alg
* @returns {Promise<[number, number, number, number]>}
*/
async alg2cpos(attribute, alg) {
const fn_name = 'alg2cpos';
const fn_args = {attribute: attribute, alg: alg};
return await this.#request(fn_name, fn_args);
}
/**
* <query> must include the ';' character terminating the query.
*
* @param {string} mother_corpus
* @param {string} subcorpus_name
* @param {string} query
* @returns {Promise<nopaque.corpus_analysis.cqi.status.StatusOk>}
*/
async cqp_query(mother_corpus, subcorpus_name, query) {
const fn_name = 'cqp_query';
const fn_args = {mother_corpus: mother_corpus, subcorpus_name: subcorpus_name, query: query};
let payload = await this.#request(fn_name, fn_args);
return new nopaque.corpus_analysis.cqi.status.lookup[payload.code]();
}
/**
* @param {string} corpus
* @returns {Promise<string[]>}
*/
async cqp_list_subcorpora(corpus) {
const fn_name = 'cqp_list_subcorpora';
const fn_args = {corpus: corpus};
return await this.#request(fn_name, fn_args);
}
/**
* @param {string} subcorpus
* @returns {Promise<number>}
*/
async cqp_subcorpus_size(subcorpus) {
const fn_name = 'cqp_subcorpus_size';
const fn_args = {subcorpus: subcorpus};
return await this.#request(fn_name, fn_args);
}
/**
* @param {string} subcorpus
* @param {number} field
* @returns {Promise<boolean>}
*/
async cqp_subcorpus_has_field(subcorpus, field) {
const fn_name = 'cqp_subcorpus_has_field';
const fn_args = {subcorpus: subcorpus, field: field};
return await this.#request(fn_name, fn_args);
}
/**
* Dump the values of <field> for match ranges <first> .. <last>
* in <subcorpus>. <field> is one of the nopaque.corpus_analysis.cqi.constants.FIELD_* constants.
*
* @param {string} subcorpus
* @param {number} field
* @param {number} first
* @param {number} last
* @returns {Promise<number[]>}
*/
async cqp_dump_subcorpus(subcorpus, field, first, last) {
const fn_name = 'cqp_dump_subcorpus';
const fn_args = {subcorpus: subcorpus, field: field, first: first, last: last};
return await this.#request(fn_name, fn_args);
}
/**
* delete a subcorpus from memory
*
* @param {string} subcorpus
* @returns {Promise<nopaque.corpus_analysis.cqi.status.StatusOk>}
*/
async cqp_drop_subcorpus(subcorpus) {
const fn_name = 'cqp_drop_subcorpus';
const fn_args = {subcorpus: subcorpus};
let payload = await this.#request(fn_name, fn_args);
return new nopaque.corpus_analysis.cqi.status.lookup[payload.code]();
}
/**
* NOTE: The following two functions are temporarily included for the
* Euralex 2000 tutorial demo
*/
/**
* frequency distribution of single tokens
*
* returns <n> (id, frequency) pairs flattened into a list of size 2*<n>
* field is one of
* - nopaque.corpus_analysis.cqi.constants.FIELD_MATCH
* - nopaque.corpus_analysis.cqi.constants.FIELD_TARGET
* - nopaque.corpus_analysis.cqi.constants.FIELD_KEYWORD
*
* NB: pairs are sorted by frequency desc.
*
* @param {string} subcorpus
* @param {number} cutoff
* @param {number} field
* @param {string} attribute
* @returns {Promise<number[]>}
*/
async cqp_fdist_1(subcorpus, cutoff, field, attribute) {
const fn_name = 'cqp_fdist_1';
const fn_args = {subcorpus: subcorpus, cutoff: cutoff, field: field, attribute: attribute};
return await this.#request(fn_name, fn_args);
}
/**
* frequency distribution of pairs of tokens
*
* returns <n> (id1, id2, frequency) pairs flattened into a list of
* size 3*<n>
*
* NB: triples are sorted by frequency desc.
*
* @param {string} subcorpus
* @param {number} cutoff
* @param {number} field1
* @param {string} attribute1
* @param {number} field2
* @param {string} attribute2
* @returns {Promise<number[]>}
*/
async cqp_fdist_2(subcorpus, cutoff, field1, attribute1, field2, attribute2) {
const fn_name = 'cqp_fdist_2';
const fn_args = {subcorpus: subcorpus, cutoff: cutoff, field1: field1, attribute1: attribute1, field2: field2, attribute2: attribute2};
return await this.#request(fn_name, fn_args);
}
/**************************************************************************
* NOTE: The following is not included in the CQi specification. *
**************************************************************************/
/**************************************************************************
* Custom additions for nopaque *
**************************************************************************/
/**
* @param {string} corpus
* @returns {Promise<nopaque.corpus_analysis.cqi.status.StatusOk>}
*/
async ext_corpus_update_db(corpus) {
const fn_name = 'ext_corpus_update_db';
const fn_args = {corpus: corpus};
let payload = await this.#request(fn_name, fn_args);
return new nopaque.corpus_analysis.cqi.status.lookup[payload.code]();
}
/**
* @param {string} corpus
* @returns {Promise<object>}
*/
async ext_corpus_static_data(corpus) {
const fn_name = 'ext_corpus_static_data';
const fn_args = {corpus: corpus};
let compressedEncodedData = await this.#request(fn_name, fn_args);
let data = pako.inflate(compressedEncodedData, {to: 'string'});
return JSON.parse(data);
}
/**
* @param {string} corpus
* @param {number=} page
* @param {number=} per_page
* @returns {Promise<object>}
*/
async ext_corpus_paginate_corpus(corpus, page, per_page) {
const fn_name = 'ext_corpus_paginate_corpus';
const fn_args = {corpus: corpus}
if (page !== undefined) {fn_args.page = page;}
if (per_page !== undefined) {fn_args.per_page = per_page;}
return await this.#request(fn_name, fn_args);
}
/**
* @param {string} subcorpus
* @param {number=} context
* @param {number=} page
* @param {number=} per_page
* @returns {Promise<object>}
*/
async ext_cqp_paginate_subcorpus(subcorpus, context, page, per_page) {
const fn_name = 'ext_cqp_paginate_subcorpus';
const fn_args = {subcorpus: subcorpus}
if (context !== undefined) {fn_args.context = context;}
if (page !== undefined) {fn_args.page = page;}
if (per_page !== undefined) {fn_args.per_page = per_page;}
return await this.#request(fn_name, fn_args);
}
/**
* @param {string} subcorpus
* @param {number[]} match_id_list
* @param {number=} context
* @returns {Promise<object>}
*/
async ext_cqp_partial_export_subcorpus(subcorpus, match_id_list, context) {
const fn_name = 'ext_cqp_partial_export_subcorpus';
const fn_args = {subcorpus: subcorpus, match_id_list: match_id_list};
if (context !== undefined) {fn_args.context = context;}
return await this.#request(fn_name, fn_args);
}
/**
* @param {string} subcorpus
* @param {number=} context
* @returns {Promise<object>}
*/
async ext_cqp_export_subcorpus(subcorpus, context) {
const fn_name = 'ext_cqp_export_subcorpus';
const fn_args = {subcorpus: subcorpus};
if (context !== undefined) {fn_args.context = context;}
return await this.#request(fn_name, fn_args);
}
};

View File

@ -1 +0,0 @@
nopaque.corpus_analysis.cqi.api = {};

View File

@ -1,57 +0,0 @@
nopaque.corpus_analysis.cqi.Client = class Client {
/**
* @param {string} host
* @param {number} [timeout=60] timeout
* @param {string} [version=0.1] version
*/
constructor(host, timeout = 60, version = '0.1') {
/** @type {nopaque.corpus_analysis.cqi.api.Client} */
this.api = new nopaque.corpus_analysis.cqi.api.Client(host, timeout, version);
}
/**
* @returns {nopaque.corpus_analysis.cqi.models.corpora.CorpusCollection}
*/
get corpora() {
return new nopaque.corpus_analysis.cqi.models.corpora.CorpusCollection(this);
}
/**
* @returns {Promise<nopaque.corpus_analysis.cqi.status.StatusByeOk>}
*/
async bye() {
return await this.api.ctrl_bye();
}
/**
* @param {string} username
* @param {string} password
* @returns {Promise<nopaque.corpus_analysis.cqi.status.StatusConnectOk>}
*/
async connect(username, password) {
return await this.api.ctrl_connect(username, password);
}
/**
* @returns {Promise<nopaque.corpus_analysis.cqi.status.StatusPingOk>}
*/
async ping() {
return await this.api.ctrl_ping();
}
/**
* @returns {Promise<null>}
*/
async userAbort() {
return await this.api.ctrl_user_abort();
}
/**
* Alias for "bye" method
*
* @returns {Promise<nopaque.corpus_analysis.cqi.status.StatusByeOk>}
*/
async disconnect() {
return await this.api.ctrl_bye();
}
};

View File

@ -1,43 +0,0 @@
nopaque.corpus_analysis.cqi.constants = {};
/** @type {number} */
nopaque.corpus_analysis.cqi.constants.FIELD_KEYWORD = 9;
/** @type {number} */
nopaque.corpus_analysis.cqi.constants.FIELD_MATCH = 16;
/** @type {number} */
nopaque.corpus_analysis.cqi.constants.FIELD_MATCHEND = 17;
/** @type {number} */
nopaque.corpus_analysis.cqi.constants.FIELD_TARGET = 0;
/** @type {number} */
nopaque.corpus_analysis.cqi.constants.FIELD_TARGET_0 = 0;
/** @type {number} */
nopaque.corpus_analysis.cqi.constants.FIELD_TARGET_1 = 1;
/** @type {number} */
nopaque.corpus_analysis.cqi.constants.FIELD_TARGET_2 = 2;
/** @type {number} */
nopaque.corpus_analysis.cqi.constants.FIELD_TARGET_3 = 3;
/** @type {number} */
nopaque.corpus_analysis.cqi.constants.FIELD_TARGET_4 = 4;
/** @type {number} */
nopaque.corpus_analysis.cqi.constants.FIELD_TARGET_5 = 5;
/** @type {number} */
nopaque.corpus_analysis.cqi.constants.FIELD_TARGET_6 = 6;
/** @type {number} */
nopaque.corpus_analysis.cqi.constants.FIELD_TARGET_7 = 7;
/** @type {number} */
nopaque.corpus_analysis.cqi.constants.FIELD_TARGET_8 = 8;
/** @type {number} */
nopaque.corpus_analysis.cqi.constants.FIELD_TARGET_9 = 9;

View File

@ -1,185 +0,0 @@
nopaque.corpus_analysis.cqi.errors = {};
/**
* A base class from which all other errors inherit.
* If you want to catch all errors that the CQi package might throw,
* catch this base error.
*/
nopaque.corpus_analysis.cqi.errors.CQiError = class CQiError extends Error {
constructor(message) {
super(message);
this.code = undefined;
this.description = undefined;
}
};
nopaque.corpus_analysis.cqi.errors.Error = class Error extends nopaque.corpus_analysis.cqi.errors.CQiError {
constructor(message) {
super(message);
this.code = 2;
}
};
nopaque.corpus_analysis.cqi.errors.ErrorGeneralError = class ErrorGeneralError extends nopaque.corpus_analysis.cqi.errors.Error {
constructor(message) {
super(message);
this.code = 513;
}
};
nopaque.corpus_analysis.cqi.errors.ErrorConnectRefused = class ErrorConnectRefused extends nopaque.corpus_analysis.cqi.errors.Error {
constructor(message) {
super(message);
this.code = 514;
}
};
nopaque.corpus_analysis.cqi.errors.ErrorUserAbort = class ErrorUserAbort extends nopaque.corpus_analysis.cqi.errors.Error {
constructor(message) {
super(message);
this.code = 515;
}
};
nopaque.corpus_analysis.cqi.errors.ErrorSyntaxError = class ErrorSyntaxError extends nopaque.corpus_analysis.cqi.errors.Error {
constructor(message) {
super(message);
this.code = 516;
}
};
nopaque.corpus_analysis.cqi.errors.CLError = class Error extends nopaque.corpus_analysis.cqi.errors.CQiError {
constructor(message) {
super(message);
this.code = 4;
}
};
nopaque.corpus_analysis.cqi.errors.CLErrorNoSuchAttribute = class CLErrorNoSuchAttribute extends nopaque.corpus_analysis.cqi.errors.CLError {
constructor(message) {
super(message);
this.code = 1025;
this.description = "CQi server couldn't open attribute";
}
};
nopaque.corpus_analysis.cqi.errors.CLErrorWrongAttributeType = class CLErrorWrongAttributeType extends nopaque.corpus_analysis.cqi.errors.CLError {
constructor(message) {
super(message);
this.code = 1026;
}
};
nopaque.corpus_analysis.cqi.errors.CLErrorOutOfRange = class CLErrorOutOfRange extends nopaque.corpus_analysis.cqi.errors.CLError {
constructor(message) {
super(message);
this.code = 1027;
}
};
nopaque.corpus_analysis.cqi.errors.CLErrorRegex = class CLErrorRegex extends nopaque.corpus_analysis.cqi.errors.CLError {
constructor(message) {
super(message);
this.code = 1028;
}
};
nopaque.corpus_analysis.cqi.errors.CLErrorCorpusAccess = class CLErrorCorpusAccess extends nopaque.corpus_analysis.cqi.errors.CLError {
constructor(message) {
super(message);
this.code = 1029;
}
};
nopaque.corpus_analysis.cqi.errors.CLErrorOutOfMemory = class CLErrorOutOfMemory extends nopaque.corpus_analysis.cqi.errors.CLError {
constructor(message) {
super(message);
this.code = 1030;
this.description = 'CQi server has run out of memory; try discarding some other corpora and/or subcorpora';
}
};
nopaque.corpus_analysis.cqi.errors.CLErrorInternal = class CLErrorInternal extends nopaque.corpus_analysis.cqi.errors.CLError {
constructor(message) {
super(message);
this.code = 1031;
this.description = "The classical 'please contact technical support' error";
}
};
nopaque.corpus_analysis.cqi.errors.CQPError = class Error extends nopaque.corpus_analysis.cqi.errors.CQiError {
constructor(message) {
super(message);
this.code = 5;
}
};
nopaque.corpus_analysis.cqi.errors.CQPErrorGeneral = class CQPErrorGeneral extends nopaque.corpus_analysis.cqi.errors.CQPError {
constructor(message) {
super(message);
this.code = 1281;
}
};
nopaque.corpus_analysis.cqi.errors.CQPErrorNoSuchCorpus = class CQPErrorNoSuchCorpus extends nopaque.corpus_analysis.cqi.errors.CQPError {
constructor(message) {
super(message);
this.code = 1282;
}
};
nopaque.corpus_analysis.cqi.errors.CQPErrorInvalidField = class CQPErrorInvalidField extends nopaque.corpus_analysis.cqi.errors.CQPError {
constructor(message) {
super(message);
this.code = 1283;
}
};
nopaque.corpus_analysis.cqi.errors.CQPErrorOutOfRange = class CQPErrorOutOfRange extends nopaque.corpus_analysis.cqi.errors.CQPError {
constructor(message) {
super(message);
this.code = 1284;
this.description = 'A number is out of range';
}
};
nopaque.corpus_analysis.cqi.errors.lookup = {
2: nopaque.corpus_analysis.cqi.errors.Error,
513: nopaque.corpus_analysis.cqi.errors.ErrorGeneralError,
514: nopaque.corpus_analysis.cqi.errors.ErrorConnectRefused,
515: nopaque.corpus_analysis.cqi.errors.ErrorUserAbort,
516: nopaque.corpus_analysis.cqi.errors.ErrorSyntaxError,
4: nopaque.corpus_analysis.cqi.errors.CLError,
1025: nopaque.corpus_analysis.cqi.errors.CLErrorNoSuchAttribute,
1026: nopaque.corpus_analysis.cqi.errors.CLErrorWrongAttributeType,
1027: nopaque.corpus_analysis.cqi.errors.CLErrorOutOfRange,
1028: nopaque.corpus_analysis.cqi.errors.CLErrorRegex,
1029: nopaque.corpus_analysis.cqi.errors.CLErrorCorpusAccess,
1030: nopaque.corpus_analysis.cqi.errors.CLErrorOutOfMemory,
1031: nopaque.corpus_analysis.cqi.errors.CLErrorInternal,
5: nopaque.corpus_analysis.cqi.errors.CQPError,
1281: nopaque.corpus_analysis.cqi.errors.CQPErrorGeneral,
1282: nopaque.corpus_analysis.cqi.errors.CQPErrorNoSuchCorpus,
1283: nopaque.corpus_analysis.cqi.errors.CQPErrorInvalidField,
1284: nopaque.corpus_analysis.cqi.errors.CQPErrorOutOfRange
};

View File

@ -1 +0,0 @@
nopaque.corpus_analysis.cqi = {};

View File

@ -1,289 +0,0 @@
nopaque.corpus_analysis.cqi.models.attributes = {};
nopaque.corpus_analysis.cqi.models.attributes.Attribute = class Attribute extends nopaque.corpus_analysis.cqi.models.resource.Model {
/**
* @returns {string}
*/
get apiName() {
return this.attrs.api_name;
}
/**
* @returns {string}
*/
get name() {
return this.attrs.name;
}
/**
* @returns {number}
*/
get size() {
return this.attrs.size;
}
/**
* @returns {Promise<nopaque.corpus_analysis.cqi.status.StatusOk>}
*/
async drop() {
return await this.client.api.cl_drop_attribute(this.apiName);
}
};
nopaque.corpus_analysis.cqi.models.attributes.AttributeCollection = class AttributeCollection extends nopaque.corpus_analysis.cqi.models.resource.Collection {
/** @type{typeof nopaque.corpus_analysis.cqi.models.attributes.Attribute} */
static model = nopaque.corpus_analysis.cqi.models.attributes.Attribute;
/**
* @param {nopaque.corpus_analysis.cqi.Client} client
* @param {nopaque.corpus_analysis.cqi.models.corpora.Corpus} corpus
*/
constructor(client, corpus) {
super(client);
/** @type {nopaque.corpus_analysis.cqi.models.corpora.Corpus} */
this.corpus = corpus;
}
/**
* @param {string} attributeName
* @returns {Promise<object>}
*/
async _get(attributeName) {
/** @type{string} */
let apiName = `${this.corpus.apiName}.${attributeName}`;
return {
api_name: apiName,
name: attributeName,
size: await this.client.api.cl_attribute_size(apiName)
}
}
/**
* @param {string} attributeName
* @returns {Promise<nopaque.corpus_analysis.cqi.models.attributes.Attribute>}
*/
async get(attributeName) {
return this.prepareModel(await this._get(attributeName));
}
};
nopaque.corpus_analysis.cqi.models.attributes.AlignmentAttribute = class AlignmentAttribute extends nopaque.corpus_analysis.cqi.models.attributes.Attribute {
/**
* @param {number} id
* @returns {Promise<[number, number, number, number]>}
*/
async cposById(id) {
return await this.client.api.cl_alg2cpos(this.apiName, id);
}
/**
* @param {number[]} cposList
* @returns {Promise<number[]>}
*/
async idsByCpos(cposList) {
return await this.client.api.cl_cpos2alg(this.apiName, cposList);
}
};
nopaque.corpus_analysis.cqi.models.attributes.AlignmentAttributeCollection = class AlignmentAttributeCollection extends nopaque.corpus_analysis.cqi.models.attributes.AttributeCollection {
/** @type{typeof nopaque.corpus_analysis.cqi.models.attributes.AlignmentAttribute} */
static model = nopaque.corpus_analysis.cqi.models.attributes.AlignmentAttribute;
/**
* @returns {Promise<nopaque.corpus_analysis.cqi.models.attributes.AlignmentAttribute[]>}
*/
async list() {
/** @type {string[]} */
let alignmentAttributeNames = await this.client.api.corpus_alignment_attributes(this.corpus.apiName);
/** @type {nopaque.corpus_analysis.cqi.models.attributes.AlignmentAttribute[]} */
let alignmentAttributes = [];
for (let alignmentAttributeName of alignmentAttributeNames) {
alignmentAttributes.push(await this.get(alignmentAttributeName));
}
return alignmentAttributes;
}
};
nopaque.corpus_analysis.cqi.models.attributes.PositionalAttribute = class PositionalAttribute extends nopaque.corpus_analysis.cqi.models.attributes.Attribute {
/**
* @returns {number}
*/
get lexiconSize() {
return this.attrs.lexicon_size;
}
/**
* @param {number} id
* @returns {Promise<number[]>}
*/
async cposById(id) {
return await this.client.api.cl_id2cpos(this.apiName, id);
}
/**
* @param {number[]} idList
* @returns {Promise<number[]>}
*/
async cposByIds(idList) {
return await this.client.api.cl_idlist2cpos(this.apiName, idList);
}
/**
* @param {number[]} idList
* @returns {Promise<number[]>}
*/
async freqsByIds(idList) {
return await this.client.api.cl_id2freq(this.apiName, idList);
}
/**
* @param {number[]} cposList
* @returns {Promise<number[]>}
*/
async idsByCpos(cposList) {
return await this.client.api.cl_cpos2id(this.apiName, cposList);
}
/**
* @param {string} regex
* @returns {Promise<number[]>}
*/
async idsByRegex(regex) {
return await this.client.api.cl_regex2id(this.apiName, regex);
}
/**
* @param {string[]} valueList
* @returns {Promise<number[]>}
*/
async idsByValues(valueList) {
return await this.client.api.cl_str2id(this.apiName, valueList);
}
/**
* @param {number[]} cposList
* @returns {Promise<string[]>}
*/
async valuesByCpos(cposList) {
return await this.client.api.cl_cpos2str(this.apiName, cposList);
}
/**
* @param {number[]} idList
* @returns {Promise<string[]>}
*/
async valuesByIds(idList) {
return await this.client.api.cl_id2str(this.apiName, idList);
}
};
nopaque.corpus_analysis.cqi.models.attributes.PositionalAttributeCollection = class PositionalAttributeCollection extends nopaque.corpus_analysis.cqi.models.attributes.AttributeCollection {
/** @type{typeof nopaque.corpus_analysis.cqi.models.attributes.PositionalAttribute} */
static model = nopaque.corpus_analysis.cqi.models.attributes.PositionalAttribute;
/**
* @param {string} positionalAttributeName
* @returns {Promise<object>}
*/
async _get(positionalAttributeName) {
let positionalAttribute = await super._get(positionalAttributeName);
positionalAttribute.lexicon_size = await this.client.api.cl_lexicon_size(positionalAttribute.api_name);
return positionalAttribute;
}
/**
* @returns {Promise<nopaque.corpus_analysis.cqi.models.attributes.PositionalAttribute[]>}
*/
async list() {
let positionalAttributeNames = await this.client.api.corpus_positional_attributes(this.corpus.apiName);
let positionalAttributes = [];
for (let positionalAttributeName of positionalAttributeNames) {
positionalAttributes.push(await this.get(positionalAttributeName));
}
return positionalAttributes;
}
};
nopaque.corpus_analysis.cqi.models.attributes.StructuralAttribute = class StructuralAttribute extends nopaque.corpus_analysis.cqi.models.attributes.Attribute {
/**
* @returns {boolean}
*/
get hasValues() {
return this.attrs.has_values;
}
/**
* @param {number} id
* @returns {Promise<[number, number]>}
*/
async cposById(id) {
return await this.client.api.cl_struc2cpos(this.apiName, id);
}
/**
* @param {number[]} cposList
* @returns {Promise<number[]>}
*/
async idsByCpos(cposList) {
return await this.client.api.cl_cpos2struc(this.apiName, cposList);
}
/**
* @param {number[]} cposList
* @returns {Promise<number[]>}
*/
async lboundByCpos(cposList) {
return await this.client.api.cl_cpos2lbound(this.apiName, cposList);
}
/**
* @param {number[]} cposList
* @returns {Promise<number[]>}
*/
async rboundByCpos(cposList) {
return await this.client.api.cl_cpos2rbound(this.apiName, cposList);
}
/**
* @param {number[]} idList
* @returns {Promise<string[]>}
*/
async valuesByIds(idList) {
return await this.client.api.cl_struc2str(this.apiName, idList);
}
};
nopaque.corpus_analysis.cqi.models.attributes.StructuralAttributeCollection = class StructuralAttributeCollection extends nopaque.corpus_analysis.cqi.models.attributes.AttributeCollection {
/** @type{typeof nopaque.corpus_analysis.cqi.models.attributes.StructuralAttribute} */
static model = nopaque.corpus_analysis.cqi.models.attributes.StructuralAttribute;
/**
* @param {string} structuralAttributeName
* @returns {Promise<object>}
*/
async _get(structuralAttributeName) {
let structuralAttribute = await super._get(structuralAttributeName);
structuralAttribute.has_values = await this.client.api.cl_has_values(structuralAttribute.api_name);
return structuralAttribute;
}
/**
* @returns {Promise<nopaque.corpus_analysis.cqi.models.attributes.StructuralAttribute[]>}
*/
async list() {
let structuralAttributeNames = await this.client.api.corpus_structural_attributes(this.corpus.apiName);
let structuralAttributes = [];
for (let structuralAttributeName of structuralAttributeNames) {
structuralAttributes.push(await this.get(structuralAttributeName));
}
return structuralAttributes;
}
};

View File

@ -1,166 +0,0 @@
nopaque.corpus_analysis.cqi.models.corpora = {};
nopaque.corpus_analysis.cqi.models.corpora.Corpus = class Corpus extends nopaque.corpus_analysis.cqi.models.resource.Model {
/**
* @returns {string}
*/
get apiName() {
return this.attrs.api_name;
}
/**
* @returns {string}
*/
get name() {
return this.attrs.name;
}
/**
* @returns {number}
*/
get size() {
return this.attrs.size;
}
/**
* @returns {string}
*/
get charset() {
return this.attrs.charset;
}
/**
* @returns {string[]}
*/
get properties() {
return this.attrs?.properties;
}
/**
* @returns {nopaque.corpus_analysis.cqi.models.attributes.AlignmentAttributeCollection}
*/
get alignmentAttributes() {
return new nopaque.corpus_analysis.cqi.models.attributes.AlignmentAttributeCollection(this.client, this);
}
/**
* @returns {nopaque.corpus_analysis.cqi.models.attributes.PositionalAttributeCollection}
*/
get positionalAttributes() {
return new nopaque.corpus_analysis.cqi.models.attributes.PositionalAttributeCollection(this.client, this);
}
/**
* @returns {nopaque.corpus_analysis.cqi.models.attributes.StructuralAttributeCollection}
*/
get structuralAttributes() {
return new nopaque.corpus_analysis.cqi.models.attributes.StructuralAttributeCollection(this.client, this);
}
/**
* @returns {nopaque.corpus_analysis.cqi.models.subcorpora.SubcorpusCollection}
*/
get subcorpora() {
return new nopaque.corpus_analysis.cqi.models.subcorpora.SubcorpusCollection(this.client, this);
}
/**
* @returns {Promise<nopaque.corpus_analysis.cqi.status.StatusOk>}
*/
async drop() {
return await this.client.api.corpus_drop_corpus(this.apiName);
}
/**
* @param {string} subcorpusName
* @param {string} query
* @returns {Promise<nopaque.corpus_analysis.cqi.status.StatusOk>}
*/
async query(subcorpusName, query) {
return await this.client.api.cqp_query(this.apiName, subcorpusName, query);
}
/**************************************************************************
* NOTE: The following is not included in the CQi specification. *
**************************************************************************/
/**************************************************************************
* Custom additions for nopaque *
**************************************************************************/
/**
* @returns {string}
*/
get staticData() {
return this.attrs.static_data;
}
/**
* @returns {nopaque.corpus_analysis.cqi.status.StatusOk}
*/
async updateDb() {
return await this.client.api.ext_corpus_update_db(this.apiName);
}
/**
* @param {number=} page
* @param {number=} per_page
* @returns {Promise<object>}
*/
async paginate(page, per_page) {
return await this.client.api.ext_corpus_paginate_corpus(this.apiName, page, per_page);
}
};
nopaque.corpus_analysis.cqi.models.corpora.CorpusCollection = class CorpusCollection extends nopaque.corpus_analysis.cqi.models.resource.Collection {
/** @type {typeof nopaque.corpus_analysis.cqi.models.corpora.Corpus} */
static model = nopaque.corpus_analysis.cqi.models.corpora.Corpus;
/**
* @param {string} corpusName
* @returns {Promise<object>}
*/
async _get(corpusName) {
const returnValue = {
api_name: corpusName,
charset: await this.client.api.corpus_charset(corpusName),
// full_name: await this.client.api.corpus_full_name(corpusName),
// info: await this.client.api.corpus_info(corpusName),
name: corpusName,
properties: await this.client.api.corpus_properties(corpusName),
size: await this.client.api.cl_attribute_size(`${corpusName}.word`)
};
/************************************************************************
* NOTE: The following is not included in the CQi specification. *
************************************************************************/
/************************************************************************
* Custom additions for nopaque *
************************************************************************/
returnValue.static_data = await this.client.api.ext_corpus_static_data(corpusName);
return returnValue;
}
/**
* @param {string} corpusName
* @returns {Promise<nopaque.corpus_analysis.cqi.models.corpora.Corpus>}
*/
async get(corpusName) {
return this.prepareModel(await this._get(corpusName));
}
/**
* @returns {Promise<nopaque.corpus_analysis.cqi.models.corpora.Corpus[]>}
*/
async list() {
/** @type {string[]} */
let corpusNames = await this.client.api.corpus_list_corpora();
/** @type {nopaque.corpus_analysis.cqi.models.corpora.Corpus[]} */
let corpora = [];
for (let corpusName of corpusNames) {
corpora.push(await this.get(corpusName));
}
return corpora;
}
};

Some files were not shown because too many files have changed in this diff Show More