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

Проверка XML. Schematron

Проверка XML. Schematron - 1


Так или иначе, все сервисы сталкиваются с задачами валидации. Часто они сводятся к простым и однотипным проверкам: заполнены ли все обязательные поля, верен ли формат телефонного номера, кредитной карты и пр. Но существуют проекты, в которых условия и правила проверок более разнообразные, да и те временами требуют серьёзного пересмотра. Внесение же изменений или создание дополнительных правил валидации требует непростых согласований и привлечения внимания нескольких команд разработчиков, обновления документации.

Недавно мне довелось поучаствовать в проекте, особую роль в котором занимают функции форматно-логического контроля входящих документов. Как следствие, у меня появились некоторые варианты решения подобных задач. Одним из них я и хочу поделиться.

Давайте разбираться на абстрактном примере. Предположим, что от поставщика данных мы получаем xml-файл с перечнем водительских удостоверений. Но прежде чем появится возможность их использования, нам предстоит:

  • проверить валидность полученного xml;
  • провести форматный и логический контроль каждого водительского удостоверения.

В качестве тестовых данных примем source/main.xml [1]. И ещё, дабы не утомлять читателя длинными листингами, исходники и всё необходимое для их исполнения размещено в гит-репозитории [2].

XML Schema

Первая мысль, которая приходит в голову — использование 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 позволяет выполнять проверки на основе содержимого документа. Это даёт возможность накладывать ограничения на один элемент, исходя из содержания другого.

Проверка XML. Schematron - 2

Этапы валидации

Небольшое замечание. В нашем примере мы смотрим на проблему валидации только со стороны получателя данных. Но стоит понимать, что те же самые XSD и Schematron-схемы могут быть использованы и поставщиком данных. Это позволит поставщику убедиться в валидности своих данных локально, то есть до фактического обращения к сервису получателя.

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]. Пока же познакомимся с основными из них.

▍ <rule>, <assert> и <report>

Краеугольным камнем Schematron являются «тесты» — утверждения и отчёты, <assert> и <report> соответственно. Фактически именно они и выполняют всю работу по проверкам. Эти элементы должны иметь атрибут @ test, в котором определено XPath-выражение. Работает это так:

  • <assert> — тест проваливается, когда результатом XPath-выражения в @ test является FALSE;
  • <report> — тест проваливается, когда в @ test возвращается TRUE.

Шаблон 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 в принципе пригоден к пониманию и неподготовленным в этом плане человеком.

Интеграция cо Schematron

Как правило, процессор Schematron представляет из себя конвейер XSLT-преобразований. Результатом этих преобразований является отчёт в формате XML, известном как Schematron Validation Report Language (SVRL).

Проверка XML. Schematron - 3

Пример реализации 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> и список проваленных тестов.

Проверка XML. Schematron - 4

Пример результата трансформации SVRL в HTML

Включения в схемы

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

Проверка XML. Schematron - 5

В качестве живого примера сгенерируем Schematron-схемы на основе файлов source/categories.xml и source/pre-training.xml. Для этого выполните (требуется Java):

~/x_validation$ ./tools/scaffold/schematron.sh

Этот скрипт создаст следующие схемы:

  • schematron/include/enumerate_categories.sch [10] — проверка, что указаны только известные категории ТС;
  • schematron/include/age_restriction.sch [11] — проверка, что возраст водителя соответствует указанным категориям ТС;
  • schematron/include/pre-training.sch [12] — проверка, что водитель имеет право получить указанную категорию, т. к. имеет достаточный стаж управления ТС другой категории.

Для генерации 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.

Проверка XML. Schematron - 6

Пример результата трансформации 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