- PVSM.RU - https://www.pvsm.ru -

Недавно мне довелось поучаствовать в проекте, особую роль в котором занимают функции форматно-логического контроля входящих документов. Как следствие, у меня появились некоторые варианты решения подобных задач. Одним из них я и хочу поделиться.
Давайте разбираться на абстрактном примере. Предположим, что от поставщика данных мы получаем xml-файл с перечнем водительских удостоверений. Но прежде чем появится возможность их использования, нам предстоит:
В качестве тестовых данных примем source/main.xml [1]. И ещё, дабы не утомлять читателя длинными листингами, исходники и всё необходимое для их исполнения размещено в гит-репозитории [2].
Первая мысль, которая приходит в голову — использование XML Schema. И действительно, наличие XSD-схемы как минимум позволит нам быть уверенными в валидности XML. К тому же схема поможет провести некоторый форматный контроль.
<!-- schema/main.xsd -->
<!-- пример проверки формата серии удостоверения -->
...
<xs:element name="Series">
<xs:annotation>
<xs:documentation>Серия документа. Две цифры и две русские заглавные буквы для водительского удостоверения, полученного до 1 марта 2011 г., или четыре цифры для водительского удостоверения, полученного после 1 марта 2011. Пример: 44АА или 4403</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:length value="4"/>
<xs:pattern value="([0-9]){2}([А-Я, 0-9]){2}"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
...
Проверить соответствие документа схеме довольно просто:
~/x_validation$ xmllint --schema schema/main.xsd source/main.xml --noout
Но, к сожалению, XML Schema не сможет выполнить все необходимые нам проверки. Внимательный читатель заметил, что формат серии водительского удостоверения зависит от даты выдачи последнего. Такое с помощью XSD мы не проверим. Возникнут проблемы и при проверке, например, возраста водителя, наличия и стажа управления ТС определённых категорий и т. д.
Для реализации форматно-логического контроля потребуется ещё один этап проверок. На первом этапе мы убедимся, что xml-файл соответствует xsd-схеме. И если тот окажется валидным, следующим этапом проверим, удовлетворяет ли содержание xml бизнес-правилам.
Для реализации второго этапа отлично подойдёт Schematron. В отличие от XML Schema, Schematron позволяет выполнять проверки на основе содержимого документа. Это даёт возможность накладывать ограничения на один элемент, исходя из содержания другого.

Этапы валидации
Небольшое замечание. В нашем примере мы смотрим на проблему валидации только со стороны получателя данных. Но стоит понимать, что те же самые XSD и Schematron-схемы могут быть использованы и поставщиком данных. Это позволит поставщику убедиться в валидности своих данных локально, то есть до фактического обращения к сервису получателя.
По заявлениям ресурса schematron.com [3], этот инструмент является перьевой метёлкой [4], позволяющей добраться до углов, недоступных другим языкам схем. На мой взгляд, это крайне точное определение происходящего.
Cхема Schematron выглядит примерно так:
<?xml version="1.0" encoding="UTF-8"?>
<sch:schema queryBinding="xslt2" xmlns="http://purl.oclc.org/dsdl/schematron">
<pattern><!-- набор связанных правил -->
<rule context=""><!-- набор тестов, применимых в определённом контексте -->
<assert test=""> ... </assert>
<report test=""> ... </assert>
</rule>
</pattern>
</schema>
Подробное описание элементов и их атрибутов доступно в шпаргалке [5]. Пока же познакомимся с основными из них.
Краеугольным камнем Schematron являются «тесты» — утверждения и отчёты, <assert> и <report> соответственно. Фактически именно они и выполняют всю работу по проверкам. Эти элементы должны иметь атрибут @ test, в котором определено XPath-выражение. Работает это так:
Шаблон XPath, определённый в атрибуте @ context элемента <rule>, задаёт контекст для дочерних <assert> и <report>. Работает это аналогично @ match в элементе <xsl:template> в XSLT. Как и в случае с XSLT, если в @ context указано, например, Categories/Category, то тесты будут применены к элементам Category только в том случае, если те являются дочерними для Categories.
Каждое утверждение или отчёт может иметь атрибут @ role [6]. Его назначение — определение уровня важности теста. Таким образом, валидация может сводиться не к простому «VALID / INVALID», а к принятию решения о действительности XML в зависимости от выявленных «FATAL», «ERROR», «WARNING» и прочих уровней проваленных тестов.
Вернёмся к нашему примеру. С помощью Schematron проверка корректности дат, поиск дубликатов удостоверений и выявление повторяющихся категорий в этих удостоверениях могут быть выполнены следующим образом:
<!-- ещё больше примеров в schematron/main.sch -->
<sch:schema queryBinding="xslt2" xmlns:sch="http://purl.oclc.org/dsdl/schematron">
<sch:pattern name="Date">
<sch:title>Проверка корректности дат</sch:title>
<sch:rule context="License">
<sch:title>Начало и окончание действия удостоверения</sch:title>
<sch:assert test="xs:date(current-date()) >= xs:date(IssueDate)">Дата выдачи документа не должна быть больше текущей даты</sch:assert>
<sch:assert test="xs:date(ExpDate) = xs:yearMonthDuration('P10Y')+xs:date(IssueDate)" role="WARNING">Удостоверение должно быть выдано на 10 лет</sch:assert>
</sch:rule>
<sch:rule context="License/Categories/Category">
<sch:title>Начало и окончание действия категории</sch:title>
<sch:assert test="xs:date(current-date()) >= xs:date(IssueDate)">Дата начала действия категории не должна быть больше текущей даты</sch:assert>
<sch:assert test="xs:date(../../ExpDate) >= xs:date(ExpDate)">Срок действия категории не может превышать срок действия удостоверения</sch:assert>
</sch:rule>
</sch:pattern>
<sch:pattern name="Dublicate">
<sch:title>Проверка на уникальность удостоверения (в границах файла) и категории ТС (в границах удостоверения)</sch:title>
<sch:rule context="License">
<sch:let name="SeriesNumber" value="string-join((Series,Number),' ')"/>
<sch:assert test="count(Series[$SeriesNumber = ../preceding-sibling::License/string-join((Series, Number), ' ')])=0" role="FATAL" >Удостоверение <sch:value-of select="$SeriesNumber"/> повторяется</sch:assert>
</sch:rule>
<sch:rule context="Categories/Category">
<sch:assert test="count(Name[. = ../preceding-sibling::Category/Name])=0" role="ERROR" >Категория "<sch:value-of select="Name"/>" указана в удостоверении несколько раз</sch:assert>
</sch:rule>
</sch:pattern>
</sch:schema>
В случае провала какого-либо теста будет сгенерировано сообщение — значение элемента <assert> или <report>. В консоли это может выглядеть так:
~/x_validation$ java -jar tools/schxslt-cli.jar -d source/main.xml -s schematron/main.sch -v
[invalid] /home/artirm/x_validation/source/main.xml
[invalid] /home/artirm/x_validation/source/main.xml failed-assert /Q{}DrivingLicenses[1]/Q{}License[8]/Q{}Categories[1]/Q{}Category[1] L - не является категорией ТС
[invalid] /home/artirm/x_validation/source/main.xml failed-assert /Q{}DrivingLicenses[1]/Q{}License[12]/Q{}Driver[1] В фамилии водителя обнаружены недопустимые символы
[invalid] /home/artirm/x_validation/source/main.xml failed-assert /Q{}DrivingLicenses[1]/Q{}License[15]/Q{}Driver[1] В отчестве водителя обнаружены недопустимые символы
[invalid] /home/artirm/x_validation/source/main.xml failed-assert /Q{}DrivingLicenses[1]/Q{}License[5] Удостоверение должно быть выдано на 10 лет
[invalid] /home/artirm/x_validation/source/main.xml failed-assert /Q{}DrivingLicenses[1]/Q{}License[4] Удостоверение 11АБ 123456 повторяется
[invalid] /home/artirm/x_validation/source/main.xml failed-assert /Q{}DrivingLicenses[1]/Q{}License[11]/Q{}Categories[1]/Q{}Category[2] Категория "D" указана в удостоверении несколько раз
[invalid] /home/artirm/x_validation/source/main.xml successful-report /Q{}DrivingLicenses[1]/Q{}License[7]/Q{}Driver[1] В удостоверении указаны категории ТС, для управления которыми водитель должен быть старше 16 лет
[invalid] /home/artirm/x_validation/source/main.xml successful-report /Q{}DrivingLicenses[1]/Q{}License[9]/Q{}Categories[1]/Q{}Category[1] Для получения категории "BE" водитель должен иметь право управления транспортными средствами категории "B" не менее 12 месяцев
[invalid] /home/artirm/x_validation/source/main.xml successful-report /Q{}DrivingLicenses[1]/Q{}License[6] Серия водительского удостоверения, выданного после 1 марта 2011 г., должна содержать две цифры и две буквы кириллицы
[invalid] /home/artirm/x_validation/source/main.xml successful-report /Q{}DrivingLicenses[1]/Q{}License[7] Серия водительского удостоверения, выданного до 1 марта 2011 г, должна содержать четыре цифры
[invalid] /home/artirm/x_validation/source/main.xml successful-report /Q{}DrivingLicenses[1]/Q{}License[13]/Q{}Driver[1] Не указана фамилия или имя водителя
[invalid] /home/artirm/x_validation/source/main.xml successful-report /Q{}DrivingLicenses[1]/Q{}License[14]/Q{}Driver[1] Не указана фамилия или имя водителя
[invalid] /home/artirm/x_validation/source/main.xml successful-report /Q{}DrivingLicenses[1]/Q{}License[16]/Q{}Driver[1] Не указано отчество водителя
Ещё немного о работе со схемами Schematron. При их составлении может потребоваться написание довольно сложных XPath-выражений. Откровенно говоря, составление XPath-запросов — самая трудная часть работы. Тем не менее, уже составленный XPath в принципе пригоден к пониманию и неподготовленным в этом плане человеком.
Как правило, процессор Schematron представляет из себя конвейер XSLT-преобразований. Результатом этих преобразований является отчёт в формате XML, известном как Schematron Validation Report Language (SVRL).

Пример реализации Schematron-процессора
Это даёт нам возможность использовать Schematron везде, где доступен XSLT. А применение XSLT-трансформаций к SVRL позволяет получать результаты проверок в HTML, PDF или любом другом формате.
Простейший пример работы со Schematron в PHP можно увидеть в tools/schematron.php [7]. Для работы потребуется SaxonC-HE [8].
~/x_validation$ php tools/schematron.php > tools/output/output.html
После выполнения вышеуказанной команды в tools/output/output.html [9] появится перечень выполненных <pattern> и список проваленных тестов.

Пример результата трансформации SVRL в HTML
XML Schema и схемы Schematron поддерживают элементы <include>, что позволяет «собирать» схемы из отдельных частей. В некоторых ситуациях эти части имеет смысл генерировать автоматически. Например, при необходимости формировать проверки на основе информации из БД. Реализация подобной генерации может выглядеть так: экспорт данных в XML и последующая XSLT-трансформация в Schematron и XSD-схемы (их «части»).

В качестве живого примера сгенерируем Schematron-схемы на основе файлов source/categories.xml и source/pre-training.xml. Для этого выполните (требуется Java):
~/x_validation$ ./tools/scaffold/schematron.sh
Этот скрипт создаст следующие схемы:
Для генерации XSD-схемы:
~/x_validation$ ./tools/scaffold/xsd.sh
В результате будет сгенерирован schema/include/enumerate_categories.xsd [13] для включения в XSD-схему всех допустимых категорий ТС.
По сути говоря, применение XSLT ограничено только лишь нашей фантазией. В качестве ещё одного полезного примера использования трансформаций рассмотрим автоматическое создание пользовательского интерфейса из составленных ранее XSD-схем.
Чтобы создавать HTML5-формы из схемы XML, обратимся к проекту xsd2html2xml [14]. Немного изменим JavaScript, создадим и вызовем bash-скрипт.
~/x_validation$ ./tools/scaffold/xsd2html.sh
Вследствие чего и получим UI для создания валидных XML — tools/output/xsd2html.html [15].
Схема Schematron сама по себе содержит достаточно информации для составления технической документации. Но кроме этого, Schematron игнорирует неизвестную ему разметку и позволяет разработчику оставлять дополнительные метаданные в схеме. Всё это даёт повод задуматься над автоматическим созданием документации на основе имеющихся схем.
Возвратимся к примеру и сгенерируем документацию из нашей схемы schematron/main.sch [16].
~/x_validation$ ./tools/scaffold/docbook.sh
Скриптом tools/scaffold/docbook.sh [17] будет создано два файла: tools/output/docbook[DateTime].xml и tools/output/docbook[DateTime].html.

Пример результата трансформации Shematron-схемы в HTML-документацию
В рамках этой короткой заметки я постарался осветить основные моменты работы со Schematron и его место в процессе валидации XML, привести простые, но ёмкие примеры. Тем не менее, за рамками осталось много интересных и важных моментов. Например, совсем не затронута важнейшая тема тестирования [18] схем Schematron. Ничего не сказано о расширении Schematron Quick Fix [19]. Но я надеюсь, что заинтересованный читатель разберётся в этом самостоятельно, а возможно, и дополнит написанное, оставив свой комментарий.
Автор:
artirm
Источник [20]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/383435
Ссылки в тексте:
[1] source/main.xml: https://github.com/ensoelectric/x_validation/blob/master/source/main.xml
[2] гит-репозитории: https://github.com/ensoelectric/x_validation
[3] schematron.com: http://schematron.com
[4] перьевой метёлкой: https://ru.m.wikipedia.org/wiki/%D0%9F%D0%B5%D1%80%D1%8C%D0%B5%D0%B2%D0%B0%D1%8F_%D0%BC%D0%B5%D1%82%D1%91%D0%BB%D0%BA%D0%B0
[5] шпаргалке: https://www.schematron.com/schematron_open_documentation/schematron_cheat_sheet.html
[6] @ role: https://www.schematron.com/standards/standard_severity_levels_with_schematron_%40role.html
[7] tools/schematron.php: https://github.com/ensoelectric/x_validation/blob/master/tools/schematron.php
[8] SaxonC-HE: https://www.saxonica.com/saxon-c/documentation11/index.html#!starting/installing/installingLinux
[9] tools/output/output.html: https://github.com/ensoelectric/x_validation/blob/master/tools/output/output.html
[10] schematron/include/enumerate_categories.sch: https://github.com/ensoelectric/x_validation/blob/master/schematron/include/enumerate_categories.sch
[11] schematron/include/age_restriction.sch: https://github.com/ensoelectric/x_validation/blob/master/schematron/include/age_restriction.sch
[12] schematron/include/pre-training.sch: https://github.com/ensoelectric/x_validation/blob/master/schematron/include/pre-training.sch
[13] schema/include/enumerate_categories.xsd: https://github.com/ensoelectric/x_validation/blob/master/schema/include/enumerate_categories.xsd
[14] xsd2html2xml: https://github.com/MichielCM/xsd2html2xml
[15] tools/output/xsd2html.html: https://github.com/ensoelectric/x_validation/blob/master/tools/output/xsd2html.html
[16] schematron/main.sch: https://github.com/ensoelectric/x_validation/blob/master/schematron/main.sch
[17] tools/scaffold/docbook.sh: https://github.com/ensoelectric/x_validation/blob/master/tools/scaffold/docbook.sh
[18] тестирования: https://github.com/Schematron/stf
[19] Schematron Quick Fix: https://www.schematron-quickfix.com
[20] Источник: https://habr.com/ru/post/719956/?utm_source=habrahabr&utm_medium=rss&utm_campaign=719956
Нажмите здесь для печати.