diff --git a/Build/Test/docker-compose.yml b/Build/Test/docker-compose.yml
index 30719293a..908ff2af3 100644
--- a/Build/Test/docker-compose.yml
+++ b/Build/Test/docker-compose.yml
@@ -1,6 +1,5 @@
# Adopted/reduced from https://github.com/TYPO3/typo3/blob/608f238a8b7696a49a47e1e73ce8e2845455f0f5/Build/testing-docker/local/docker-compose.yml
-version: "2.3"
services:
mysql:
image: docker.io/mysql:${MYSQL_VERSION}
diff --git a/Classes/Middleware/DOMDocumentValidation.php b/Classes/Middleware/DOMDocumentValidation.php
new file mode 100644
index 000000000..90d4b6144
--- /dev/null
+++ b/Classes/Middleware/DOMDocumentValidation.php
@@ -0,0 +1,116 @@
+
+ *
+ * This file is part of the Kitodo and TYPO3 projects.
+ *
+ * @license GNU General Public License version 3 or later.
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ */
+
+use DOMDocument;
+use InvalidArgumentException;
+use Kitodo\Dlf\Validation\DOMDocumentValidationStack;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use Psr\Log\LoggerAwareTrait;
+use TYPO3\CMS\Core\Http\ResponseFactory;
+use TYPO3\CMS\Core\Log\LogManager;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
+use TYPO3\CMS\Extbase\Error\Result;
+
+/**
+ * Middleware for validation of DOMDocuments.
+ *
+ * @package TYPO3
+ * @subpackage dlf
+ * @access public
+ */
+class DOMDocumentValidation implements MiddlewareInterface
+{
+ use LoggerAwareTrait;
+
+ /**
+ * The main method of the middleware.
+ *
+ * @access public
+ *
+ * @param ServerRequestInterface $request for processing
+ * @param RequestHandlerInterface $handler for processing
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return ResponseInterface
+ */
+ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+ {
+ $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(static::class);
+ $response = $handler->handle($request);
+ // parameters are sent by POST --> use getParsedBody() instead of getQueryParams()
+ $parameters = $request->getQueryParams();
+
+ // Return if not this middleware
+ if (!isset($parameters['middleware']) || ($parameters['middleware'] != 'dlf/domDocumentValidation')) {
+ return $response;
+ }
+
+ $urlParam = $parameters['url'];
+ if (!isset($urlParam)) {
+ throw new InvalidArgumentException('URL parameter is missing.', 1724334674);
+ }
+
+ /** @var ConfigurationManagerInterface $configurationManager */
+ $configurationManager = GeneralUtility::makeInstance(ConfigurationManagerInterface::class);
+ $settings = $configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS);
+
+ if (!array_key_exists("domDocumentValidationValidators", $settings)) {
+ $this->logger->error('DOMDocumentValidation is not configured correctly.');
+ throw new InvalidArgumentException('DOMDocumentValidation is not configured correctly.', 1724335601);
+ }
+
+ $validation = GeneralUtility::makeInstance(DOMDocumentValidationStack::class, $settings['domDocumentValidationValidators']);
+
+ if (!GeneralUtility::isValidUrl($urlParam)) {
+ $this->logger->debug('Parameter "' . $urlParam . '" is not a valid url.');
+ throw new InvalidArgumentException('Value of url parameter is not a valid url.', 1724852611);
+ }
+
+ $content = GeneralUtility::getUrl($urlParam);
+ if ($content === false) {
+ $this->logger->debug('Error while loading content of "' . $urlParam . '"');
+ throw new InvalidArgumentException('Error while loading content of url.', 1724420640);
+ }
+
+ $document = new DOMDocument();
+ if ($document->loadXML($content) === false) {
+ $this->logger->debug('Error converting content of "' . $urlParam . '" to xml.');
+ throw new InvalidArgumentException('Error converting content to xml.', 1724420648);
+ }
+
+ $result = $validation->validate($document);
+ return $this->getJsonResponse($result);
+ }
+
+ protected function getJsonResponse(Result $result): ResponseInterface
+ {
+ $resultContent = ["valid" => !$result->hasErrors()];
+
+ foreach ($result->getErrors() as $error) {
+ $resultContent["results"][$error->getTitle()][] = $error->getMessage();
+ }
+
+ /** @var ResponseFactory $responseFactory */
+ $responseFactory = GeneralUtility::makeInstance(ResponseFactory::class);
+ $response = $responseFactory->createResponse()
+ ->withHeader('Content-Type', 'application/json; charset=utf-8');
+ $response->getBody()->write(json_encode($resultContent));
+ return $response;
+ }
+}
diff --git a/Classes/Validation/AbstractDlfValidationStack.php b/Classes/Validation/AbstractDlfValidationStack.php
new file mode 100644
index 000000000..36ca9c964
--- /dev/null
+++ b/Classes/Validation/AbstractDlfValidationStack.php
@@ -0,0 +1,129 @@
+
+ *
+ * This file is part of the Kitodo and TYPO3 projects.
+ *
+ * @license GNU General Public License version 3 or later.
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ */
+
+namespace Kitodo\Dlf\Validation;
+
+use InvalidArgumentException;
+use Psr\Log\LoggerAwareTrait;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Abstract class provides functions for implementing a validation stack.
+ *
+ * @package TYPO3
+ * @subpackage dlf
+ *
+ * @access public
+ */
+abstract class AbstractDlfValidationStack extends AbstractDlfValidator
+{
+ use LoggerAwareTrait;
+
+ const ITEM_KEY_TITLE = "title";
+ const ITEM_KEY_BREAK_ON_ERROR = "breakOnError";
+ const ITEM_KEY_VALIDATOR = "validator";
+
+ protected array $validatorStack = [];
+
+ public function __construct(string $valueClassName)
+ {
+ parent::__construct($valueClassName);
+ }
+
+ /**
+ * Add validators by validation stack configuration to the internal validator stack.
+ *
+ * @param array $configuration The configuration of validators
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return void
+ */
+ public function addValidators(array $configuration): void
+ {
+ foreach ($configuration as $configurationItem) {
+ if (!class_exists($configurationItem["className"])) {
+ $this->logger->error('Unable to load class ' . $configurationItem["className"] . '.');
+ throw new InvalidArgumentException('Unable to load validator class.', 1723200537037);
+ }
+ $breakOnError = !isset($configurationItem["breakOnError"]) || $configurationItem["breakOnError"] !== "false";
+ $this->addValidator($configurationItem["className"], $configurationItem["title"] ?? "", $breakOnError, $configurationItem["configuration"] ?? []);
+ }
+ }
+
+ /**
+ * Add validator to the internal validator stack.
+ *
+ * @param string $className Class name of the validator which was derived from Kitodo\Dlf\Validation\AbstractDlfValidator
+ * @param string $title The title of the validator
+ * @param bool $breakOnError True if the execution of validator stack is interrupted when validator throws an error
+ * @param array|null $configuration The configuration of validator
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return void
+ */
+ protected function addValidator(string $className, string $title, bool $breakOnError = true, array $configuration = null): void
+ {
+ if ($configuration === null) {
+ $validator = GeneralUtility::makeInstance($className);
+ } else {
+ $validator = GeneralUtility::makeInstance($className, $configuration);
+ }
+
+ if (!$validator instanceof AbstractDlfValidator) {
+ $this->logger->error($className . ' must be an instance of AbstractDlfValidator.');
+ throw new InvalidArgumentException('Class must be an instance of AbstractDlfValidator.', 1723121212747);
+ }
+
+ $title = empty($title) ? $className : $title;
+
+ $this->validatorStack[] = [self::ITEM_KEY_TITLE => $title, self::ITEM_KEY_VALIDATOR => $validator, self::ITEM_KEY_BREAK_ON_ERROR => $breakOnError];
+ }
+
+ /**
+ * Check if value is valid across all validation classes of validation stack.
+ *
+ * @param $value The value of defined class name.
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return void
+ */
+ protected function isValid($value): void
+ {
+ if (!$value instanceof $this->valueClassName) {
+ $this->logger->error('Value must be an instance of ' . $this->valueClassName . '.');
+ throw new InvalidArgumentException('Type of value is not valid.', 1723127564821);
+ }
+
+ if (empty($this->validatorStack)) {
+ $this->logger->error('The validation stack has no validator.');
+ throw new InvalidArgumentException('The validation stack has no validator.', 1724662426);
+ }
+
+ foreach ($this->validatorStack as $validationStackItem) {
+ $validator = $validationStackItem[self::ITEM_KEY_VALIDATOR];
+ $result = $validator->validate($value);
+
+ foreach ($result->getErrors() as $error) {
+ $this->addError($error->getMessage(), $error->getCode(), [], $validationStackItem[self::ITEM_KEY_TITLE]);
+ }
+
+ if ($validationStackItem[self::ITEM_KEY_BREAK_ON_ERROR] && $result->hasErrors()) {
+ break;
+ }
+ }
+ }
+}
diff --git a/Classes/Validation/AbstractDlfValidator.php b/Classes/Validation/AbstractDlfValidator.php
new file mode 100644
index 000000000..4dbb39761
--- /dev/null
+++ b/Classes/Validation/AbstractDlfValidator.php
@@ -0,0 +1,56 @@
+
+ *
+ * This file is part of the Kitodo and TYPO3 projects.
+ *
+ * @license GNU General Public License version 3 or later.
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ */
+
+namespace Kitodo\Dlf\Validation;
+
+use InvalidArgumentException;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerAwareTrait;
+use TYPO3\CMS\Core\Log\LogManager;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator;
+
+/**
+ * Base Validator provides functionalities for using the derived validator within a validation stack.
+ *
+ * @package TYPO3
+ * @subpackage dlf
+ *
+ * @access public
+ */
+abstract class AbstractDlfValidator extends AbstractValidator
+{
+ use LoggerAwareTrait;
+
+ protected string $valueClassName;
+
+ /**
+ * @param $valueClassName string The class name of the value
+ */
+ public function __construct(string $valueClassName)
+ {
+ parent::__construct();
+ $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(static::class);
+ $this->valueClassName = $valueClassName;
+ }
+
+ public function validate($value)
+ {
+ if (!$value instanceof $this->valueClassName) {
+ $this->logger->debug('Value must be an instance of ' . $this->valueClassName . '.');
+ throw new InvalidArgumentException('Type of value is not valid.', 1723126505626);
+ }
+ return parent::validate($value);
+ }
+}
diff --git a/Classes/Validation/DOMDocumentValidationStack.php b/Classes/Validation/DOMDocumentValidationStack.php
new file mode 100644
index 000000000..c173d786e
--- /dev/null
+++ b/Classes/Validation/DOMDocumentValidationStack.php
@@ -0,0 +1,32 @@
+
+ *
+ * This file is part of the Kitodo and TYPO3 projects.
+ *
+ * @license GNU General Public License version 3 or later.
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ */
+
+namespace Kitodo\Dlf\Validation;
+
+/**
+ * Implementation of AbstractDlfValidationStack for validating DOMDocument against the configured validators.
+ *
+ * @package TYPO3
+ * @subpackage dlf
+ *
+ * @access public
+ */
+class DOMDocumentValidationStack extends AbstractDlfValidationStack
+{
+ public function __construct(array $configuration)
+ {
+ parent::__construct(\DOMDocument::class);
+ $this->addValidators($configuration);
+ }
+}
diff --git a/Classes/Validation/LibXmlTrait.php b/Classes/Validation/LibXmlTrait.php
new file mode 100644
index 000000000..ea7f73ddc
--- /dev/null
+++ b/Classes/Validation/LibXmlTrait.php
@@ -0,0 +1,44 @@
+
+ *
+ * This file is part of the Kitodo and TYPO3 projects.
+ *
+ * @license GNU General Public License version 3 or later.
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ */
+
+trait LibXmlTrait
+{
+ /**
+ * Add the errors from the libxml error buffer as validation error.
+ *
+ * To enable user error handling, you need to use libxml_use_internal_errors(true) beforehand.
+ *
+ * @return void
+ */
+ public function addErrorsOfBuffer(): void
+ {
+ $errors = libxml_get_errors();
+ foreach ($errors as $error) {
+ $this->addError($error->message, $error->code);
+ }
+ libxml_clear_errors();
+ }
+
+ public function enableErrorBuffer(): void
+ {
+ libxml_use_internal_errors(true);
+ }
+
+ public function disableErrorBuffer(): void
+ {
+ libxml_use_internal_errors(false);
+ }
+}
diff --git a/Classes/Validation/SaxonXslToSvrlValidator.php b/Classes/Validation/SaxonXslToSvrlValidator.php
new file mode 100644
index 000000000..4971df1c8
--- /dev/null
+++ b/Classes/Validation/SaxonXslToSvrlValidator.php
@@ -0,0 +1,92 @@
+
+ *
+ * This file is part of the Kitodo and TYPO3 projects.
+ *
+ * @license GNU General Public License version 3 or later.
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ */
+
+namespace Kitodo\Dlf\Validation;
+
+use DOMDocument;
+use Exception;
+use InvalidArgumentException;
+use Psr\Log\LoggerAwareInterface;
+use SimpleXMLElement;
+use Symfony\Component\Process\Exception\ProcessFailedException;
+use Symfony\Component\Process\Process;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * The validator validates the DOMDocument against an XSL Schematron and converts error output to validation errors.
+ *
+ * @package TYPO3
+ * @subpackage dlf
+ *
+ * @access public
+ */
+class SaxonXslToSvrlValidator extends AbstractDlfValidator implements LoggerAwareInterface
+{
+ private string $jar;
+
+ private string $xsl;
+
+ public function __construct(array $configuration)
+ {
+ parent::__construct(DOMDocument::class);
+ $this->jar = GeneralUtility::getFileAbsFileName($configuration["jar"] ?? '');
+ $this->xsl = GeneralUtility::getFileAbsFileName($configuration["xsl"] ?? '');
+ if (empty($this->jar)) {
+ $this->logger->error('Saxon JAR file not found.');
+ throw new InvalidArgumentException('Saxon JAR file not found.', 1723121212747);
+ }
+ if (empty($this->xsl)) {
+ $this->logger->error('XSL Schematron file not found.');
+ throw new InvalidArgumentException('XSL Schematron file not found.', 1723121212747);
+ }
+ }
+
+ protected function isValid($value)
+ {
+ $svrl = $this->process($value);
+ $this->addErrorsOfSvrl($svrl);
+ }
+
+ protected function process(mixed $value): string
+ {
+ // using source from standard input
+ $process = new Process(['java', '-jar', $this->jar, '-xsl:' . $this->xsl, '-s:-'], null, null, $value->saveXML());
+ $process->run();
+ // executes after the command finish
+ if (!$process->isSuccessful()) {
+ $this->logger->error('Processing exits with code "' . $process->getExitCode() . '"');
+ throw new InvalidArgumentException('Processing was not successful.', 1724862680);
+ }
+ return $process->getOutput();
+ }
+
+ /**
+ * Add errors of schematron output.
+ *
+ * @throws InvalidArgumentException
+ */
+ private function addErrorsOfSvrl(string $svrl): void
+ {
+ try {
+ $xml = new SimpleXMLElement($svrl);
+ $results = $xml->xpath("/svrl:schematron-output/svrl:failed-assert/svrl:text");
+
+ foreach ($results as $error) {
+ $this->addError($error->__toString(), 1724405095);
+ }
+ } catch (Exception $e) {
+ throw new InvalidArgumentException('Schematron output XML could not be parsed.', 1724754882);
+ }
+ }
+}
diff --git a/Classes/Validation/XmlSchemesValidator.php b/Classes/Validation/XmlSchemesValidator.php
new file mode 100644
index 000000000..216ae505f
--- /dev/null
+++ b/Classes/Validation/XmlSchemesValidator.php
@@ -0,0 +1,63 @@
+
+ *
+ * This file is part of the Kitodo and TYPO3 projects.
+ *
+ * @license GNU General Public License version 3 or later.
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ */
+
+namespace Kitodo\Dlf\Validation;
+
+use DOMDocument;
+
+/**
+ * The validator combines the configured schemes into one schema and validates the provided DOMDocument against this.
+ *
+ * @package TYPO3
+ * @subpackage dlf
+ *
+ * @access public
+ */
+class XmlSchemesValidator extends AbstractDlfValidator
+{
+ use LibXmlTrait;
+
+ private array $schemes;
+
+ public function __construct(array $configuration)
+ {
+ parent::__construct(\DOMDocument::class);
+ $this->schemes = $configuration;
+ }
+
+ /**
+ * Combines the schemes to one schema and validates the DOMDocument against this.
+ *
+ * @param $value DOMDocument The value to validate
+ * @return bool True if is valid
+ */
+ protected function isSchemeValid(DOMDocument $value): bool
+ {
+ $xsd = '';
+ foreach ($this->schemes as $scheme) {
+ $xsd .= '';
+ }
+ $xsd .= '';
+ return $value->schemaValidateSource($xsd);
+ }
+
+ protected function isValid($value): void
+ {
+ $this->enableErrorBuffer();
+ if (!$this->isSchemeValid($value)) {
+ $this->addErrorsOfBuffer();
+ }
+ $this->disableErrorBuffer();
+ }
+}
diff --git a/Configuration/RequestMiddlewares.php b/Configuration/RequestMiddlewares.php
index 748fba273..f328daa0f 100644
--- a/Configuration/RequestMiddlewares.php
+++ b/Configuration/RequestMiddlewares.php
@@ -29,6 +29,12 @@
'after' => [
'typo3/cms-frontend/prepare-tsfe-rendering'
]
+ ],
+ 'dlf/domDocumentValidation' => [
+ 'target' => \Kitodo\Dlf\Middleware\DOMDocumentValidation::class,
+ 'after' => [
+ 'typo3/cms-frontend/prepare-tsfe-rendering'
+ ]
]
],
];
diff --git a/Documentation/Developers/Index.rst b/Documentation/Developers/Index.rst
index 6b20688c7..3639619a4 100644
--- a/Documentation/Developers/Index.rst
+++ b/Documentation/Developers/Index.rst
@@ -8,4 +8,5 @@ These pages are aimed at developers working on Kitodo.Presentation.
Metadata
Database
+ Validation
Embedded3DViewer
diff --git a/Documentation/Developers/Validation.rst b/Documentation/Developers/Validation.rst
new file mode 100644
index 000000000..f1b386708
--- /dev/null
+++ b/Documentation/Developers/Validation.rst
@@ -0,0 +1,132 @@
+===============
+Validation
+===============
+
+.. contents::
+ :local:
+ :depth: 2
+
+Validators
+=======
+
+DOMDocumentValidationStack
+--------------------------
+
+``Kitodo\Dlf\Validation\DOMDocumentValidationStack`` implementation of ``Kitodo\Dlf\Validation\AbstractDlfValidationStack`` for validating DOMDocument against the configurable validators.
+
+The configuration is an array validator configurations each with following entries:
+
+.. t3-field-list-table::
+ :header-rows: 1
+
+ - :field: Key
+ :description: Description
+
+ - :field: title
+ :description: Title of the validator
+
+ - :field: className
+ :description: Fully qualified class name of validator class derived from ``Kitodo\Dlf\Validation\AbstractDlfValidator``
+
+ - :field: breakOnError
+ :description: Indicates whether the validation of the validation stack should be interrupted in case of errors.
+
+ - :field: configuration
+ :description: Specific configuration of validator
+
+
+XmlSchemesValidator
+--------------------------
+
+``Kitodo\Dlf\Validation\XmlSchemesValidator`` combines the configured schemes into one schema and validates the provided DOMDocument against this.
+
+The configuration is an array validator configurations each with following entries:
+
+.. t3-field-list-table::
+ :header-rows: 1
+
+ - :field: Key
+ :description: Description
+
+ - :field: namespace
+ :description: Specifies the URI of the namespace to import
+
+ - :field: schemaLocation
+ :description: Specifies the URI to the schema for the imported namespace
+
+
+SaxonXslToSvrlValidator
+--------------------------
+
+``Kitodo\Dlf\Validation\SaxonXslToSvrlValidator`` validates the DOMDocument against an XSL Schematron and converts error output to validation errors.
+
+To use the validator, the XSL Schematron must be available alongside the XSL processor as a JAR file, and the required Java version of the processor must be installed.
+
+.. t3-field-list-table::
+ :header-rows: 1
+
+ - :field: Key
+ :description: Description
+
+ - :field: jar
+ :description: Absolute path to the Saxon JAR file
+
+ - :field: xsl
+ :description: Absolute path to the XSL Schematron
+
+DOMDocumentValidation Middleware
+=======
+
+``Kitodo\Dlf\Validation\DOMDocumentValidation`` middleware can be used via the parameter ``middleware`` with the value ``dlf/domDocumentValidation`` and the parameter ``url`` with the URL to the ``DOMDocument`` content to validate.
+
+Configuration
+--------------------------
+
+The validation middleware can be configured through the plugin settings in TypoScript with the block called ``domDocumentValidationValidators``.
+
+ .. code-block::
+
+ plugin.tx_dlf {
+ settings {
+ domDocumentValidationValidators {
+ validator {
+ ...
+ },
+ validatorStack {
+ ...
+ },
+ ...
+
+Validators derived from ``Kitodo\Dlf\Validation\AbstractDlfValidator`` can be configured here. This also includes the use of validation stack implementations derived from ``Kitodo\Dlf\Validation\AbstractDlfValidationStack``, which use ``DOMDocument`` as the ``valueClassName`` for validation. This allows for multiple levels of nesting.
+
+In the background of the middleware, the ``Kitodo\Dlf\Validation\DOMDocumentValidationStack`` is used, to which the configured validators are assigned.
+
+TypoScript Example
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+ .. code-block::
+
+ plugin.tx_dlf {
+ settings {
+ storagePid = {$config.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
+ }
+ }
+ },
+ ...
diff --git a/Tests/Unit/Validation/DOMDocumentValidationStackTest.php b/Tests/Unit/Validation/DOMDocumentValidationStackTest.php
new file mode 100644
index 000000000..34092cc83
--- /dev/null
+++ b/Tests/Unit/Validation/DOMDocumentValidationStackTest.php
@@ -0,0 +1,83 @@
+
+ *
+ * This file is part of the Kitodo and TYPO3 projects.
+ *
+ * @license GNU General Public License version 3 or later.
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ */
+
+namespace Kitodo\Dlf\Tests\Unit\Validation;
+
+use DOMDocument;
+use InvalidArgumentException;
+use Kitodo\Dlf\Validation\DOMDocumentValidationStack;
+use Kitodo\Dlf\Validation\XmlSchemesValidator;
+use TYPO3\CMS\Extbase\Validation\Validator\UrlValidator;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+/**
+ * Testing AbstractDlfValidatorStack with implementation of DOMDocumentValidationStack.
+ *
+ * @package TYPO3
+ * @subpackage dlf
+ *
+ * @access public
+ */
+class DOMDocumentValidationStackTest extends UnitTestCase
+{
+ public function setUp(): void
+ {
+ parent::setUp();
+ $this->resetSingletonInstances = true;
+ }
+
+ public function testValueTypeException(): void
+ {
+ $domDocumentValidationStack = new DOMDocumentValidationStack([]);
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage("Type of value is not valid.");
+ $domDocumentValidationStack->validate("");
+ }
+
+ public function testEmptyValidationStack(): void
+ {
+ $domDocumentValidationStack = new DOMDocumentValidationStack([]);
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage("The validation stack has no validator.");
+ $domDocumentValidationStack->validate(new DOMDocument());
+ }
+
+ public function testValidatorClassNotExists(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage("Unable to load validator class.");
+ new DOMDocumentValidationStack([["className" => "Kitodo\Tests\TestValidator"]]);
+ }
+
+ public function testValidatorDerivation(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage("Class must be an instance of AbstractDlfValidator.");
+ new DOMDocumentValidationStack([["className" => UrlValidator::class]]);
+ }
+
+ public function testBreakOnError(): void
+ {
+ $xmlSchemesValidatorConfig = ["className" => XmlSchemesValidator::class, "configuration" => [["namespace" => "http://www.openarchives.org/OAI/2.0/", "schemaLocation" => "https://www.openarchives.org/OAI/2.0/OAI-PMH.xsd"]]];
+ $domDocumentValidationStack = new DOMDocumentValidationStack([$xmlSchemesValidatorConfig, $xmlSchemesValidatorConfig]);
+ $result = $domDocumentValidationStack->validate(new DOMDocument());
+ self::assertCount(1, $result->getErrors());
+
+ // disable breaking on error
+ $xmlSchemesValidatorConfig["breakOnError"] = "false";
+ $domDocumentValidationStack = new DOMDocumentValidationStack([$xmlSchemesValidatorConfig, $xmlSchemesValidatorConfig]);
+ $result = $domDocumentValidationStack->validate(new DOMDocument());
+ self::assertCount(2, $result->getErrors());
+ }
+}
diff --git a/Tests/Unit/Validation/SaxonXslToSvrlValidatorTest.php b/Tests/Unit/Validation/SaxonXslToSvrlValidatorTest.php
new file mode 100644
index 000000000..d18d404fc
--- /dev/null
+++ b/Tests/Unit/Validation/SaxonXslToSvrlValidatorTest.php
@@ -0,0 +1,81 @@
+
+ *
+ * This file is part of the Kitodo and TYPO3 projects.
+ *
+ * @license GNU General Public License version 3 or later.
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ */
+
+namespace Kitodo\Dlf\Tests\Unit\Validation;
+
+use InvalidArgumentException;
+use Kitodo\Dlf\Validation\SaxonXslToSvrlValidator;
+use ReflectionClass;
+use TYPO3\CMS\Extbase\Error\Result;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+/**
+ * Testing the SaxonXslToSvrlValidator class.
+ *
+ * @package TYPO3
+ * @subpackage dlf
+ *
+ * @access public
+ */
+class SaxonXslToSvrlValidatorTest extends UnitTestCase
+{
+ const SVRL = <<
+
+ The year must be greater than 1900.
+
+
+ SVRL;
+
+ public function setUp(): void
+ {
+ parent::setUp();
+ $this->resetSingletonInstances = true;
+ }
+
+ public function testJarFileNotFound(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage("Saxon JAR file not found.");
+ new SaxonXslToSvrlValidator([]);
+ }
+
+ public function testXslFileNotFound(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage("XSL Schematron file not found.");
+ // It only checks if a file exists at the specified path, so we can use one of the test files.
+ new SaxonXslToSvrlValidator(["jar" => 'EXT:dlf/Tests/Fixtures/Format/alto.xml']);
+ }
+
+ public function testValidation(): void
+ {
+ $saxonXslToSvrlValidator = new SaxonXslToSvrlValidator(["jar" => 'EXT:dlf/Tests/Fixtures/Format/alto.xml', "xsl" => 'EXT:dlf/Tests/Fixtures/Format/alto.xml']);
+ $reflection = new ReflectionClass($saxonXslToSvrlValidator);
+
+ $result = $reflection->getProperty("result");
+ $result->setAccessible(true);
+ $result->setValue($saxonXslToSvrlValidator, new Result());
+
+ $method = $reflection->getMethod("addErrorsOfSvrl");
+ $method->setAccessible(true);
+ $method->invoke($saxonXslToSvrlValidator, self::SVRL);
+
+ self::assertTrue($result->getValue($saxonXslToSvrlValidator)->hasErrors());
+ self::assertEquals("The year must be greater than 1900.", $result->getValue($saxonXslToSvrlValidator)->getErrors()[0]->getMessage());
+ }
+}
diff --git a/Tests/Unit/Validation/XmlSchemesValidatorTest.php b/Tests/Unit/Validation/XmlSchemesValidatorTest.php
new file mode 100644
index 000000000..339dd92f2
--- /dev/null
+++ b/Tests/Unit/Validation/XmlSchemesValidatorTest.php
@@ -0,0 +1,106 @@
+
+ *
+ * This file is part of the Kitodo and TYPO3 projects.
+ *
+ * @license GNU General Public License version 3 or later.
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ */
+
+namespace Kitodo\Dlf\Tests\Unit\Validation;
+
+use DOMDocument;
+use Kitodo\Dlf\Validation\XmlSchemesValidator;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+/**
+ * Testing the XmlSchemesValidator class.
+ *
+ * @package TYPO3
+ * @subpackage dlf
+ *
+ * @access public
+ */
+class XmlSchemesValidatorTest extends UnitTestCase
+{
+ const METS = <<
+
+
+
+
+
+
+
+
+
+ METS;
+
+ const METS_MODS = <<
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ METS_MODS;
+
+ public function setUp(): void
+ {
+ parent::setUp();
+ $this->resetSingletonInstances = true;
+ }
+
+ public function testValidation(): void
+ {
+ $xmlSchemesValidator = new XmlSchemesValidator(
+ [["namespace" => "http://www.loc.gov/METS/", "schemaLocation" => "http://www.loc.gov/standards/mets/mets.xsd"], ["namespace" => "http://www.loc.gov/mods/v3", "schemaLocation" => "http://www.loc.gov/standards/mods/mods.xsd"]]
+ );
+
+ $domDocument = new DOMDocument();
+ // Test with empty document
+ $result = $xmlSchemesValidator->validate($domDocument);
+ self::assertCount(1, $result->getErrors());
+
+ $domDocument->loadXML(self::METS);
+ $result = $xmlSchemesValidator->validate($domDocument);
+ self::assertFalse($result->hasErrors());
+
+ $domDocument->loadXML(self::METS_MODS);
+ $result = $xmlSchemesValidator->validate($domDocument);
+ self::assertFalse($result->hasErrors());
+
+ // Test with wrong mets element
+ $domDocument->loadXML(str_replace("mets:metsHdr", "mets:Hdr", self::METS));
+ $result = $xmlSchemesValidator->validate($domDocument);
+ self::assertTrue($result->hasErrors());
+
+ // Test with wrong mods element
+ $domDocument->loadXML(str_replace("mods:titleInfo", "mods:title", self::METS_MODS));
+
+ $result = $xmlSchemesValidator->validate($domDocument);
+ self::assertTrue($result->hasErrors());
+ }
+}
diff --git a/composer.json b/composer.json
index f74baad88..7b3d42fcb 100644
--- a/composer.json
+++ b/composer.json
@@ -39,7 +39,8 @@
"caseyamcl/phpoaipmh": "^3.3.1",
"slub/php-mods-reader": "^0.4.0",
"ubl/php-iiif-prezi-reader": "0.3.0",
- "solarium/solarium": "5.2 - 6.3"
+ "solarium/solarium": "5.2 - 6.3",
+ "symfony/process": "^5.4.40"
},
"require-dev": {
"phpstan/phpstan": "^1.11.10",