diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 000000000..288ac405c
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,21 @@
+name: Unit Testing
+
+on:
+ push:
+ branches: [ "master" ]
+ pull_request:
+ branches: [ "master" ]
+
+jobs:
+ test:
+ name: TYPO3
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install dependencies
+ run: Build/Test/runTests.sh -s composerInstall -t 11.5
+
+ - name: Run unit tests
+ run: Build/Test/runTests.sh -s unit
diff --git a/.gitignore b/.gitignore
index 4c5f4df22..2714fd7d8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,10 @@
+.cache
.idea/
.vscode/
.DS_Store
node_modules
*.css.map
+Build/Test/.env
Documentation-GENERATED-temp/
public/
vendor/
diff --git a/Build/Test/UnitTests.xml b/Build/Test/UnitTests.xml
new file mode 100644
index 000000000..3f6bfeceb
--- /dev/null
+++ b/Build/Test/UnitTests.xml
@@ -0,0 +1,31 @@
+
+
+
+
+ ../../Tests/Unit
+
+
+
+
+
+
+
diff --git a/Build/Test/docker-compose.yml b/Build/Test/docker-compose.yml
new file mode 100644
index 000000000..43b7a1fd3
--- /dev/null
+++ b/Build/Test/docker-compose.yml
@@ -0,0 +1,147 @@
+# Adopted/reduced from https://github.com/TYPO3/typo3/blob/608f238a8b7696a49a47e1e73ce8e2845455f0f5/Build/testing-docker/local/docker-compose.yml
+
+services:
+ mysql:
+ image: docker.io/mysql:${MYSQL_VERSION}
+ environment:
+ MYSQL_ROOT_PASSWORD: funcp
+ tmpfs:
+ - /var/lib/mysql/:rw,noexec,nosuid
+
+ mariadb:
+ image: docker.io/mariadb:${MARIADB_VERSION}
+ environment:
+ MYSQL_ROOT_PASSWORD: funcp
+ tmpfs:
+ - /var/lib/mysql/:rw,noexec,nosuid
+
+ web:
+ image: docker.io/typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
+ user: "${HOST_UID}:${HOST_GID}"
+ stop_grace_period: 1s
+ volumes:
+ - ${EXTENSIONS_ROOT}:/var/www/extensions/
+ - ${DFGVIEWER_ROOT}:${DFGVIEWER_ROOT}
+ extra_hosts:
+ - "host.docker.internal:host-gateway"
+ ports:
+ - "${SERVER_PORT}:${SERVER_PORT}"
+ # NOTE: For testing PageViewProxy, we need another web server web:8001 that
+ # can be requested from TYPO3 running on web:${SERVER_PORT}.
+ # Setting PHP_CLI_SERVER_WORKERS wouldn't seem to work consistently.
+ command: >
+ /bin/sh -c "
+ if [ ${PHP_XDEBUG_ON} -eq 0 ]; then
+ XDEBUG_MODE=\"off\" \
+ php -S web:${SERVER_PORT} -t ${DFGVIEWER_ROOT} ${DFGVIEWER_ROOT}/Tests/routeFunctionalInstance.php &
+ php -S web:8001 -t ${DFGVIEWER_ROOT}
+ else
+ XDEBUG_MODE=\"debug,develop\" \
+ XDEBUG_TRIGGER=\"foo\" \
+ XDEBUG_CONFIG=\"client_port=${PHP_XDEBUG_PORT} client_host=host.docker.internal\" \
+ php -S web:${SERVER_PORT} -t ${DFGVIEWER_ROOT} ${DFGVIEWER_ROOT}/Tests/routeFunctionalInstance.php &
+ php -S web:8001 -t ${DFGVIEWER_ROOT}
+ fi
+ "
+ composer_install:
+ image: docker.io/typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
+ user: "${HOST_UID}:${HOST_GID}"
+ volumes:
+ - ${EXTENSIONS_ROOT}:/var/www/extensions/
+ - ${DFGVIEWER_ROOT}:${DFGVIEWER_ROOT}
+ working_dir: ${DFGVIEWER_ROOT}
+ environment:
+ COMPOSER_CACHE_DIR: ".cache/composer"
+ command: >
+ /bin/sh -c "
+ if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
+ set -x
+ fi
+ if [ -z ${TYPO3_VERSION} ]; then
+ composer install --no-progress --no-interaction;
+ else
+ composer update --with=typo3/cms-core:^${TYPO3_VERSION} --no-progress --no-interaction;
+ fi
+ "
+ functional:
+ image: docker.io/typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
+ links:
+ - ${DBMS}
+ - web
+ user: "${HOST_UID}:${HOST_GID}"
+ volumes:
+ - ${EXTENSIONS_ROOT}:/var/www/extensions/
+ - ${DFGVIEWER_ROOT}:${DFGVIEWER_ROOT}
+ environment:
+ typo3DatabaseDriver: "${DATABASE_DRIVER}"
+ typo3DatabaseName: func_test
+ typo3DatabaseUsername: root
+ typo3DatabasePassword: funcp
+ typo3DatabaseHost: ${DBMS}
+ working_dir: ${DFGVIEWER_ROOT}
+ extra_hosts:
+ - "host.docker.internal:host-gateway"
+ command: >
+ /bin/sh -c "
+ if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
+ set -x
+ fi
+ echo Waiting for database start...;
+ while ! nc -z ${DBMS} 3306; do
+ sleep 1;
+ done;
+ echo Database is up;
+ echo Waiting for Solr start...;
+ while ! nc -z solr 8983; do
+ sleep 1;
+ done;
+ echo Solr is up;
+ if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
+ set -x
+ fi
+ php -v | grep '^PHP'
+ if [ ${PHPUNIT_WATCH} -eq 0 ]; then
+ PHPUNIT_BIN=\"vendor/bin/phpunit\"
+ else
+ PHPUNIT_BIN=\"vendor/bin/phpunit-watcher watch\"
+ fi
+ COMMAND=\"$${PHPUNIT_BIN} -c Build/Test/FunctionalTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE}\"
+ if [ ${PHP_XDEBUG_ON} -eq 0 ]; then
+ XDEBUG_MODE=\"off\" $${COMMAND};
+ else
+ XDEBUG_MODE=\"debug,develop\" \
+ XDEBUG_TRIGGER=\"foo\" \
+ XDEBUG_CONFIG=\"client_port=${PHP_XDEBUG_PORT} client_host=host.docker.internal\" \
+ $${COMMAND};
+ fi
+ "
+ unit:
+ image: docker.io/typo3/core-testing-${DOCKER_PHP_IMAGE}:latest
+ user: "${HOST_UID}:${HOST_GID}"
+ volumes:
+ - ${EXTENSIONS_ROOT}:/var/www/extensions/
+ - ${DFGVIEWER_ROOT}:${DFGVIEWER_ROOT}
+ working_dir: ${DFGVIEWER_ROOT}
+ extra_hosts:
+ - "host.docker.internal:host-gateway"
+ command: >
+ /bin/sh -c "
+ if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
+ set -x
+ fi
+ php -v | grep '^PHP'
+ if [ ${PHPUNIT_WATCH} -eq 0 ]; then
+ PHPUNIT_BIN=\"vendor/bin/phpunit\"
+ else
+ PHPUNIT_BIN=\"vendor/bin/phpunit-watcher watch\"
+ fi
+ if [ ${PHP_XDEBUG_ON} -eq 0 ]; then
+ XDEBUG_MODE=\"off\" \
+ $${PHPUNIT_BIN} -c Build/Test/UnitTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE};
+ else
+ XDEBUG_MODE=\"debug,develop\" \
+ XDEBUG_TRIGGER=\"foo\" \
+ XDEBUG_CONFIG=\"client_port=${PHP_XDEBUG_PORT} client_host=host.docker.internal\" \
+ $${PHPUNIT_BIN} -c Build/Test/UnitTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE};
+ fi
+ "
diff --git a/Build/Test/runTests.sh b/Build/Test/runTests.sh
new file mode 100755
index 000000000..d3f109f44
--- /dev/null
+++ b/Build/Test/runTests.sh
@@ -0,0 +1,367 @@
+#!/usr/bin/env bash
+
+# Adopted/reduced from https://github.com/TYPO3/typo3/blob/f6d73fea5a8f3a5cd8537e29308f18bec65a0c92/Build/Scripts/runTests.sh
+
+# Function to write a .env file in Build/Test
+# This is read by docker compose and vars defined here are
+# used in Build/Test/docker-compose.yml
+setUpDockerComposeDotEnv() {
+ # Delete possibly existing local .env file if exists
+ [ -e .env ] && rm .env
+ # Set up a new .env file for docker compose
+ {
+ echo "COMPOSE_PROJECT_NAME=dfgviewer_testing"
+ # To prevent access rights of files created by the testing, the docker image later
+ # runs with the same user that is currently executing the script. docker compose can't
+ # use $UID directly itself since it is a shell variable and not an env variable, so
+ # we have to set it explicitly here.
+ echo "HOST_UID=$(id -u)"
+ echo "HOST_GID=$(id -g)"
+ # Your local user
+ echo "DFGVIEWER_ROOT=${DFGVIEWER_ROOT}"
+ echo "EXTENSIONS_ROOT=$(dirname "$DFGVIEWER_ROOT")"
+ echo "HOST_USER=${USER}"
+ echo "TYPO3_VERSION=${TYPO3_VERSION}"
+ echo "TEST_FILE=${TEST_FILE}"
+ echo "PHP_XDEBUG_ON=${PHP_XDEBUG_ON}"
+ echo "PHP_XDEBUG_PORT=${PHP_XDEBUG_PORT}"
+ echo "SERVER_PORT=${SERVER_PORT}"
+ echo "DOCKER_PHP_IMAGE=${DOCKER_PHP_IMAGE}"
+ echo "EXTRA_TEST_OPTIONS=${EXTRA_TEST_OPTIONS}"
+ echo "SCRIPT_VERBOSE=${SCRIPT_VERBOSE}"
+ echo "PHPUNIT_WATCH=${PHPUNIT_WATCH}"
+ echo "DBMS=${DBMS}"
+ echo "DATABASE_DRIVER=${DATABASE_DRIVER}"
+ echo "MARIADB_VERSION=${MARIADB_VERSION}"
+ echo "MYSQL_VERSION=${MYSQL_VERSION}"
+ echo "PHP_VERSION=${PHP_VERSION}"
+ } > .env
+}
+
+# Options -a and -d depend on each other. The function
+# validates input combinations and sets defaults.
+handleDbmsAndDriverOptions() {
+ case ${DBMS} in
+ mysql|mariadb)
+ [ -z "${DATABASE_DRIVER}" ] && DATABASE_DRIVER="mysqli"
+ if [ "${DATABASE_DRIVER}" != "mysqli" ] && [ "${DATABASE_DRIVER}" != "pdo_mysql" ]; then
+ echo "Invalid option -a ${DATABASE_DRIVER} with -d ${DBMS}" >&2
+ echo >&2
+ echo "call \".Build/Test/runTests.sh -h\" to display help and valid options" >&2
+ exit 1
+ fi
+ ;;
+ esac
+}
+
+# Load help text into $HELP
+read -r -d '' HELP <=20.10 for xdebug break pointing to work reliably, and
+a recent docker compose (tested >=2.27.1) is needed.
+
+Usage: $0 [options] [file]
+
+No arguments: Run all unit tests with PHP 7.4
+
+Options:
+ -s <...>
+ Specifies which test suite to run
+ - composerInstall: "composer install"
+ - functional: PHP functional tests
+ - unit (default): PHP unit tests
+
+ -t <|10.4|11.5>
+ Only with -s composerInstall
+ Specifies which TYPO3 version to install. When unset, installs either the packages from
+ composer.lock, or the latest version otherwise (default behavior of "composer install").
+ - 10.4
+ - 11.5
+
+ -a
+ Only with -s functional
+ Specifies to use another driver, following combinations are available:
+ - mysql
+ - mysqli (default)
+ - pdo_mysql
+ - mariadb
+ - mysqli (default)
+ - pdo_mysql
+
+ -d
+ Only with -s functional
+ Specifies on which DBMS tests are performed
+ - mariadb (default): use mariadb
+ - mysql: use MySQL server
+
+ -i <10.1|10.2|10.3|10.4|10.5|10.6|10.7|10.8|10.9|10.10>
+ Only with -d mariadb
+ Specifies on which version of mariadb tests are performed
+ - 10.1
+ - 10.2
+ - 10.3 (default)
+ - 10.4
+ - 10.5
+ - 10.6
+ - 10.7
+ - 10.8
+ - 10.9
+ - 10.10
+
+ -j <5.5|5.6|5.7|8.0>
+ Only with -d mysql
+ Specifies on which version of mysql tests are performed
+ - 5.5 (default)
+ - 5.6
+ - 5.7
+ - 8.0
+
+ -p <7.4|8.0|8.1|8.2>
+ Specifies the PHP minor version to be used
+ - 7.4: (default) use PHP 7.4
+ - 8.0: use PHP 8.0
+ - 8.1: use PHP 8.1
+ - 8.2: use PHP 8.2 (note that xdebug is currently not available for PHP8.2)
+
+ -e ""
+ Only with -s functional|functionalDeprecated|unit|unitDeprecated|unitRandom|acceptance
+ Additional options to send to phpunit (unit & functional tests) or codeception (acceptance
+ tests). For phpunit, options starting with "--" must be added after options starting with "-".
+ Example -e "-v --filter canRetrieveValueWithGP" to enable verbose output AND filter tests
+ named "canRetrieveValueWithGP"
+
+ -x
+ Only with -s functional|functionalDeprecated|unit|unitDeprecated|unitRandom|acceptance|acceptanceInstall
+ Send information to host instance for test or system under test break points. This is especially
+ useful if a local PhpStorm instance is listening on default xdebug port 9003. A different port
+ can be selected with -y
+
+ -y
+ Send xdebug information to a different port than default 9003 if an IDE like PhpStorm
+ is not listening on default port.
+
+ -w
+ Only with -s functional|unit
+ Run tests in watch mode.
+
+ -u
+ Update existing typo3/core-testing-*:latest docker images and remove dangling local docker volumes.
+ Maintenance call to docker pull latest versions of the main php images. The images are updated once
+ in a while and only the latest ones are supported by core testing. Use this if weird test errors occur.
+ Also removes obsolete image versions of typo3/core-testing-*.
+
+ -v
+ Enable verbose script output. Shows variables and docker commands.
+
+ -h
+ Show this help.
+EOF
+
+# Go to the directory this script is located, so everything else is relative
+# to this dir, no matter from where this script is called.
+THIS_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
+cd "$THIS_SCRIPT_DIR" || exit 1
+
+# Go to directory that contains the local docker-compose.yml file
+# cd ../testing-docker/local || exit 1
+
+# Set root path by checking whether realpath exists
+if ! command -v realpath &> /dev/null; then
+ echo "Consider installing realpath for properly resolving symlinks" >&2
+ DFGVIEWER_ROOT="${PWD}/../../"
+else
+ DFGVIEWER_ROOT=$(realpath "${PWD}/../../")
+fi
+
+# Option defaults
+TEST_SUITE="unit"
+TYPO3_VERSION=""
+DBMS="mariadb"
+PHP_VERSION="7.4"
+PHP_XDEBUG_ON=0
+PHP_XDEBUG_PORT=9003
+SERVER_PORT=8000
+EXTRA_TEST_OPTIONS=""
+SCRIPT_VERBOSE=0
+PHPUNIT_WATCH=0
+DATABASE_DRIVER=""
+MARIADB_VERSION="10.3"
+MYSQL_VERSION="5.5"
+
+# Option parsing
+# Reset in case getopts has been used previously in the shell
+OPTIND=1
+# Array for invalid options
+INVALID_OPTIONS=();
+# Simple option parsing based on getopts (! not getopt)
+while getopts ":a:s:t:d:i:j:p:e:xy:whuv" OPT; do
+ case ${OPT} in
+ s)
+ TEST_SUITE=${OPTARG}
+ ;;
+ t)
+ TYPO3_VERSION=${OPTARG}
+ ;;
+ a)
+ DATABASE_DRIVER=${OPTARG}
+ ;;
+ d)
+ DBMS=${OPTARG}
+ ;;
+ i)
+ MARIADB_VERSION=${OPTARG}
+ if ! [[ ${MARIADB_VERSION} =~ ^(10.2|10.3|10.4|10.5|10.6|10.11)$ ]]; then
+ INVALID_OPTIONS+=("${OPTARG}")
+ fi
+ ;;
+ j)
+ MYSQL_VERSION=${OPTARG}
+ if ! [[ ${MYSQL_VERSION} =~ ^(5.7|8.0)$ ]]; then
+ INVALID_OPTIONS+=("${OPTARG}")
+ fi
+ ;;
+ p)
+ PHP_VERSION=${OPTARG}
+ if ! [[ ${PHP_VERSION} =~ ^(7.4|8.0|8.1|8.2|8.3)$ ]]; then
+ INVALID_OPTIONS+=("${OPTARG}")
+ fi
+ ;;
+ e)
+ EXTRA_TEST_OPTIONS=${OPTARG}
+ ;;
+ x)
+ PHP_XDEBUG_ON=1
+ ;;
+ y)
+ PHP_XDEBUG_PORT=${OPTARG}
+ ;;
+ w)
+ PHPUNIT_WATCH=1
+ ;;
+ h)
+ echo "${HELP}"
+ exit 0
+ ;;
+ u)
+ TEST_SUITE=update
+ ;;
+ v)
+ SCRIPT_VERBOSE=1
+ ;;
+ \?)
+ INVALID_OPTIONS+=("${OPTARG}")
+ ;;
+ :)
+ INVALID_OPTIONS+=("${OPTARG}")
+ ;;
+ esac
+done
+
+# Exit on invalid options
+if [ ${#INVALID_OPTIONS[@]} -ne 0 ]; then
+ echo "Invalid option(s):" >&2
+ for I in "${INVALID_OPTIONS[@]}"; do
+ echo "-${I}" >&2
+ done
+ echo >&2
+ echo "call \".Build/Test/runTests.sh -h\" to display help and valid options"
+ exit 1
+fi
+
+# Move "7.4" to "php74", the latter is the docker container name
+DOCKER_PHP_IMAGE=$(echo "php${PHP_VERSION}" | sed -e 's/\.//')
+
+# Set $1 to first mass argument, this is the optional test file or test directory to execute
+shift $((OPTIND - 1))
+TEST_FILE=${1}
+
+if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
+ set -x
+fi
+
+# Suite execution
+case ${TEST_SUITE} in
+ composerInstall)
+ setUpDockerComposeDotEnv
+ docker compose run composer_install
+ SUITE_EXIT_CODE=$?
+ docker compose down
+ ;;
+ functional)
+ handleDbmsAndDriverOptions
+ setUpDockerComposeDotEnv
+ case ${DBMS} in
+ mariadb|mysql)
+ echo "Using driver: ${DATABASE_DRIVER}"
+ docker compose run functional
+ SUITE_EXIT_CODE=$?
+ ;;
+ *)
+ echo "Functional tests don't run with DBMS ${DBMS}" >&2
+ echo >&2
+ echo "call \".Build/Test/runTests.sh -h\" to display help and valid options" >&2
+ exit 1
+ esac
+ docker compose down
+ ;;
+ unit)
+ setUpDockerComposeDotEnv
+ docker compose run unit
+ SUITE_EXIT_CODE=$?
+ docker compose down
+ ;;
+ update)
+ # pull typo3/core-testing-*:latest versions of those ones that exist locally
+ docker images typo3/core-testing-*:latest --format "{{.Repository}}:latest" | xargs -I {} docker pull {}
+ # remove "dangling" typo3/core-testing-* images (those tagged as )
+ docker images typo3/core-testing-* --filter "dangling=true" --format "{{.ID}}" | xargs -I {} docker rmi {}
+ ;;
+ *)
+ echo "Invalid -s option argument ${TEST_SUITE}" >&2
+ echo >&2
+ echo "${HELP}" >&2
+ exit 1
+esac
+
+case ${DBMS} in
+ mariadb)
+ DBMS_OUTPUT="DBMS: ${DBMS} version ${MARIADB_VERSION} driver ${DATABASE_DRIVER}"
+ ;;
+ mysql)
+ DBMS_OUTPUT="DBMS: ${DBMS} version ${MYSQL_VERSION} driver ${DATABASE_DRIVER}"
+ ;;
+ *)
+ DBMS_OUTPUT="DBMS not recognized: $DBMS"
+ exit 1
+esac
+
+# Print summary
+if [ ${SCRIPT_VERBOSE} -eq 1 ]; then
+ # Turn off verbose mode for the script summary
+ set +x
+fi
+echo "" >&2
+echo "###########################################################################" >&2
+if [[ ${TEST_SUITE} =~ ^(functional)$ ]]; then
+ echo "Result of ${TEST_SUITE}" >&2
+ echo "PHP: ${PHP_VERSION}" >&2
+ echo "${DBMS_OUTPUT}" >&2
+else
+ echo "Result of ${TEST_SUITE}" >&2
+ echo "PHP: ${PHP_VERSION}" >&2
+fi
+
+if [[ ${SUITE_EXIT_CODE} -eq 0 ]]; then
+ echo "SUCCESS" >&2
+else
+ echo "FAILURE" >&2
+fi
+echo "###########################################################################" >&2
+echo "" >&2
+
+# Exit with code of test suite - This script return non-zero if the executed test failed.
+exit "$SUITE_EXIT_CODE"
diff --git a/Classes/Common/ValidationHelper.php b/Classes/Common/ValidationHelper.php
new file mode 100644
index 000000000..8b4b97f21
--- /dev/null
+++ b/Classes/Common/ValidationHelper.php
@@ -0,0 +1,91 @@
+
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ */
+
+/**
+ * The validator helper contains constants and functions to support the validation process.
+ *
+ * @package TYPO3
+ * @subpackage dfg-viewer
+ *
+ * @access public
+ */
+class ValidationHelper
+{
+ const NAMESPACE_DV = 'http://dfg-viewer.de/';
+
+ const NAMESPACE_METS = 'http://www.loc.gov/METS/';
+
+ const STRUCTURE_DATASET = [
+ 'section', 'file', 'album', 'register', 'annotation', 'address', 'article', 'atlas', 'issue', 'bachelor_thesis', 'volume', 'contained_work', 'additional', 'report', 'official_notification', 'provenance', 'inventory', 'image', 'collation', 'ornament', 'letter', 'cover', 'cover_front', 'cover_back', 'diploma_thesis', 'doctoral_thesis', 'document', 'printers_mark', 'printed_archives', 'binding', 'entry', 'corrigenda', 'bookplate', 'fascicle', 'leaflet', 'research_paper', 'photograph', 'fragment', 'land_register', 'ground_plan', 'habilitation_thesis', 'manuscript', 'illustration', 'imprint', 'contents', 'initial_decoration', 'year', 'chapter', 'map', 'cartulary', 'colophon', 'ephemera', 'engraved_titlepage', 'magister_thesis', 'folder', 'master_thesis', 'multivolume_work', 'month', 'monograph', 'musical_notation', 'periodical', 'poster', 'plan', 'privileges', 'index', 'spine', 'scheme', 'edge', 'seal', 'paste down', 'stamp', 'study', 'table', 'day', 'proceeding', 'text', 'title_page', 'subinventory', 'act', 'judgement', 'verse', 'note', 'preprint', 'dossier', 'lecture', 'endsheet', 'paper', 'preface', 'dedication', 'newspaper'
+ ];
+
+ const XPATH_METS = '//mets:mets';
+
+ const XPATH_ADMINISTRATIVE_METADATA = self::XPATH_METS . '/mets:amdSec';
+
+ const XPATH_ADMINISTRATIVE_TECHNICAL_METADATA = self::XPATH_ADMINISTRATIVE_METADATA . '/mets:techMD';
+
+ const XPATH_ADMINISTRATIVE_RIGHTS_METADATA = self::XPATH_ADMINISTRATIVE_METADATA . '/mets:rightsMD';
+
+ const XPATH_ADMINISTRATIVE_DIGIPROV_METADATA = self::XPATH_ADMINISTRATIVE_METADATA . '/mets:digiprovMD';
+
+ const XPATH_DESCRIPTIVE_METADATA_SECTIONS = self::XPATH_METS . '/mets:dmdSec';
+
+ const XPATH_FILE_SECTIONS = self::XPATH_METS . '/mets:fileSec';
+
+ const XPATH_FILE_SECTION_GROUPS = self::XPATH_FILE_SECTIONS . '/mets:fileGrp';
+
+ const XPATH_FILE_SECTION_FILES = self::XPATH_FILE_SECTION_GROUPS . '/mets:file';
+
+ const XPATH_STRUCT_LINK = self::XPATH_METS . '/mets:structLink';
+
+ const XPATH_STRUCT_LINK_ELEMENTS = self::XPATH_STRUCT_LINK . '/mets:smLink';
+
+ const XPATH_LOGICAL_STRUCTURES = self::XPATH_METS . '/mets:structMap[@TYPE="LOGICAL"]';
+
+ const XPATH_LOGICAL_STRUCTURAL_ELEMENTS = self::XPATH_LOGICAL_STRUCTURES . '/mets:div';
+
+ const XPATH_LOGICAL_EXTERNAL_REFERENCES = self::XPATH_LOGICAL_STRUCTURAL_ELEMENTS . '/mets:mptr';
+
+ const XPATH_PHYSICAL_STRUCTURES = self::XPATH_METS . '/mets:structMap[@TYPE="PHYSICAL"]';
+
+ const XPATH_PHYSICAL_STRUCTURAL_ELEMENT_SEQUENCE = self::XPATH_PHYSICAL_STRUCTURES . '/mets:div';
+
+ const XPATH_PHYSICAL_STRUCTURAL_ELEMENTS = self::XPATH_PHYSICAL_STRUCTURAL_ELEMENT_SEQUENCE . '/mets:div';
+
+ const XPATH_DVRIGHTS = self::XPATH_ADMINISTRATIVE_RIGHTS_METADATA . '/mets:mdWrap[@MDTYPE="OTHER" and @OTHERMDTYPE="DVRIGHTS"]/mets:xmlData/dv:rights';
+
+ const XPATH_DVLINKS = self::XPATH_ADMINISTRATIVE_DIGIPROV_METADATA . '/mets:mdWrap[@MDTYPE="OTHER" and @OTHERMDTYPE="DVLINKS"]/mets:xmlData/dv:links';
+
+ public static function trimDoubleSlash(string $value): string
+ {
+ if (str_starts_with($value, '//')) {
+ return substr($value, 1);
+ }
+ return $value;
+ }
+}
diff --git a/Classes/Validation/AbstactDomDocumentValidator.php b/Classes/Validation/AbstactDomDocumentValidator.php
new file mode 100644
index 000000000..f5c85b993
--- /dev/null
+++ b/Classes/Validation/AbstactDomDocumentValidator.php
@@ -0,0 +1,65 @@
+
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ */
+
+use DOMDocument;
+use DOMNode;
+use DOMXPath;
+use Kitodo\Dlf\Validation\AbstractDlfValidator;
+use Slub\Dfgviewer\Validation\Dom\DomNodeListValidator;
+use Slub\Dfgviewer\Validation\Dom\DomNodeValidator;
+
+abstract class AbstactDomDocumentValidator extends AbstractDlfValidator
+{
+
+ /**
+ * @var DOMXPath The XPath of DOMDocument value.
+ */
+ protected DOMXpath $xpath;
+
+ public function __construct()
+ {
+ parent::__construct(DOMDocument::class);
+ }
+
+ abstract public function isValidDocument();
+
+ protected function isValid($value): void
+ {
+ $this->xpath = new DOMXPath($value);
+ $this->isValidDocument();
+ }
+
+ protected function createNodeListValidator(string $expression, ?DOMNode $contextNode=null): DomNodeListValidator
+ {
+ return new DomNodeListValidator($this->xpath, $this->result, $expression, $contextNode);
+ }
+
+ protected function createNodeValidator(?DOMNode $node): DomNodeValidator
+ {
+ return new DomNodeValidator($this->xpath, $this->result, $node);
+ }
+}
diff --git a/Classes/Validation/ApplicationProfileValidationStack.php b/Classes/Validation/ApplicationProfileValidationStack.php
new file mode 100644
index 000000000..9ab3c46a7
--- /dev/null
+++ b/Classes/Validation/ApplicationProfileValidationStack.php
@@ -0,0 +1,51 @@
+
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ */
+
+use Kitodo\Dlf\Validation\AbstractDlfValidationStack;
+use Slub\Dfgviewer\Validation\Mets\AdministrativeMetadataValidator;
+use Slub\Dfgviewer\Validation\Mets\DescriptiveMetadataValidator;
+use Slub\Dfgviewer\Validation\Mets\DigitalRepresentationValidator;
+use Slub\Dfgviewer\Validation\Mets\LinkingLogicalPhysicalStructureValidator;
+use Slub\Dfgviewer\Validation\Mets\LogicalStructureValidator;
+use Slub\Dfgviewer\Validation\Mets\PhysicalStructureValidator;
+
+class ApplicationProfileValidationStack extends AbstractDlfValidationStack
+{
+ public function __construct()
+ {
+ parent::__construct(\DOMDocument::class);
+ $this->addValidator(LogicalStructureValidator::class, "Validation of the logical document structure", false);
+ $this->addValidator(PhysicalStructureValidator::class, "Validation of the physical document structure", false);
+ $this->addValidator(LinkingLogicalPhysicalStructureValidator::class, "Validation of linking between logical and physical structure", false);
+ $this->addValidator(DigitalRepresentationValidator::class, "Validation of the digital representation", false);
+ $this->addValidator(DescriptiveMetadataValidator::class, "Validation of the descriptive metadata", false);
+ $this->addValidator(AdministrativeMetadataValidator::class, "Validation of the administrative metadata", false);
+ $this->addValidator(DvMetadataValidator::class, "Validation of the DFG-Viewer specific details", false);
+ }
+}
diff --git a/Classes/Validation/Dom/DomNodeListValidator.php b/Classes/Validation/Dom/DomNodeListValidator.php
new file mode 100644
index 000000000..479add2f9
--- /dev/null
+++ b/Classes/Validation/Dom/DomNodeListValidator.php
@@ -0,0 +1,151 @@
+
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ */
+
+use DOMNode;
+use DOMNodeList;
+use DOMXPath;
+use TYPO3\CMS\Extbase\Error\Error;
+use TYPO3\CMS\Extbase\Error\Result;
+
+/**
+ * The validator contains functions to validate a DOMNodeList.
+ *
+ * @package TYPO3
+ * @subpackage dfg-viewer
+ *
+ * @access public
+ */
+class DomNodeListValidator
+{
+
+ /**
+ * @var string The expression of XPath query
+ */
+ private string $expression;
+
+ /**
+ * @var DOMNode|null The context node of XPath query
+ */
+ private ?DOMNode $contextNode;
+
+ /**
+ * @var DOMNodeList The node list result of XPath query
+ */
+ private DOMNodeList $nodeList;
+
+ /**
+ * @var Result The result containing errors of validation
+ */
+ private Result $result;
+
+ public function __construct(DOMXPath $xpath, Result $result, string $expression, ?DOMNode $contextNode=null)
+ {
+ $this->expression = $expression;
+ $this->contextNode = $contextNode;
+ $this->nodeList = $xpath->query($expression, $contextNode);
+ $this->result = $result;
+ }
+
+ /**
+ * Get the first node from the node list.
+ *
+ * @return DOMNode|null
+ */
+ public function getFirstNode(): ?DOMNode
+ {
+ return $this->getNode(0);
+ }
+
+ /**
+ * Get a node from the node list at a specific index.
+ *
+ * @param int $index The index to retrieve the node
+ * @return DOMNode|null
+ */
+ public function getNode(int $index): ?DOMNode
+ {
+ return $this->nodeList->item($index);
+ }
+
+ /**
+ * Get the node list.
+ *
+ * @return DOMNodeList
+ */
+ public function getNodeList(): DOMNodeList
+ {
+ return $this->nodeList;
+ }
+
+ /**
+ * Validates the node list has any node.
+ *
+ * @return $this
+ */
+ public function validateHasAny(): DomNodeListValidator
+ {
+ if (!$this->nodeList->length > 0) {
+ $this->addError('There must be at least one element', 1736504345);
+ }
+ return $this;
+ }
+
+ /**
+ * Validates the node list has one node.
+ *
+ * @return $this
+ */
+ public function validateHasOne(): DomNodeListValidator
+ {
+ if ($this->nodeList->length != 1) {
+ $this->addError('There must be an element', 1736504354);
+ }
+ return $this;
+ }
+
+ /**
+ * Validates the node list has none or one node.
+ *
+ * @return $this
+ */
+ public function validateHasNoneOrOne(): DomNodeListValidator
+ {
+ if (!($this->nodeList->length == 0 || $this->nodeList->length == 1)) {
+ $this->addError('There must be no more than one element', 1736504361);
+ }
+ return $this;
+ }
+
+ private function addError(string $prefix, int $code): void
+ {
+ $message = $prefix . ' that matches the XPath expression "' . $this->expression . '"';
+ if ($this->contextNode) {
+ $message .= ' under "' . $this->contextNode->getNodePath() . '"';
+ }
+ $this->result->addError(new Error($message, $code));
+ }
+}
diff --git a/Classes/Validation/Dom/DomNodeValidator.php b/Classes/Validation/Dom/DomNodeValidator.php
new file mode 100644
index 000000000..7f27d0646
--- /dev/null
+++ b/Classes/Validation/Dom/DomNodeValidator.php
@@ -0,0 +1,251 @@
+
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ */
+
+use DOMNode;
+use DOMXPath;
+use TYPO3\CMS\Extbase\Error\Error;
+use TYPO3\CMS\Extbase\Error\Result;
+
+/**
+ * The validator contains functions to validate a DOMNode.
+ *
+ * @package TYPO3
+ * @subpackage dfg-viewer
+ *
+ * @access public
+ */
+class DomNodeValidator
+{
+
+ /**
+ * @var DOMXPath The XPath of document to validate
+ */
+ private DOMXPath $xpath;
+
+ /**
+ * @var DOMNode|null The node to validate
+ */
+ private ?DOMNode $node;
+
+ /**
+ * @var Result The result containing errors of validation
+ */
+ private Result $result;
+
+ public function __construct(DOMXPath $xpath, Result $result, ?DOMNode $node)
+ {
+ $this->xpath = $xpath;
+ $this->result = $result;
+ $this->node = $node;
+ }
+
+ /**
+ * Validate that the node's content contains an Email.
+ *
+ * @return $this
+ */
+ public function validateHasContentWithEmail(): DomNodeValidator
+ {
+ if (!isset($this->node) || !$this->node->nodeValue) {
+ return $this;
+ }
+
+ $email = $this->node->nodeValue;
+
+ if (str_starts_with(strtolower($email), 'mailto:')) {
+ $email = substr($email, 7);
+ }
+
+ if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
+ $this->result->addError(new Error('Email "' . $this->node->nodeValue . '" in the content of "' . $this->node->getNodePath() . '" is not valid.', 1736504169));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Validate that the node's content contains a URL.
+ *
+ * @return $this
+ */
+ public function validateHasContentWithUrl(): DomNodeValidator
+ {
+ if (!isset($this->node) || !$this->node->nodeValue) {
+ return $this;
+ }
+
+ if (!filter_var($this->node->nodeValue, FILTER_VALIDATE_URL)) {
+ $this->result->addError(new Error('URL "' . $this->node->nodeValue . '" in the content of "' . $this->node->getNodePath() . '" is not valid.', 1736504177));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Validate that the node has an attribute with a URL value.
+ *
+ * @param string $name The attribute name
+ * @return $this
+ */
+ public function validateHasAttributeWithUrl(string $name): DomNodeValidator
+ {
+ if (!isset($this->node)) {
+ return $this;
+ }
+
+ // @phpstan-ignore-next-line
+ if (!$this->node->hasAttribute($name)) {
+ return $this->validateHasAttribute($name);
+ }
+
+ // @phpstan-ignore-next-line
+ $value = $this->node->getAttribute($name);
+ if (!filter_var($value, FILTER_VALIDATE_URL)) {
+ $this->result->addError(new Error('URL "' . $value . '" in the "' . $name . '" attribute of "' . $this->node->getNodePath() . '" is not valid.', 1736504189));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Validate that the node has an attribute with a specific value.
+ *
+ * @param string $name The attribute name
+ * @param array $values The allowed values
+ * @return $this
+ */
+ public function validateHasAttributeWithValue(string $name, array $values): DomNodeValidator
+ {
+ if (!isset($this->node)) {
+ return $this;
+ }
+
+ // @phpstan-ignore-next-line
+ if (!$this->node->hasAttribute($name)) {
+ return $this->validateHasAttribute($name);
+ }
+
+ // @phpstan-ignore-next-line
+ $value = $this->node->getAttribute($name);
+ if (!in_array($value, $values)) {
+ $this->result->addError(new Error('Value "' . $value . '" in the "' . $name . '" attribute of "' . $this->node->getNodePath() . '" is not permissible.', 1736504197));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Validate that the node has a unique attribute with name.
+ *
+ * @param string $name The attribute name
+ * @param string $contextExpression The context expression to determine uniqueness.
+ * @return $this
+ */
+ public function validateHasUniqueAttribute(string $name, string $contextExpression): DomNodeValidator
+ {
+ if (!isset($this->node)) {
+ return $this;
+ }
+
+ // @phpstan-ignore-next-line
+ if (!$this->node->hasAttribute($name)) {
+ return $this->validateHasAttribute($name);
+ }
+
+ // @phpstan-ignore-next-line
+ $value = $this->node->getAttribute($name);
+ if ($this->xpath->query($contextExpression . '[@' . $name . '="' . $value . '"]')->length > 1) {
+ $this->result->addError(new Error('"' . $name . '" attribute with value "' . $value . '" of "' . $this->node->getNodePath() . '" already exists.', 1736504203));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Validate that the node has a unique identifier.
+ *
+ * @return $this
+ */
+ public function validateHasUniqueId(): DomNodeValidator
+ {
+ $this->validateHasUniqueAttribute("ID", "//*");
+ return $this;
+ }
+
+ /**
+ * Validate that the node has attribute with name.
+ *
+ * @param string $name The attribute name
+ * @return $this
+ */
+ public function validateHasAttribute(string $name): DomNodeValidator
+ {
+ if (!isset($this->node)) {
+ return $this;
+ }
+
+ // @phpstan-ignore-next-line
+ if (!$this->node->hasAttribute($name)) {
+ $this->result->addError(new Error('Mandatory "' . $name . '" attribute of "' . $this->node->getNodePath() . '" is missing.', 1736504217));
+ }
+ return $this;
+ }
+
+ /**
+ * Validate that the node's resolvable identifier attribute points to a target with the specified "ID" attribute.
+ *
+ * @param string $name The attribute name containing the reference id as value
+ * @param string $targetExpression The context expression to the target reference
+ * @return $this
+ */
+ public function validateHasReferenceToId(string $name, string $targetExpression): DomNodeValidator
+ {
+ if (!isset($this->node)) {
+ return $this;
+ }
+
+ // @phpstan-ignore-next-line
+ if (!$this->node->hasAttribute($name)) {
+ return $this->validateHasAttribute($name);
+ }
+
+ $targetNodes = $this->xpath->query($targetExpression);
+ // @phpstan-ignore-next-line
+ $identifier = $this->node->getAttribute($name);
+
+ $foundElements = 0;
+ foreach ($targetNodes as $targetNode) {
+ $foundElements += $this->xpath->query('//*[@ID="' . $identifier . '"]', $targetNode)->length;
+ }
+
+ if ($foundElements !== 1) {
+ $this->result->addError(new Error('Value "' . $identifier . '" in the "' . $name . '" attribute of "' . $this->node->getNodePath() . '" must reference one element under XPath expression "' . $targetExpression, 1736504228));
+ }
+
+ return $this;
+ }
+}
diff --git a/Classes/Validation/DvMetadataValidator.php b/Classes/Validation/DvMetadataValidator.php
new file mode 100644
index 000000000..f722a7c42
--- /dev/null
+++ b/Classes/Validation/DvMetadataValidator.php
@@ -0,0 +1,155 @@
+
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ */
+
+use Slub\Dfgviewer\Common\ValidationHelper as VH;
+
+/**
+ * The validator validates against the rules outlined in chapter 2.7 of the METS application profile 2.3.1.
+ *
+ * @package TYPO3
+ * @subpackage dfg-viewer
+ *
+ * @access public
+ */
+class DvMetadataValidator extends AbstactDomDocumentValidator
+{
+ public function isValidDocument(): void
+ {
+ // Validates against the rules of chapter "2.7.1 Rechteangaben – dv:rights"
+ $this->createNodeListValidator(VH::XPATH_DVRIGHTS)
+ ->validateHasOne();
+
+ $this->validateDvRights();
+
+ // Validates against the rules of chapter "2.7.3 Verweise – dv:links"
+ $this->createNodeListValidator(VH::XPATH_DVLINKS)
+ ->validateHasOne();
+
+ $this->validateDvLinks();
+ }
+
+ /**
+ * Validates the DFG-Viewer links.
+ *
+ * Validates against the rules of chapter "2.7.4 Unterelemente zu dv:links"
+ *
+ * @return void
+ */
+ protected function validateDvLinks(): void
+ {
+ $references = $this->createNodeListValidator(VH::XPATH_DVLINKS . '/dv:reference')
+ ->validateHasAny()
+ ->getNodeList();
+ foreach ($references as $reference) {
+ $this->validateReference($reference);
+ }
+
+ $this->createNodeListValidator(VH::XPATH_DVLINKS . '/dv:presentation')
+ ->validateHasNoneOrOne();
+
+ $sruNode = $this->createNodeListValidator(VH::XPATH_DVLINKS . '/dv:sru')
+ ->validateHasNoneOrOne()->getFirstNode();
+ $this->createNodeValidator($sruNode)->validateHasContentWithUrl();
+
+ $iiifNode = $this->createNodeListValidator(VH::XPATH_DVLINKS . '/dv:iiif')
+ ->validateHasNoneOrOne()->getFirstNode();
+ $this->createNodeValidator($iiifNode)->validateHasContentWithUrl();
+ }
+
+ /**
+ * Validates the DFG-Viewer rights.
+ *
+ * Validates against the rules of chapter "2.7.2 Unterelemente zu dv:rights"
+ *
+ * @return void
+ */
+ protected function validateDvRights(): void
+ {
+ $this->createNodeListValidator(VH::XPATH_DVRIGHTS . '/dv:owner')
+ ->validateHasOne();
+
+ $this->validateNodeContent(VH::XPATH_DVRIGHTS . '/dv:ownerLogo');
+ $this->validateNodeContent(VH::XPATH_DVRIGHTS . '/dv:ownerSiteURL');
+ $this->validateNodeContent(VH::XPATH_DVRIGHTS . '/dv:ownerContact');
+
+ $this->createNodeListValidator(VH::XPATH_DVRIGHTS . '/dv:aggregator')->validateHasNoneOrOne();
+ $this->validateNodeContent(VH::XPATH_DVRIGHTS . '/dv:aggregatorLogo', true);
+ $this->validateNodeContent(VH::XPATH_DVRIGHTS . '/dv:aggregatorSiteURL', true);
+
+ $this->createNodeListValidator(VH::XPATH_DVRIGHTS . '/dv:sponsor')->validateHasNoneOrOne();
+ $this->validateNodeContent(VH::XPATH_DVRIGHTS . '/dv:sponsorLogo', true);
+ $this->validateNodeContent(VH::XPATH_DVRIGHTS . '/dv:sponsorSiteURL', true);
+
+ $licenseNode = $this->createNodeListValidator(VH::XPATH_DVRIGHTS . '/dv:license')
+ ->validateHasNoneOrOne()
+ ->getFirstNode();
+ if ($licenseNode && !in_array($licenseNode->nodeValue, ['pdm', 'cc0', 'cc-by', 'cc-by-sa', 'cc-by-nd', 'cc-by-nc', 'cc-by-nc-sa', 'cc-by-nc-nd', 'reserved'])) {
+ $this->createNodeValidator($licenseNode)->validateHasContentWithUrl();
+ }
+ }
+
+ /**
+ * Validates the reference.
+ *
+ * Validates against the rules of chapter "2.7.4.1 Katalog- bzw. Findbuchnachweis – dv:reference"
+ *
+ * @param \DOMNode $reference
+ * @return void
+ */
+ protected function validateReference(\DOMNode $reference): void
+ {
+ if ($this->xpath->query('dv:reference', $reference->parentNode)->length > 1) {
+ $this->createNodeValidator($reference)
+ ->validateHasAttribute('linktext');
+ }
+ }
+
+ private function validateNodeContent(string $expression, bool $optional=false): void
+ {
+ $nodeListValidator = $this->createNodeListValidator($expression);
+
+ if ($optional) {
+ $nodeListValidator
+ ->validateHasNoneOrOne();
+ } else {
+ $nodeListValidator
+ ->validateHasOne();
+ }
+
+ $node = $nodeListValidator->getFirstNode();
+ if (!isset($node)) {
+ return;
+ }
+
+ $nodeValidator = $this->createNodeValidator($node);
+ if (str_starts_with(strtolower($node->nodeValue), 'mailto:')) {
+ $nodeValidator->validateHasContentWithEmail();
+ } else {
+ $nodeValidator->validateHasContentWithUrl();
+ }
+ }
+}
diff --git a/Classes/Validation/Mets/AdministrativeMetadataValidator.php b/Classes/Validation/Mets/AdministrativeMetadataValidator.php
new file mode 100644
index 000000000..9175b90ad
--- /dev/null
+++ b/Classes/Validation/Mets/AdministrativeMetadataValidator.php
@@ -0,0 +1,164 @@
+
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ */
+
+use Slub\Dfgviewer\Common\ValidationHelper as VH;
+use Slub\Dfgviewer\Validation\AbstactDomDocumentValidator;
+
+/**
+ * The validator validates against the rules outlined in chapter 2.6 of the METS application profile 2.3.1.
+ *
+ * @package TYPO3
+ * @subpackage dfg-viewer
+ *
+ * @access public
+ */
+class AdministrativeMetadataValidator extends AbstactDomDocumentValidator
+{
+ public function isValidDocument(): void
+ {
+ // Validates against the rules of chapter "2.6.1 Metadatensektion – mets:amdSec"
+ $amdSections = $this->createNodeListValidator(VH::XPATH_ADMINISTRATIVE_METADATA)
+ ->validateHasAny()
+ ->getNodeList();
+ foreach ($amdSections as $amdSection) {
+ $this->validateAdministrativMetadataNode($amdSection);
+ }
+
+ // Check if one administrative metadata exist with "mets:rightsMD" and "mets:digiprovMD" as children
+ $this->createNodeListValidator(VH::XPATH_ADMINISTRATIVE_METADATA . '[mets:rightsMD and mets:digiprovMD]')
+ ->validateHasOne();
+
+ $this->validateTechnicalMetadata();
+ $this->validateRightsMetadata();
+ $this->validateDigitalProvenanceMetadata();
+ }
+
+ protected function validateAdministrativMetadataNode(\DOMNode $amdSection): void
+ {
+ $this->createNodeValidator($amdSection)
+ ->validateHasUniqueId();
+ }
+
+ /**
+ * Validates the digital provenance metadata.
+ *
+ * Validates against the rules of chapters "2.6.2.5 Herstellung – mets:digiprovMD" and "2.6.2.6 Eingebettete Verweise – mets:digiprovMD/mets:mdWrap"
+ *
+ * @return void
+ */
+ protected function validateDigitalProvenanceMetadata(): void
+ {
+ $digiprovs = $this->createNodeListValidator(VH::XPATH_ADMINISTRATIVE_DIGIPROV_METADATA)
+ ->getNodeList();
+ foreach ($digiprovs as $digiprov) {
+ $this->validateDigitalProvenanceMetadataNode($digiprov);
+ }
+ }
+
+ protected function validateDigitalProvenanceMetadataNode(\DOMNode $digiprov): void
+ {
+ $this->createNodeValidator($digiprov)
+ ->validateHasUniqueId();
+
+ $mdWrap = $this->createNodeListValidator('mets:mdWrap', $digiprov)
+ ->validateHasOne()
+ ->getFirstNode();
+
+ $this->createNodeValidator($mdWrap)
+ ->validateHasAttributeWithValue('MDTYPE', ['OTHER'])
+ ->validateHasAttributeWithValue('OTHERMDTYPE', ['DVLINKS']);
+
+ $this->createNodeListValidator('mets:xmlData[dv:links]', $mdWrap)
+ ->validateHasOne();
+ }
+
+ /**
+ * Validates the rights metadata.
+ *
+ * Validates against the rules of chapters "2.6.2.4 Rechtedeklaration – mets:rightsMD" and "2.6.2.4 Eingebettete Rechteangaben – mets:rightsMD/mets:mdWrap"
+ *
+ * @return void
+ */
+ protected function validateRightsMetadata(): void
+ {
+ $rightsMetadata = $this->createNodeListValidator(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA)
+ ->getNodeList();
+ foreach ($rightsMetadata as $rightsMetadataNode) {
+ $this->validateRightsMetadataNode($rightsMetadataNode);
+ }
+ }
+
+ protected function validateRightsMetadataNode(\DOMNode $rightsMetadata): void
+ {
+ $this->createNodeValidator($rightsMetadata)
+ ->validateHasUniqueId();
+
+ $mpWrap = $this->createNodeListValidator('mets:mdWrap', $rightsMetadata)
+ ->validateHasOne()
+ ->getFirstNode();
+
+ $this->createNodeValidator($mpWrap)
+ ->validateHasAttributeWithValue('MDTYPE', ['OTHER'])
+ ->validateHasAttributeWithValue('OTHERMDTYPE', ['DVRIGHTS']);
+
+ $this->createNodeListValidator('mets:xmlData[dv:rights]', $mpWrap)
+ ->validateHasOne();
+ }
+
+ /**
+ * Validates the technical metadata.
+ *
+ * Validates against the rules of chapters "2.6.2.1 Technische Metadaten – mets:techMD" and "2.6.2.2 Eingebettete technische Daten – mets:techMD/mets:mdWrap"
+ *
+ * @return void
+ */
+ protected function validateTechnicalMetadata(): void
+ {
+ $technicalMd = $this->createNodeListValidator(VH::XPATH_ADMINISTRATIVE_TECHNICAL_METADATA)
+ ->getNodeList();
+ foreach ($technicalMd as $technicalMdNode) {
+ $this->validateTechnicalMetadataNode($technicalMdNode);
+ }
+ }
+
+ protected function validateTechnicalMetadataNode(\DOMNode $technicalMd): void
+ {
+ $this->createNodeValidator($technicalMd)
+ ->validateHasUniqueId();
+
+ $mdWrap = $this->createNodeListValidator('mets:mdWrap', $technicalMd)
+ ->validateHasOne()
+ ->getFirstNode();
+
+ $this->createNodeValidator($mdWrap)
+ ->validateHasAttribute("MDTYPE")
+ ->validateHasAttribute("OTHERMDTYPE");
+
+ $this->createNodeListValidator('mets:xmlData', $mdWrap)
+ ->validateHasOne();
+ }
+}
diff --git a/Classes/Validation/Mets/DescriptiveMetadataValidator.php b/Classes/Validation/Mets/DescriptiveMetadataValidator.php
new file mode 100644
index 000000000..787a503e7
--- /dev/null
+++ b/Classes/Validation/Mets/DescriptiveMetadataValidator.php
@@ -0,0 +1,91 @@
+
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ */
+
+use Slub\Dfgviewer\Common\ValidationHelper as VH;
+use Slub\Dfgviewer\Validation\AbstactDomDocumentValidator;
+
+/**
+ * The validator validates against the rules outlined in chapter 2.5 of the METS application profile 2.3.1.
+ *
+ * @package TYPO3
+ * @subpackage dfg-viewer
+ *
+ * @access public
+ */
+class DescriptiveMetadataValidator extends AbstactDomDocumentValidator
+{
+ public function isValidDocument(): void
+ {
+ // Validates against the rules of chapter "2.5.1 Metadatensektion – mets:dmdSec"
+ $descriptiveSections = $this->createNodeListValidator(VH::XPATH_DESCRIPTIVE_METADATA_SECTIONS)
+ ->validateHasAny()
+ ->getNodeList();
+ foreach ($descriptiveSections as $descriptiveSection) {
+ $this->validateDescriptiveMetadataSection($descriptiveSection);
+ }
+
+ // there must be one primary structural element
+ $structureElement = $this->createNodeListValidator(VH::XPATH_LOGICAL_STRUCTURAL_ELEMENTS)
+ ->validateHasOne()
+ ->getFirstNode();
+
+ $this->createNodeValidator($structureElement)
+ ->validateHasReferenceToId('DMDID', VH::XPATH_DESCRIPTIVE_METADATA_SECTIONS);
+ }
+
+ /**
+ * Validates the embedded metadata.
+ *
+ * Validates against the rules of chapter "2.5.2.1 Eingebettete Metadaten – mets:mdWrap"
+ *
+ * @return void
+ */
+ protected function validateDescriptiveMetadataSection(\DOMNode $descriptiveSection): void
+ {
+ $mdWrap = $this->createNodeListValidator('mets:mdWrap', $descriptiveSection)
+ ->validateHasOne()
+ ->getFirstNode();
+
+ $this->createNodeValidator($mdWrap)
+ ->validateHasAttributeWithValue('MDTYPE', ['MODS', 'TEIHDR']);
+
+ if (!$mdWrap) {
+ return;
+ }
+
+ // @phpstan-ignore-next-line
+ $mdType = $mdWrap->getAttribute('MDTYPE');
+ if ($mdType == 'TEIHDR' || $mdType == 'MODS') {
+ $childNode = 'mods:mods';
+ if ($mdType == 'TEIHDR') {
+ $childNode = 'tei:teiHeader';
+ }
+ $this->createNodeListValidator('mets:xmlData[' . $childNode . ']', $mdWrap)
+ ->validateHasOne();
+ }
+ }
+}
diff --git a/Classes/Validation/Mets/DigitalRepresentationValidator.php b/Classes/Validation/Mets/DigitalRepresentationValidator.php
new file mode 100644
index 000000000..d8a51934b
--- /dev/null
+++ b/Classes/Validation/Mets/DigitalRepresentationValidator.php
@@ -0,0 +1,116 @@
+
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ */
+
+use Slub\Dfgviewer\Common\ValidationHelper as VH;
+use Slub\Dfgviewer\Validation\AbstactDomDocumentValidator;
+
+/**
+ * The validator validates against the rules outlined in chapter 2.4 of the METS application profile 2.3.1.
+ *
+ * @package TYPO3
+ * @subpackage dfg-viewer
+ *
+ * @access public
+ */
+class DigitalRepresentationValidator extends AbstactDomDocumentValidator
+{
+ public function isValidDocument(): void
+ {
+ // Validates against the rules of chapter "2.4.1 Dateisektion – mets:fileSec"
+ $this->createNodeListValidator(VH::XPATH_FILE_SECTIONS)
+ ->validateHasNoneOrOne();
+
+ // If a physical structure is present, there must be one file section.
+ if ($this->xpath->query(VH::XPATH_PHYSICAL_STRUCTURES)->length > 0) {
+ $this->createNodeListValidator(VH::XPATH_FILE_SECTIONS)
+ ->validateHasOne();
+ }
+
+ if ($this->xpath->query(VH::XPATH_FILE_SECTIONS)->length > 0) {
+ $this->validateFileGroups();
+ $this->validateFiles();
+ }
+ }
+
+ /**
+ * Validates the file groups.
+ *
+ * Validates against the rules of chapter "2.4.2.1 Dateigruppen – mets:fileGrp"
+ *
+ * @return void
+ */
+ protected function validateFileGroups(): void
+ {
+ $fileSectionGroups = $this->createNodeListValidator(VH::XPATH_FILE_SECTION_GROUPS)
+ ->validateHasAny()
+ ->getNodeList();
+ foreach ($fileSectionGroups as $fileSectionGroup) {
+ $this->validateFileGroup($fileSectionGroup);
+ }
+
+ $this->createNodeListValidator(VH::XPATH_FILE_SECTION_GROUPS . '[@USE="DEFAULT"]')
+ ->validateHasOne();
+ }
+
+ protected function validateFileGroup(\DOMNode $fileGroup): void
+ {
+ $this->createNodeValidator($fileGroup)
+ ->validateHasUniqueAttribute("USE", VH::XPATH_FILE_SECTION_GROUPS);
+ }
+
+ /**
+ * Validates the files.
+ *
+ * Validates against the rules of chapters "2.4.2.2 Datei – mets:fileGrp/mets:file" and "2.4.2.3 Dateilink – mets:fileGrp/mets:file/mets:FLocat"
+ *
+ * @return void
+ */
+ protected function validateFiles(): void
+ {
+ $files = $this->createNodeListValidator(VH::XPATH_FILE_SECTION_FILES)
+ ->validateHasAny()
+ ->getNodeList();
+ foreach ($files as $file) {
+ $this->validateFile($file);
+ }
+ }
+
+ protected function validateFile(\DOMNode $file): void
+ {
+ $this->createNodeValidator($file)
+ ->validateHasUniqueId()
+ ->validateHasAttribute('MIMETYPE');
+
+ $fLocat = $this->createNodeListValidator('mets:FLocat', $file)
+ ->validateHasOne()
+ ->getFirstNode();
+
+ $this->createNodeValidator($fLocat)
+ ->validateHasAttributeWithValue('LOCTYPE', ['URL', 'PURL'])
+ ->validateHasAttributeWithUrl('xlink:href');
+ }
+}
diff --git a/Classes/Validation/Mets/LinkingLogicalPhysicalStructureValidator.php b/Classes/Validation/Mets/LinkingLogicalPhysicalStructureValidator.php
new file mode 100644
index 000000000..ae50de37a
--- /dev/null
+++ b/Classes/Validation/Mets/LinkingLogicalPhysicalStructureValidator.php
@@ -0,0 +1,73 @@
+
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ */
+
+use Slub\Dfgviewer\Common\ValidationHelper as VH;
+use Slub\Dfgviewer\Validation\AbstactDomDocumentValidator;
+
+/**
+ * The validator validates against the rules outlined in chapter 2.3 of the METS application profile 2.3.1.
+ *
+ * @package TYPO3
+ * @subpackage dfg-viewer
+ *
+ * @access public
+ */
+class LinkingLogicalPhysicalStructureValidator extends AbstactDomDocumentValidator
+{
+ public function isValidDocument(): void
+ {
+ // Validates against the rules of chapter "2.3.1 Structure links - mets:structLink"
+ $this->createNodeListValidator(VH::XPATH_STRUCT_LINK)
+ ->validateHasNoneOrOne();
+
+ $this->validateLinkElements();
+ }
+
+ /**
+ * Validates the linking elements.
+ *
+ * Validates against the rules of chapter "2.3.2.1 Linking – mets:smLink"
+ *
+ * @return void
+ */
+ protected function validateLinkElements(): void
+ {
+ $linkElements = $this->createNodeListValidator(VH::XPATH_STRUCT_LINK_ELEMENTS)
+ ->validateHasAny()
+ ->getNodeList();
+ foreach ($linkElements as $linkElement) {
+ $this->validateLinkElement($linkElement);
+ }
+ }
+
+ protected function validateLinkElement(\DOMNode $linkElement): void
+ {
+ $this->createNodeValidator($linkElement)
+ ->validateHasReferenceToId("xlink:from", VH::XPATH_LOGICAL_STRUCTURES)
+ ->validateHasReferenceToId("xlink:to", VH::XPATH_PHYSICAL_STRUCTURES);
+ }
+}
diff --git a/Classes/Validation/Mets/LogicalStructureValidator.php b/Classes/Validation/Mets/LogicalStructureValidator.php
new file mode 100644
index 000000000..e2e5194b4
--- /dev/null
+++ b/Classes/Validation/Mets/LogicalStructureValidator.php
@@ -0,0 +1,110 @@
+
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ */
+
+use Slub\Dfgviewer\Common\ValidationHelper as VH;
+use Slub\Dfgviewer\Validation\AbstactDomDocumentValidator;
+
+/**
+ * The validator validates against the rules outlined in chapter 2.1 of the METS application profile 2.3.1.
+ *
+ * @package TYPO3
+ * @subpackage dfg-viewer
+ *
+ * @access public
+ */
+class LogicalStructureValidator extends AbstactDomDocumentValidator
+{
+ public function isValidDocument(): void
+ {
+ // Validates against the rules of chapter "2.1.1 Logical structure - mets:structMap"
+ $this->createNodeListValidator(VH::XPATH_LOGICAL_STRUCTURES)
+ ->validateHasAny();
+
+ $this->validateStructuralElements();
+ $this->validateExternalReferences();
+ $this->validatePeriodicPublishingSequences();
+ }
+
+ /**
+ * Validates the structural elements.
+ *
+ * Validates against the rules of chapter "2.1.2.1 Structural element - mets:div"
+ *
+ * @return void
+ */
+ protected function validateStructuralElements(): void
+ {
+ $structuralElements = $this->createNodeListValidator(VH::XPATH_LOGICAL_STRUCTURAL_ELEMENTS)
+ ->validateHasAny()
+ ->getNodeList();
+ foreach ($structuralElements as $structuralElement) {
+ $this->validateStructuralElement($structuralElement);
+ }
+ }
+
+ protected function validateStructuralElement(\DOMNode $structureElement): void
+ {
+ $this->createNodeValidator($structureElement)
+ ->validateHasUniqueId()
+ ->validateHasAttributeWithValue("TYPE", VH::STRUCTURE_DATASET);
+ }
+
+ /**
+ * Validates the external references.
+ *
+ * Validates against the rules of chapter "2.1.2.2 Reference to external METS-files - mets:div / mets:mptr"
+ *
+ * @return void
+ */
+ protected function validateExternalReferences(): void
+ {
+ $externalReferences = $this->createNodeListValidator(VH::XPATH_LOGICAL_EXTERNAL_REFERENCES)
+ ->validateHasNoneOrOne()
+ ->getNodeList();
+ foreach ($externalReferences as $externalReference) {
+ $this->validateExternalReference($externalReference);
+ }
+ }
+
+ protected function validateExternalReference(\DOMNode $externalReference): void
+ {
+ $this->createNodeValidator($externalReference)
+ ->validateHasAttributeWithValue("LOCTYPE", ["URL", "PURL"])
+ ->validateHasAttributeWithUrl("xlink:href");
+ }
+
+ /**
+ * Validates the periodic publishing sequences.
+ *
+ * Validates against the rules of chapter "2.1.3 Periodic publishing sequences"
+ *
+ * @return void
+ */
+ protected function validatePeriodicPublishingSequences(): void
+ {
+ }
+}
diff --git a/Classes/Validation/Mets/PhysicalStructureValidator.php b/Classes/Validation/Mets/PhysicalStructureValidator.php
new file mode 100644
index 000000000..feed4e351
--- /dev/null
+++ b/Classes/Validation/Mets/PhysicalStructureValidator.php
@@ -0,0 +1,81 @@
+
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ */
+
+use Slub\Dfgviewer\Common\ValidationHelper as VH;
+use Slub\Dfgviewer\Validation\AbstactDomDocumentValidator;
+
+/**
+ * The validator validates against the rules outlined in chapter 2.2 of the METS application profile 2.3.1.
+ *
+ * @package TYPO3
+ * @subpackage dfg-viewer
+ *
+ * @access public
+ */
+class PhysicalStructureValidator extends AbstactDomDocumentValidator
+{
+ public function isValidDocument(): void
+ {
+ // Validates against the rules of chapter "2.2.1 Physical structure - mets:structMap"
+ $this->createNodeListValidator(VH::XPATH_PHYSICAL_STRUCTURES)
+ ->validateHasNoneOrOne();
+
+ $this->validateStructuralElements();
+ }
+
+ /**
+ *
+ * Validates the structural elements.
+ *
+ * Validates against the rules of chapter "2.2.2.1 Structural element - mets:div"
+ *
+ * @return void
+ */
+ protected function validateStructuralElements(): void
+ {
+ $node = $this->createNodeListValidator(VH::XPATH_PHYSICAL_STRUCTURAL_ELEMENT_SEQUENCE)
+ ->validateHasOne()
+ ->getFirstNode();
+
+ $this->createNodeValidator($node)
+ ->validateHasAttributeWithValue('TYPE', ['physSequence']);
+
+ $structuralElements = $this->createNodeListValidator(VH::XPATH_PHYSICAL_STRUCTURAL_ELEMENTS)
+ ->validateHasAny()
+ ->getNodeList();
+ foreach ($structuralElements as $structuralElement) {
+ $this->validateStructuralElement($structuralElement);
+ }
+ }
+
+ protected function validateStructuralElement(\DOMNode $structureElement): void
+ {
+ $this->createNodeValidator($structureElement)
+ ->validateHasUniqueId()
+ ->validateHasAttributeWithValue("TYPE", ["page", "doublepage", "track"]);
+ }
+}
diff --git a/Configuration/TypoScript/Plugins/kitodo.typoscript b/Configuration/TypoScript/Plugins/kitodo.typoscript
index bf2b3cf0a..eb5a7f10e 100644
--- a/Configuration/TypoScript/Plugins/kitodo.typoscript
+++ b/Configuration/TypoScript/Plugins/kitodo.typoscript
@@ -28,6 +28,31 @@ plugin.tx_dlf {
}
settings {
storagePid = {$plugin.tx_dlf.persistence.storagePid}
+ domDocumentValidationValidators {
+ 10 {
+ title = XML-Schemes Validator
+ className = Kitodo\Dlf\Validation\XmlSchemesValidator
+ breakOnError = false
+ configuration {
+ oai {
+ namespace = http://www.openarchives.org/OAI/2.0/
+ schemaLocation = https://www.openarchives.org/OAI/2.0/OAI-PMH.xsd
+ }
+ mets {
+ namespace = http://www.loc.gov/METS/
+ schemaLocation = http://www.loc.gov/standards/mets/mets.xsd
+ }
+ mods {
+ namespace = http://www.loc.gov/mods/v3
+ schemaLocation = http://www.loc.gov/standards/mods/mods.xsd
+ }
+ }
+ }
+ 20 {
+ title = Application Profile Validation
+ className = Slub\Dfgviewer\Validation\ApplicationProfileValidationStack
+ }
+ }
}
view {
partialRootPaths {
diff --git a/Tests/Fixtures/mets.xml b/Tests/Fixtures/mets.xml
new file mode 100644
index 000000000..2d1193f71
--- /dev/null
+++ b/Tests/Fixtures/mets.xml
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+ "INDIA"
+
+
+ none
+
+
+ "tecnoarcadia"
+
+ aut
+
+
+
+ none
+
+
+ https://zenodo.org/api/files/df31cc16-0a54-4599-89e9-0dd4ada4016d/b5df7cd550f64e818943ad96fff7e902.glb
+
+ prv
+
+
+
+
+
+
+
+
+
+
+
+
+
+ none
+
+
+
+
+
+
+
+ cre
+
+
+
+ 0
+ 0
+
+
+
+ "not available"
+ none
+ none
+ none
+
+
+ none
+ CC-BY
+
+
+ INDIA
+
+
+
+
+
+
+
+
+
+
+ 3D Repository
+ http://dfg-viewer.de/fileadmin/_processed_/a/8/csm_HSM_Logo_T_schwarz_klein_bold_regular_d61a371993.jpg
+ https://3d-repository.hs-mainz.de/
+ https://3d-repository.hs-mainz.de/contact
+ Aggregator
+ http://dfg-viewer.de/fileadmin/_processed_/a/8/csm_HSM_Logo_T_schwarz_klein_bold_regular_d61a371993.jpg
+ https://3d-repository.hs-mainz.de/
+ Sponsor
+ http://dfg-viewer.de/fileadmin/_processed_/a/8/csm_HSM_Logo_T_schwarz_klein_bold_regular_d61a371993.jpg
+ https://3d-repository.hs-mainz.de/
+ pdm
+
+
+
+
+
+
+
+
+ http://slub-dresden.de/FOZK.pl?PPN=356448053
+ http://slub-dresden.de/356448053
+ http://digital.slub-dresden.de/sru/356448053
+ http://digital.slub-dresden.de/iiif/356448053.xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/Unit/Validation/AbstractDomDocumentValidatorTest.php b/Tests/Unit/Validation/AbstractDomDocumentValidatorTest.php
new file mode 100644
index 000000000..db5986431
--- /dev/null
+++ b/Tests/Unit/Validation/AbstractDomDocumentValidatorTest.php
@@ -0,0 +1,363 @@
+
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ */
+
+use DOMDocument;
+use DOMElement;
+use DOMXPath;
+use Kitodo\Dlf\Validation\AbstractDlfValidator;
+use TYPO3\CMS\Extbase\Error\Result;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+abstract class AbstractDomDocumentValidatorTest extends UnitTestCase
+{
+
+ /**
+ * @var AbstractDlfValidator
+ */
+ protected $validator;
+
+ /**
+ * @var DOMDocument
+ */
+ protected $doc;
+
+ abstract protected function createValidator(): AbstractDlfValidator;
+
+ public function setUp(): void
+ {
+ parent::setUp();
+ $this->resetSingletonInstances = true;
+ $this->doc = $this->getDomDocument();
+ $this->validator = $this->createValidator();
+ }
+
+ /**
+ * Validates the document using the created validator.
+ *
+ * @return void
+ */
+ public function testDocument()
+ {
+ $this->hasNoError();
+ }
+
+ /**
+ * Validates using validator and DOMDocument
+ *
+ * @return Result
+ */
+ protected function validate(): Result
+ {
+ return $this->validator->validate($this->doc);
+ }
+
+ /**
+ * Validates using validator and DOMDocument and assert result error message for equality.
+ *
+ * Validates using a validator and DOMDocument, then asserts that the resulting error message matches the expected value.
+ *
+ * @param $message string
+ * @return void
+ */
+ protected function validateAndAssertEquals(string $message): void
+ {
+ $result = $this->validator->validate($this->doc);
+ self::assertEquals($message, $result->getFirstError()->getMessage());
+ }
+
+ /**
+ * Reset the document.
+ *
+ * @return void
+ */
+ protected function resetDocument(): void
+ {
+ $this->doc = $this->getDomDocument();
+ }
+
+ /**
+ * Add child node with name and namespace to DOMDocument.
+ *
+ * @param string $expression
+ * @param string $namespace
+ * @param string $name
+ * @return void
+ * @throws \DOMException
+ */
+ protected function addChildNodeWithNamespace(string $expression, string $namespace, string $name): void
+ {
+ $this->addChildNode($expression, $this->doc->createElementNS($namespace, $name));
+ }
+
+ /**
+ * Add node as child node to DOMDocument.
+ *
+ * @param string $expression
+ * @param DOMElement $newNode
+ * @return void
+ */
+ protected function addChildNode(string $expression, DOMElement $newNode): void
+ {
+ $xpath = new DOMXPath($this->doc);
+ foreach ($xpath->evaluate($expression) as $node) {
+ $node->appendChild($newNode);
+ }
+ }
+
+ /**
+ * Remove notes found by node expression in DOMDocument.
+ *
+ * @param string $expression
+ * @return void
+ */
+ protected function removeNodes(string $expression): void
+ {
+ $xpath = new DOMXPath($this->doc);
+ foreach ($xpath->query($expression) as $node) {
+ $node->parentNode->removeChild($node);
+ }
+ }
+
+ /**
+ * Set value of attribute found by node expression in DOMDocument.
+ *
+ * @param string $expression
+ * @param string $attribute
+ * @param string $value
+ * @return void
+ */
+ protected function setAttributeValue(string $expression, string $attribute, string $value): void
+ {
+ $xpath = new DOMXPath($this->doc);
+ foreach ($xpath->evaluate($expression) as $node) {
+ $node->setAttribute($attribute, $value);
+ }
+ }
+
+ /**
+ * Remove attribute found by node expression in DOMDocument.
+ *
+ * @param string $expression
+ * @param string $attribute
+ * @return void
+ */
+ protected function removeAttribute(string $expression, string $attribute): void
+ {
+ $xpath = new DOMXPath($this->doc);
+ foreach ($xpath->evaluate($expression) as $node) {
+ $node->removeAttribute($attribute);
+ }
+ }
+
+ /**
+ * Set value of content found by node expression in DOMDocument.
+ *
+ * @param string $expression
+ * @param string $value
+ * @return void
+ */
+ protected function setContentValue(string $expression, string $value): void
+ {
+ $xpath = new DOMXPath($this->doc);
+ foreach ($xpath->evaluate($expression) as $node) {
+ $node->nodeValue = $value;
+ }
+ }
+
+ /**
+ * Gets the doc from xml file.
+ *
+ * @return DOMDocument
+ */
+ protected function getDomDocument(): DOMDocument
+ {
+ $doc = new DOMDocument();
+ $doc->load(__DIR__ . '/../../Fixtures/mets.xml');
+ self::assertNotFalse($doc);
+ return $doc;
+ }
+
+ /**
+ * Assert validation has no error.
+ *
+ * @return void
+ */
+ protected function hasNoError(): void
+ {
+ $result = $this->validate();
+ $this->assertFalse($result->hasErrors());
+ }
+
+ /**
+ * Assert error of has any validation.
+ *
+ * @param string $expression The expression in error message
+ * @param string $context The context in error message
+ * @return void
+ */
+ protected function hasErrorAny(string $expression, string $context=''): void
+ {
+ $message = 'There must be at least one element that matches the XPath expression "' . $expression . '"';
+ if ($context != '') {
+ $message .= ' under "' . $context . '"';
+ }
+ $this->validateAndAssertEquals($message);
+ }
+
+ /**
+ * Assert error of has one validation.
+ *
+ * @param string $expression The expression in error message
+ * @param string $context The context in error message
+ * @return void
+ */
+ protected function hasErrorOne(string $expression, string $context=''): void
+ {
+ $message = 'There must be an element that matches the XPath expression "' . $expression . '"';
+ if ($context != '') {
+ $message .= ' under "' . $context . '"';
+ }
+ $this->validateAndAssertEquals($message);
+ }
+
+ /**
+ * Assert error of has none or one validation.
+ *
+ * @param string $expression The expression in error message
+ * @param string $context The context in error message
+ * @return void
+ */
+ protected function hasErrorNoneOrOne(string $expression, string $context=''): void
+ {
+ $message = 'There must be no more than one element that matches the XPath expression "' . $expression . '"';
+ if ($context != '') {
+ $message .= ' under "' . $context . '"';
+ }
+ $this->validateAndAssertEquals($message);
+ }
+
+ /**
+ * Assert error of has attribute validation.
+ *
+ * @param string $expression The expression in error message
+ * @param string $name The attribute name
+ * @return void
+ */
+ protected function hasErrorAttribute(string $expression, string $name): void
+ {
+ $this->validateAndAssertEquals('Mandatory "' . $name . '" attribute of "' . $expression . '" is missing.');
+ }
+
+ /**
+ * Assert error of has attribute with value validation.
+ *
+ * @param string $expression The expression in error message
+ * @param string $name The attribute name
+ * @param string $value The attribute value
+ * @return void
+ */
+ protected function hasErrorAttributeWithValue(string $expression, string $name, string $value): void
+ {
+ $this->validateAndAssertEquals('Value "' . $value . '" in the "' . $name . '" attribute of "' . $expression . '" is not permissible.');
+ }
+
+ /**
+ * Assert error of has attribute with URL value validation.
+ *
+ * @param string $expression The expression in error message
+ * @param string $name The attribute name
+ * @param string $value The attribute value
+ * @return void
+ */
+ protected function hasErrorAttributeWithUrl(string $expression, string $name, string $value): void
+ {
+ $this->validateAndAssertEquals('URL "' . $value . '" in the "' . $name . '" attribute of "' . $expression . '" is not valid.');
+ }
+
+ /**
+ * Assert error of has attribute reference to one validation.
+ *
+ * @param string $expression The expression in error message
+ * @param string $name The attribute name
+ * @param string $value The attribute value
+ * @param string $targetExpression The target context expression
+ * @return void
+ */
+ protected function hasErrorAttributeRefToOne(string $expression, string $name, string $value, string $targetExpression): void
+ {
+ $this->validateAndAssertEquals('Value "' . $value . '" in the "' . $name . '" attribute of "' . $expression . '" must reference one element under XPath expression "' . $targetExpression);
+ }
+
+ /**
+ * Assert error of has content with Email validation.
+ *
+ * @param string $expression The expression in error message
+ * @param string $value The content value
+ * @return void
+ */
+ protected function hasErrorContentWithEmail(string $expression, string $value): void
+ {
+ $this->validateAndAssertEquals('Email "' . $value . '" in the content of "' . $expression . '" is not valid.');
+ }
+
+ /**
+ * Assert error of has content with URL.
+ *
+ * @param string $expression The expression in error message
+ * @param string $value The content value
+ * @return void
+ */
+ protected function hasErrorContentWithUrl(string $expression, string $value): void
+ {
+ $this->validateAndAssertEquals('URL "' . $value . '" in the content of "' . $expression . '" is not valid.');
+ }
+
+ /**
+ * Assert error of has unique identifier.
+ *
+ * @param string $expression The expression in error message
+ * @param string $value The attribute value
+ * @return void
+ */
+ protected function hasErrorUniqueId(string $expression, string $value): void
+ {
+ $this->hasErrorUniqueAttribute($expression, 'ID', $value);
+ }
+
+ /**
+ * Assert error of has unique attribute with value.
+ *
+ * @param string $expression The expression in error message
+ * @param string $name The attribute name
+ * @param string $value The attribute value
+ * @return void
+ */
+ protected function hasErrorUniqueAttribute(string $expression, string $name, string $value): void
+ {
+ $this->validateAndAssertEquals('"' . $name . '" attribute with value "' . $value . '" of "' . $expression . '" already exists.');
+ }
+}
diff --git a/Tests/Unit/Validation/DvMetadataValidatorTest.php b/Tests/Unit/Validation/DvMetadataValidatorTest.php
new file mode 100644
index 000000000..60afb738f
--- /dev/null
+++ b/Tests/Unit/Validation/DvMetadataValidatorTest.php
@@ -0,0 +1,154 @@
+
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ */
+
+use Kitodo\Dlf\Validation\AbstractDlfValidator;
+use Slub\Dfgviewer\Common\ValidationHelper as VH;
+use Slub\Dfgviewer\Validation\DvMetadataValidator;
+
+class DvMetadataValidatorTest extends AbstractDomDocumentValidatorTest
+{
+ /**
+ * Test validation against the rules of chapter "2.7.1 Rechteangaben – dv:rights"
+ *
+ * @return void
+ */
+ public function testDvRights(): void
+ {
+ $this->removeNodes(VH::XPATH_DVRIGHTS);
+ $this->hasErrorOne(VH::XPATH_DVRIGHTS);
+ }
+
+ /**
+ * Test validation against the rules of chapter "2.7.2 Unterelemente zu dv:rights"
+ *
+ * @return void
+ * @throws \DOMException
+ */
+ public function testDvRightsSubelements(): void
+ {
+ $this->removeNodes(VH::XPATH_DVRIGHTS . '/dv:owner');
+ $this->hasErrorOne(VH::XPATH_DVRIGHTS . '/dv:owner');
+ $this->resetDocument();
+
+ $this->assertNodeContent(VH::XPATH_DVRIGHTS . '/dv:ownerLogo', VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA) . '/mets:mdWrap/mets:xmlData/dv:rights/dv:ownerLogo');
+ $this->assertNodeContent(VH::XPATH_DVRIGHTS . '/dv:ownerSiteURL', VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA) . '/mets:mdWrap/mets:xmlData/dv:rights/dv:ownerSiteURL');
+ $this->assertNodeContent(VH::XPATH_DVRIGHTS . '/dv:ownerContact', VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA) . '/mets:mdWrap/mets:xmlData/dv:rights/dv:ownerContact');
+ $this->setContentValue(VH::XPATH_DVRIGHTS . '/dv:ownerContact', 'mailto:Test');
+ $this->hasErrorContentWithEmail(VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA) . '/mets:mdWrap/mets:xmlData/dv:rights/dv:ownerContact', 'mailto:Test');
+ $this->resetDocument();
+
+ $this->addChildNodeWithNamespace(VH::XPATH_DVRIGHTS, VH::NAMESPACE_DV, 'dv:aggregator');
+ $this->hasErrorNoneOrOne(VH::XPATH_DVRIGHTS . '/dv:aggregator');
+ $this->resetDocument();
+ $this->assertOptionalNodeContent(VH::XPATH_DVRIGHTS, 'dv:aggregatorLogo', VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA) . '/mets:mdWrap/mets:xmlData/dv:rights/dv:aggregatorLogo');
+ $this->assertOptionalNodeContent(VH::XPATH_DVRIGHTS, 'dv:aggregatorSiteURL', VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA) . '/mets:mdWrap/mets:xmlData/dv:rights/dv:aggregatorSiteURL');
+
+ $this->addChildNodeWithNamespace(VH::XPATH_DVRIGHTS, VH::NAMESPACE_DV, 'dv:sponsor');
+ $this->hasErrorNoneOrOne(VH::XPATH_DVRIGHTS . '/dv:sponsor');
+ $this->resetDocument();
+ $this->assertOptionalNodeContent(VH::XPATH_DVRIGHTS, 'dv:sponsorLogo', VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA) . '/mets:mdWrap/mets:xmlData/dv:rights/dv:sponsorLogo');
+ $this->assertOptionalNodeContent(VH::XPATH_DVRIGHTS, 'dv:sponsorSiteURL', VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA) . '/mets:mdWrap/mets:xmlData/dv:rights/dv:sponsorSiteURL');
+
+ $this->setContentValue(VH::XPATH_DVRIGHTS . '/dv:license', 'Test');
+ $this->hasErrorContentWithUrl(VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA) . '/mets:mdWrap/mets:xmlData/dv:rights/dv:license', 'Test');
+ }
+
+ /**
+ * Test validation against the rules of chapter "2.7.3 Verweise – dv:links"
+ *
+ * @return void
+ */
+ public function testDvLinks(): void
+ {
+ $this->removeNodes(VH::XPATH_DVLINKS);
+ $this->hasErrorOne(VH::XPATH_DVLINKS);
+ }
+
+ /**
+ * Test validation against the rules of chapter "2.7.4 Unterelemente zu dv:links"
+ *
+ * @return void
+ * @throws \DOMException
+ */
+ public function testDvLinksSubelements(): void
+ {
+ $this->removeNodes(VH::XPATH_DVLINKS . '/dv:reference');
+ $this->hasErrorAny(VH::XPATH_DVLINKS . '/dv:reference');
+ $this->resetDocument();
+
+ // if there are multiple `dv:references`, the `linktext` attribute must be present.
+ $this->addChildNodeWithNamespace(VH::XPATH_DVLINKS, VH::NAMESPACE_DV, 'dv:reference');
+ $this->hasErrorAttribute(VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_DIGIPROV_METADATA) . '/mets:mdWrap/mets:xmlData/dv:links/dv:reference[1]', 'linktext');
+ $this->resetDocument();
+
+ $this->addChildNodeWithNamespace(VH::XPATH_DVLINKS, VH::NAMESPACE_DV, 'dv:presentation');
+ $this->hasErrorNoneOrOne(VH::XPATH_DVLINKS . '/dv:presentation');
+ $this->resetDocument();
+
+ $this->addChildNodeWithNamespace(VH::XPATH_DVLINKS, VH::NAMESPACE_DV, 'dv:sru');
+ $this->hasErrorNoneOrOne(VH::XPATH_DVLINKS . '/dv:sru');
+ $this->resetDocument();
+
+ $this->setContentValue(VH::XPATH_DVLINKS . '/dv:sru', 'Test');
+ $this->hasErrorContentWithUrl(VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_DIGIPROV_METADATA) . '/mets:mdWrap/mets:xmlData/dv:links/dv:sru', 'Test');
+ $this->resetDocument();
+
+ $this->addChildNodeWithNamespace(VH::XPATH_DVLINKS, VH::NAMESPACE_DV, 'dv:iiif');
+ $this->hasErrorNoneOrOne(VH::XPATH_DVLINKS . '/dv:iiif');
+ $this->resetDocument();
+
+ $this->setContentValue(VH::XPATH_DVLINKS . '/dv:iiif', 'Test');
+ $this->hasErrorContentWithUrl(VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_DIGIPROV_METADATA) . '/mets:mdWrap/mets:xmlData/dv:links/dv:iiif', 'Test');
+ }
+
+ protected function assertNodeContent(string $expression, string $expectedExpression): void
+ {
+ $this->removeNodes($expression);
+ $this->hasErrorOne($expression);
+ $this->resetDocument();
+
+ $this->setContentValue($expression, 'Test');
+ $this->hasErrorContentWithUrl($expectedExpression, 'Test');
+ $this->resetDocument();
+ }
+
+ protected function assertOptionalNodeContent(string $expression, string $name, string $expectedExpression): void
+ {
+ $this->addChildNodeWithNamespace($expression, VH::NAMESPACE_DV, $name);
+ $this->hasErrorNoneOrOne($expression . '/' . $name);
+ $this->resetDocument();
+
+ $this->setContentValue($expression . '/' . $name, 'Test');
+ $this->hasErrorContentWithUrl($expectedExpression, 'Test');
+ $this->resetDocument();
+ }
+
+ protected function createValidator(): AbstractDlfValidator
+ {
+ return new DvMetadataValidator();
+ }
+}
diff --git a/Tests/Unit/Validation/Mets/AdministrativeMetadataValidatorTest.php b/Tests/Unit/Validation/Mets/AdministrativeMetadataValidatorTest.php
new file mode 100644
index 000000000..e470c09b9
--- /dev/null
+++ b/Tests/Unit/Validation/Mets/AdministrativeMetadataValidatorTest.php
@@ -0,0 +1,153 @@
+
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ */
+
+use Kitodo\Dlf\Validation\AbstractDlfValidator;
+use Slub\Dfgviewer\Common\ValidationHelper as VH;
+use Slub\Dfgviewer\Validation\Mets\AdministrativeMetadataValidator;
+
+class AdministrativeMetadataValidatorTest extends AbstractDomDocumentValidatorTest
+{
+ /**
+ * Test validation against the rules of chapter "2.6.1 Metadatensektion – mets:amdSec"
+ *
+ * @return void
+ */
+ public function testAdministrativeMetadata(): void
+ {
+ $this->removeNodes(VH::XPATH_ADMINISTRATIVE_METADATA);
+ $this->hasErrorAny(VH::XPATH_ADMINISTRATIVE_METADATA);
+ $this->resetDocument();
+
+ $this->removeNodes(VH::XPATH_ADMINISTRATIVE_METADATA . '/mets:rightsMD');
+ $this->hasErrorOne(VH::XPATH_ADMINISTRATIVE_METADATA . '[mets:rightsMD and mets:digiprovMD]');
+ $this->resetDocument();
+
+ $this->setAttributeValue(VH::XPATH_ADMINISTRATIVE_METADATA, 'ID', 'DMDLOG_0001');
+ $this->hasErrorUniqueId(VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_METADATA), 'DMDLOG_0001');
+
+ $this->removeAttribute(VH::XPATH_ADMINISTRATIVE_METADATA, 'ID');
+ $this->hasErrorAttribute(VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_METADATA), 'ID');
+ }
+
+ /**
+ * Test validation against the rules of chapters "2.6.2.5 Herstellung – mets:digiprovMD" and "2.6.2.6 Eingebettete Verweise – mets:digiprovMD/mets:mdWrap"
+ *
+ * @return void
+ */
+ public function testDigitalProvenanceMetadataStructure(): void
+ {
+ $this->setAttributeValue(VH::XPATH_ADMINISTRATIVE_DIGIPROV_METADATA, 'ID', 'DMDLOG_0001');
+ $this->hasErrorUniqueId(VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_DIGIPROV_METADATA), 'DMDLOG_0001');
+ $this->resetDocument();
+
+ $this->removeNodes(VH::XPATH_ADMINISTRATIVE_DIGIPROV_METADATA . '/mets:mdWrap');
+ $this->hasErrorOne('mets:mdWrap', VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_DIGIPROV_METADATA));
+ $this->resetDocument();
+
+ $this->removeAttribute(VH::XPATH_ADMINISTRATIVE_DIGIPROV_METADATA . '/mets:mdWrap', 'MDTYPE');
+ $this->hasErrorAttribute(VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_DIGIPROV_METADATA) . '/mets:mdWrap', 'MDTYPE');
+
+ $this->setAttributeValue(VH::XPATH_ADMINISTRATIVE_DIGIPROV_METADATA . '/mets:mdWrap', 'MDTYPE', 'Test');
+ $this->hasErrorAttributeWithValue(VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_DIGIPROV_METADATA) . '/mets:mdWrap', 'MDTYPE', 'Test');
+ $this->resetDocument();
+
+ $this->removeAttribute(VH::XPATH_ADMINISTRATIVE_DIGIPROV_METADATA . '/mets:mdWrap', 'OTHERMDTYPE');
+ $this->hasErrorAttribute(VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_DIGIPROV_METADATA) . '/mets:mdWrap', 'OTHERMDTYPE');
+
+ $this->setAttributeValue(VH::XPATH_ADMINISTRATIVE_DIGIPROV_METADATA . '/mets:mdWrap', 'OTHERMDTYPE', 'Test');
+ $this->hasErrorAttributeWithValue(VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_DIGIPROV_METADATA) . '/mets:mdWrap', 'OTHERMDTYPE', 'Test');
+ $this->resetDocument();
+
+ $this->removeNodes(VH::XPATH_ADMINISTRATIVE_DIGIPROV_METADATA . '/mets:mdWrap/mets:xmlData/dv:links');
+ $this->hasErrorOne('mets:xmlData[dv:links]', VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_DIGIPROV_METADATA) . '/mets:mdWrap');
+ }
+
+ /**
+ * Test validation against the rules of chapters "2.6.2.4 Rechtedeklaration – mets:rightsMD" and "2.6.2.4 Eingebettete Rechteangaben – mets:rightsMD/mets:mdWrap"
+ *
+ * @return void
+ */
+ public function testRightsMetadataStructure(): void
+ {
+ $this->setAttributeValue(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA, 'ID', 'DMDLOG_0001');
+ $this->hasErrorUniqueId(VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA), 'DMDLOG_0001');
+ $this->resetDocument();
+
+ $this->removeNodes(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA . '/mets:mdWrap');
+ $this->hasErrorOne('mets:mdWrap', VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA));
+ $this->resetDocument();
+
+ $this->removeAttribute(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA . '/mets:mdWrap', 'MDTYPE');
+ $this->hasErrorAttribute(VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA) . '/mets:mdWrap', 'MDTYPE');
+
+ $this->setAttributeValue(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA . '/mets:mdWrap', 'MDTYPE', 'Test');
+ $this->hasErrorAttributeWithValue(VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA) . '/mets:mdWrap', 'MDTYPE', 'Test');
+ $this->resetDocument();
+
+ $this->removeAttribute(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA . '/mets:mdWrap', 'OTHERMDTYPE');
+ $this->hasErrorAttribute(VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA) . '/mets:mdWrap', 'OTHERMDTYPE');
+
+ $this->setAttributeValue(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA . '/mets:mdWrap', 'OTHERMDTYPE', 'Test');
+ $this->hasErrorAttributeWithValue(VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA) . '/mets:mdWrap', 'OTHERMDTYPE', 'Test');
+ $this->resetDocument();
+
+ $this->removeNodes(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA . '/mets:mdWrap/mets:xmlData/dv:rights');
+ $this->hasErrorOne('mets:xmlData[dv:rights]', VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_RIGHTS_METADATA) . '/mets:mdWrap');
+ }
+
+ /**
+ * Test validation against the rules of chapters "2.6.2.1 Technische Metadaten – mets:techMD" and "2.6.2.2 Eingebettete technische Daten – mets:techMD/mets:mdWrap"
+ *
+ * @return void
+ * @throws \DOMException
+ */
+ public function testTechnicalMetadataStructure(): void
+ {
+ $this->addChildNodeWithNamespace(VH::XPATH_ADMINISTRATIVE_METADATA, VH::NAMESPACE_METS, 'mets:techMD');
+ $this->hasErrorAttribute(VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_TECHNICAL_METADATA), 'ID');
+
+ $this->setAttributeValue(VH::XPATH_ADMINISTRATIVE_TECHNICAL_METADATA, 'ID', 'DMDLOG_0001');
+ $this->hasErrorUniqueId(VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_TECHNICAL_METADATA), 'DMDLOG_0001');
+
+ $this->setAttributeValue(VH::XPATH_ADMINISTRATIVE_TECHNICAL_METADATA, 'ID', 'TECH_0001');
+ $this->hasErrorOne('mets:mdWrap', VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_TECHNICAL_METADATA));
+
+ $this->addChildNodeWithNamespace(VH::XPATH_ADMINISTRATIVE_TECHNICAL_METADATA, VH::NAMESPACE_METS, 'mets:mdWrap');
+ $this->hasErrorAttribute(VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_TECHNICAL_METADATA) . '/mets:mdWrap', 'MDTYPE');
+
+ $this->setAttributeValue(VH::XPATH_ADMINISTRATIVE_TECHNICAL_METADATA . '/mets:mdWrap', 'MDTYPE', '');
+ $this->hasErrorAttribute(VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_TECHNICAL_METADATA) . '/mets:mdWrap', 'OTHERMDTYPE');
+
+ $this->setAttributeValue(VH::XPATH_ADMINISTRATIVE_TECHNICAL_METADATA . '/mets:mdWrap', 'OTHERMDTYPE', '');
+ $this->hasErrorOne('mets:xmlData', VH::trimDoubleSlash(VH::XPATH_ADMINISTRATIVE_TECHNICAL_METADATA) . '/mets:mdWrap');
+ }
+
+ protected function createValidator(): AbstractDlfValidator
+ {
+ return new AdministrativeMetadataValidator();
+ }
+}
diff --git a/Tests/Unit/Validation/Mets/DescriptiveMetadataValidatorTest.php b/Tests/Unit/Validation/Mets/DescriptiveMetadataValidatorTest.php
new file mode 100644
index 000000000..622983b0a
--- /dev/null
+++ b/Tests/Unit/Validation/Mets/DescriptiveMetadataValidatorTest.php
@@ -0,0 +1,76 @@
+
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ */
+
+use Kitodo\Dlf\Validation\AbstractDlfValidator;
+use Slub\Dfgviewer\Common\ValidationHelper as VH;
+use Slub\Dfgviewer\Validation\Mets\DescriptiveMetadataValidator;
+
+class DescriptiveMetadataValidatorTest extends AbstractDomDocumentValidatorTest
+{
+ /**
+ * Test validation against the rules of chapter "2.5.1 Metadatensektion – mets:dmdSec"
+ *
+ * @return void
+ */
+ public function testDescriptiveMetadata(): void
+ {
+ $this->removeNodes(VH::XPATH_DESCRIPTIVE_METADATA_SECTIONS);
+ $this->hasErrorAny(VH::XPATH_DESCRIPTIVE_METADATA_SECTIONS);
+ $this->resetDocument();
+
+ $this->removeNodes(VH::XPATH_LOGICAL_STRUCTURAL_ELEMENTS);
+ $this->hasErrorOne(VH::XPATH_LOGICAL_STRUCTURAL_ELEMENTS);
+ $this->resetDocument();
+
+ $this->setAttributeValue(VH::XPATH_LOGICAL_STRUCTURAL_ELEMENTS, 'DMDID', 'Test');
+ $this->hasErrorAttributeRefToOne('/mets:mets/mets:structMap[1]/mets:div', 'DMDID', 'Test', VH::XPATH_DESCRIPTIVE_METADATA_SECTIONS);
+ }
+
+ /**
+ * Test validation against the rules of chapter "2.5.2.1 Eingebettete Metadaten – mets:mdWrap"
+ *
+ * @return void
+ */
+ public function testEmbeddedMetadata(): void
+ {
+ $this->removeNodes(VH::XPATH_DESCRIPTIVE_METADATA_SECTIONS . '/mets:mdWrap');
+ $this->hasErrorOne('mets:mdWrap', VH::trimDoubleSlash(VH::XPATH_DESCRIPTIVE_METADATA_SECTIONS));
+ $this->resetDocument();
+
+ $this->setAttributeValue(VH::XPATH_DESCRIPTIVE_METADATA_SECTIONS . '/mets:mdWrap', 'MDTYPE', 'Test');
+ $this->hasErrorAttributeWithValue(VH::trimDoubleSlash(VH::XPATH_DESCRIPTIVE_METADATA_SECTIONS) . '/mets:mdWrap', 'MDTYPE', 'Test');
+ $this->resetDocument();
+
+ $this->removeNodes(VH::XPATH_DESCRIPTIVE_METADATA_SECTIONS . '/mets:mdWrap/mets:xmlData/mods:mods');
+ $this->hasErrorOne('mets:xmlData[mods:mods]', VH::trimDoubleSlash(VH::XPATH_DESCRIPTIVE_METADATA_SECTIONS) . '/mets:mdWrap');
+ }
+
+ protected function createValidator(): AbstractDlfValidator
+ {
+ return new DescriptiveMetadataValidator();
+ }
+}
diff --git a/Tests/Unit/Validation/Mets/DigitalRepresentationValidatorTest.php b/Tests/Unit/Validation/Mets/DigitalRepresentationValidatorTest.php
new file mode 100644
index 000000000..21198fc43
--- /dev/null
+++ b/Tests/Unit/Validation/Mets/DigitalRepresentationValidatorTest.php
@@ -0,0 +1,116 @@
+
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ */
+
+use Kitodo\Dlf\Validation\AbstractDlfValidator;
+use Slub\Dfgviewer\Common\ValidationHelper as VH;
+use Slub\Dfgviewer\Validation\Mets\DigitalRepresentationValidator;
+
+class DigitalRepresentationValidatorTest extends AbstractDomDocumentValidatorTest
+{
+ /**
+ * Test validation against the rules of chapter "2.4.1 Dateisektion – mets:fileSec"
+ *
+ * @return void
+ * @throws \DOMException
+ */
+ public function testFileSections(): void
+ {
+ $this->addChildNodeWithNamespace('/mets:mets', VH::NAMESPACE_METS, 'mets:fileSec');
+ $this->hasErrorNoneOrOne(VH::XPATH_FILE_SECTIONS);
+ $this->resetDocument();
+
+ $this->removeNodes(VH::XPATH_FILE_SECTIONS);
+ $this->hasErrorOne(VH::XPATH_FILE_SECTIONS);
+ $this->resetDocument();
+
+ $this->removeNodes(VH::XPATH_PHYSICAL_STRUCTURES);
+ $this->removeNodes(VH::XPATH_FILE_SECTIONS);
+ $this->hasNoError();
+ }
+
+ /**
+ * Test validation against the rules of chapter "2.4.2.1 Dateigruppen – mets:fileGrp"
+ *
+ * @return void
+ */
+ public function testFileGroups(): void
+ {
+ $this->removeNodes(VH::XPATH_FILE_SECTION_GROUPS);
+ $this->hasErrorAny(VH::XPATH_FILE_SECTION_GROUPS);
+ $this->resetDocument();
+
+ $this->removeNodes(VH::XPATH_FILE_SECTION_GROUPS . '[@USE="DEFAULT"]');
+ $this->hasErrorOne(VH::XPATH_FILE_SECTION_GROUPS . '[@USE="DEFAULT"]');
+ $this->resetDocument();
+
+ $this->setAttributeValue(VH::XPATH_FILE_SECTION_GROUPS . '[@USE="THUMBS"]', 'USE', 'DEFAULT');
+ $this->hasErrorUniqueAttribute(VH::trimDoubleSlash(VH::XPATH_FILE_SECTION_GROUPS) . '[1]', 'USE', 'DEFAULT');
+ }
+
+ /**
+ * Test validation against the rules of chapter "2.4.2.2 Datei – mets:fileGrp/mets:file" and "2.4.2.3 Dateilink – mets:fileGrp/mets:file/mets:FLocat"
+ *
+ * @return void
+ */
+ public function testFiles(): void
+ {
+ $this->removeNodes(VH::XPATH_FILE_SECTION_FILES);
+ $this->hasErrorAny(VH::XPATH_FILE_SECTION_FILES);
+ $this->resetDocument();
+
+ $this->setAttributeValue(VH::XPATH_FILE_SECTION_FILES, 'ID', 'DMDLOG_0001');
+ $this->hasErrorUniqueId(VH::trimDoubleSlash(VH::XPATH_FILE_SECTION_GROUPS) . '[1]/mets:file', 'DMDLOG_0001');
+ $this->resetDocument();
+
+ $this->removeAttribute(VH::XPATH_FILE_SECTION_FILES, 'MIMETYPE');
+ $this->hasErrorAttribute(VH::trimDoubleSlash(VH::XPATH_FILE_SECTION_GROUPS) . '[1]/mets:file', 'MIMETYPE');
+ $this->resetDocument();
+
+ $this->removeNodes(VH::XPATH_FILE_SECTION_FILES . '/mets:FLocat');
+ $this->hasErrorOne('mets:FLocat', VH::trimDoubleSlash(VH::XPATH_FILE_SECTION_GROUPS) . '[1]/mets:file');
+ $this->resetDocument();
+
+ $this->removeAttribute(VH::XPATH_FILE_SECTION_FILES . '/mets:FLocat', 'LOCTYPE');
+ $this->hasErrorAttribute(VH::trimDoubleSlash(VH::XPATH_FILE_SECTION_GROUPS) . '[1]/mets:file/mets:FLocat', 'LOCTYPE');
+ $this->resetDocument();
+
+ $this->setAttributeValue(VH::XPATH_FILE_SECTION_FILES . '/mets:FLocat', 'LOCTYPE', 'Test');
+ $this->hasErrorAttributeWithValue(VH::trimDoubleSlash(VH::XPATH_FILE_SECTION_GROUPS) . '[1]/mets:file/mets:FLocat', 'LOCTYPE', 'Test');
+ $this->resetDocument();
+
+ $this->removeAttribute(VH::XPATH_FILE_SECTION_FILES . '/mets:FLocat', 'xlink:href');
+ $this->hasErrorAttribute(VH::trimDoubleSlash(VH::XPATH_FILE_SECTION_GROUPS) . '[1]/mets:file/mets:FLocat', 'xlink:href');
+
+ $this->setAttributeValue(VH::XPATH_FILE_SECTION_FILES . '/mets:FLocat', 'xlink:href', 'Test');
+ $this->hasErrorAttributeWithUrl(VH::trimDoubleSlash(VH::XPATH_FILE_SECTION_GROUPS) . '[1]/mets:file/mets:FLocat', 'xlink:href', 'Test');
+ }
+
+ protected function createValidator(): AbstractDlfValidator
+ {
+ return new DigitalRepresentationValidator();
+ }
+}
diff --git a/Tests/Unit/Validation/Mets/LinkingLogicalPhysicalStructureValidatorTest.php b/Tests/Unit/Validation/Mets/LinkingLogicalPhysicalStructureValidatorTest.php
new file mode 100644
index 000000000..22e4bd273
--- /dev/null
+++ b/Tests/Unit/Validation/Mets/LinkingLogicalPhysicalStructureValidatorTest.php
@@ -0,0 +1,64 @@
+
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ */
+
+use Kitodo\Dlf\Validation\AbstractDlfValidator;
+use Slub\Dfgviewer\Common\ValidationHelper as VH;
+use Slub\Dfgviewer\Validation\Mets\LinkingLogicalPhysicalStructureValidator;
+
+class LinkingLogicalPhysicalStructureValidatorTest extends AbstractDomDocumentValidatorTest
+{
+ /**
+ * Test validation against the rules of chapter "2.3.1 Structure links - mets:structLink"
+ *
+ * @return void
+ * @throws \DOMException
+ */
+ public function testMultipleStructLinks(): void
+ {
+ $this->addChildNodeWithNamespace('/mets:mets', VH::NAMESPACE_METS, 'mets:structLink');
+ $this->hasErrorNoneOrOne(VH::XPATH_STRUCT_LINK);
+ }
+
+ public function testLinkElements(): void
+ {
+ $this->removeNodes(VH::XPATH_STRUCT_LINK_ELEMENTS);
+ $this->hasErrorAny(VH::XPATH_STRUCT_LINK_ELEMENTS);
+ $this->resetDocument();
+
+ $this->setAttributeValue(VH::XPATH_STRUCT_LINK_ELEMENTS, 'xlink:from', 'Test');
+ $this->hasErrorAttributeRefToOne(VH::trimDoubleSlash(VH::XPATH_STRUCT_LINK_ELEMENTS), 'xlink:from', 'Test', VH::XPATH_LOGICAL_STRUCTURES);
+ $this->resetDocument();
+
+ $this->setAttributeValue(VH::XPATH_STRUCT_LINK_ELEMENTS, 'xlink:to', 'Test');
+ $this->hasErrorAttributeRefToOne(VH::trimDoubleSlash(VH::XPATH_STRUCT_LINK_ELEMENTS), 'xlink:to', 'Test', VH::XPATH_PHYSICAL_STRUCTURES);
+ }
+
+ protected function createValidator(): AbstractDlfValidator
+ {
+ return new LinkingLogicalPhysicalStructureValidator();
+ }
+}
diff --git a/Tests/Unit/Validation/Mets/LogicalStructureValidatorTest.php b/Tests/Unit/Validation/Mets/LogicalStructureValidatorTest.php
new file mode 100644
index 000000000..0ba64ce23
--- /dev/null
+++ b/Tests/Unit/Validation/Mets/LogicalStructureValidatorTest.php
@@ -0,0 +1,105 @@
+
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ */
+
+use Kitodo\Dlf\Validation\AbstractDlfValidator;
+use Slub\Dfgviewer\Common\ValidationHelper as VH;
+use Slub\Dfgviewer\Validation\Mets\LogicalStructureValidator;
+
+class LogicalStructureValidatorTest extends AbstractDomDocumentValidatorTest
+{
+ /**
+ * Test validation against the rules of chapter "2.1.1 Logical structure - mets:structMap"
+ *
+ * @return void
+ */
+ public function testNotExistingLogicalStructureElement(): void
+ {
+ $this->removeNodes(VH::XPATH_LOGICAL_STRUCTURES);
+ $this->hasErrorAny(VH::XPATH_LOGICAL_STRUCTURES);
+ }
+
+ /**
+ * Test validation against the rules of chapter "2.1.2.1 Structural element - mets:div"
+ * @return void
+ */
+ public function testStructuralElements(): void
+ {
+ $this->removeNodes(VH::XPATH_LOGICAL_STRUCTURAL_ELEMENTS);
+ $this->hasErrorAny(VH::XPATH_LOGICAL_STRUCTURAL_ELEMENTS);
+ $this->resetDocument();
+
+ $this->removeAttribute(VH::XPATH_LOGICAL_STRUCTURAL_ELEMENTS, 'ID');
+ $this->hasErrorAttribute('/mets:mets/mets:structMap[1]/mets:div', 'ID');
+ $this->resetDocument();
+
+ $node = $this->doc->createElementNS(VH::NAMESPACE_METS, 'mets:div');
+ $node->setAttribute('ID', 'LOG_0001');
+ $this->addChildNode(VH::XPATH_LOGICAL_STRUCTURAL_ELEMENTS, $node);
+ $this->hasErrorUniqueId('/mets:mets/mets:structMap[1]/mets:div', 'LOG_0001');
+ $this->resetDocument();
+
+ $this->removeAttribute(VH::XPATH_LOGICAL_STRUCTURAL_ELEMENTS, 'TYPE');
+ $this->hasErrorAttribute('/mets:mets/mets:structMap[1]/mets:div', 'TYPE');
+
+ $this->setAttributeValue(VH::XPATH_LOGICAL_STRUCTURAL_ELEMENTS, 'TYPE', 'Test');
+ $this->hasErrorAttributeWithValue('/mets:mets/mets:structMap[1]/mets:div', 'TYPE', 'Test');
+ }
+
+ /**
+ * Test validation against the rules of chapter "2.1.2.2 Reference to external METS-files - mets:div / mets:mptr"
+ * @return void
+ * @throws \DOMException
+ */
+ public function testExternalReference(): void
+ {
+ $this->addChildNodeWithNamespace(VH::XPATH_LOGICAL_STRUCTURAL_ELEMENTS, VH::NAMESPACE_METS, 'mets:mptr');
+ $this->addChildNodeWithNamespace(VH::XPATH_LOGICAL_STRUCTURAL_ELEMENTS, VH::NAMESPACE_METS, 'mets:mptr');
+ $this->hasErrorNoneOrOne(VH::XPATH_LOGICAL_EXTERNAL_REFERENCES);
+ $this->resetDocument();
+
+ $this->addChildNodeWithNamespace(VH::XPATH_LOGICAL_STRUCTURAL_ELEMENTS, VH::NAMESPACE_METS, 'mets:mptr');
+ $this->hasErrorAttribute('/mets:mets/mets:structMap[1]/mets:div/mets:mptr', 'LOCTYPE');
+
+ $this->setAttributeValue(VH::XPATH_LOGICAL_EXTERNAL_REFERENCES, 'LOCTYPE', 'Test');
+ $this->hasErrorAttributeWithValue('/mets:mets/mets:structMap[1]/mets:div/mets:mptr', 'LOCTYPE', 'Test');
+
+ $this->setAttributeValue(VH::XPATH_LOGICAL_EXTERNAL_REFERENCES, 'LOCTYPE', 'URL');
+ $this->hasErrorAttribute('/mets:mets/mets:structMap[1]/mets:div/mets:mptr', 'xlink:href');
+
+ $this->setAttributeValue(VH::XPATH_LOGICAL_EXTERNAL_REFERENCES, 'xlink:href', 'Test');
+ $this->hasErrorAttributeWithUrl('/mets:mets/mets:structMap[1]/mets:div/mets:mptr', 'xlink:href', 'Test');
+
+ $this->setAttributeValue(VH::XPATH_LOGICAL_EXTERNAL_REFERENCES, 'xlink:href', 'http://example.com/periodical.xml');
+ $result = $this->validate();
+ self::assertFalse($result->hasErrors());
+ }
+
+ protected function createValidator(): AbstractDlfValidator
+ {
+ return new LogicalStructureValidator();
+ }
+}
diff --git a/Tests/Unit/Validation/Mets/PhysicalStructureValidatorTest.php b/Tests/Unit/Validation/Mets/PhysicalStructureValidatorTest.php
new file mode 100644
index 000000000..70878537a
--- /dev/null
+++ b/Tests/Unit/Validation/Mets/PhysicalStructureValidatorTest.php
@@ -0,0 +1,92 @@
+
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ */
+
+use Kitodo\Dlf\Validation\AbstractDlfValidator;
+use Slub\Dfgviewer\Common\ValidationHelper as VH;
+use Slub\Dfgviewer\Validation\Mets\PhysicalStructureValidator;
+
+class PhysicalStructureValidatorTest extends AbstractDomDocumentValidatorTest
+{
+ /**
+ * Test validation against the rules of chapter "2.2.1 Physical structure - mets:structMap"
+ *
+ * @return void
+ * @throws \DOMException
+ */
+ public function testMultiplePhysicalDivisions(): void
+ {
+ $node = $this->doc->createElementNS(VH::NAMESPACE_METS, 'mets:structMap');
+ $node->setAttribute('TYPE', 'PHYSICAL');
+ $this->addChildNode('/mets:mets', $node);
+ $this->hasErrorNoneOrOne(VH::XPATH_PHYSICAL_STRUCTURES);
+ }
+
+ /**
+ * Test validation against the rules of chapter "2.2.2.1 Structural element - mets:div"
+ *
+ * @return void
+ * @throws \DOMException
+ */
+ public function testStructuralElements(): void
+ {
+ $this->removeNodes(VH::XPATH_PHYSICAL_STRUCTURAL_ELEMENT_SEQUENCE);
+ $this->hasErrorOne(VH::XPATH_PHYSICAL_STRUCTURAL_ELEMENT_SEQUENCE);
+ $this->resetDocument();
+
+ $this->removeAttribute(VH::XPATH_PHYSICAL_STRUCTURAL_ELEMENT_SEQUENCE, 'TYPE');
+ $this->hasErrorAttribute('/mets:mets/mets:structMap[2]/mets:div', 'TYPE');
+
+ $this->setAttributeValue(VH::XPATH_PHYSICAL_STRUCTURAL_ELEMENT_SEQUENCE, 'TYPE', 'Test');
+ $this->hasErrorAttributeWithValue('/mets:mets/mets:structMap[2]/mets:div', 'TYPE', 'Test');
+ $this->resetDocument();
+
+ $this->removeNodes(VH::XPATH_PHYSICAL_STRUCTURAL_ELEMENTS);
+ $this->hasErrorAny(VH::XPATH_PHYSICAL_STRUCTURAL_ELEMENTS);
+ $this->resetDocument();
+
+ $this->removeAttribute(VH::XPATH_PHYSICAL_STRUCTURAL_ELEMENTS, 'ID');
+ $this->hasErrorAttribute('/mets:mets/mets:structMap[2]/mets:div/mets:div', 'ID');
+ $this->resetDocument();
+
+ $node = $this->doc->createElementNS(VH::NAMESPACE_METS, 'mets:div');
+ $node->setAttribute('ID', 'PHYS_0001');
+ $this->addChildNode('//mets:structMap[@TYPE="PHYSICAL"]/mets:div', $node);
+ $this->hasErrorUniqueId('/mets:mets/mets:structMap[2]/mets:div/mets:div[1]', 'PHYS_0001');
+ $this->resetDocument();
+
+ $this->removeAttribute(VH::XPATH_PHYSICAL_STRUCTURAL_ELEMENTS, 'TYPE');
+ $this->hasErrorAttribute('/mets:mets/mets:structMap[2]/mets:div/mets:div', 'TYPE');
+
+ $this->setAttributeValue(VH::XPATH_PHYSICAL_STRUCTURAL_ELEMENTS, 'TYPE', 'Test');
+ $this->hasErrorAttributeWithValue('/mets:mets/mets:structMap[2]/mets:div/mets:div', 'TYPE', 'Test');
+ }
+
+ protected function createValidator(): AbstractDlfValidator
+ {
+ return new PhysicalStructureValidator();
+ }
+}
diff --git a/composer.json b/composer.json
index a4b398d80..d1094f82c 100644
--- a/composer.json
+++ b/composer.json
@@ -48,7 +48,10 @@
"slub/slub-digitalcollections": "^4.0|dev-master"
},
"require-dev": {
- "phpstan/phpstan": "^1.12"
+ "phpstan/phpstan": "^1.12",
+ "phpunit/phpunit": "^9.6.22",
+ "spatie/phpunit-watcher": "^1.23.6",
+ "typo3/testing-framework": "^7.1.1"
},
"replace": {
"typo3-ter/dfgviewer": "self.version"
@@ -82,6 +85,12 @@
],
"phpstan": [
"@php vendor/bin/phpstan --configuration=\".github/phpstan.neon\""
+ ],
+ "test:unit:local": [
+ "phpunit -c Build/Test/UnitTests.xml"
+ ],
+ "test:unit:watch": [
+ "phpunit-watcher watch -c Build/Test/UnitTests.xml"
]
},
"config": {