При работе с ориентированным на данные XML часто требуется оперировать "управляемыми словарями", известными также как перечисляемые величины. Давайте рассмотрим следующий пример банковского обобщённого счёта:
<accountSummary>
<timestamp>2003-01-01T12:25:00</timestamp>
<currency>USD</currency>
<balance>2703.35</balance>
<interest rounding="down">27.55</interest>
</accountSummary>
В этом документе присутствуют два управляемых словаря. Первый - это словарь валют, трехсимвольный код валюты по ISO-4217 ("USD" - это доллар США). Второй - это правило округления (rounding) процентов: "up" (в сторону увеличения), "down" (в сторону уменьшения) и or "nearest" (ближайший). В нашем примере банк предпочитает округлять проценты в сторону уменьшения.
Проблема, возникающая при проектировании этой схемы, заключается в том, что управление кодами валют ISO осуществляется вне нашего ведома. Эти коды могут быть изменены в любой момент времени, и если их встроили в схему, ее потребуется переиздавать всякий раз, как организация ISO меняет свои коды. А это может оказаться весьма накладным. Сказанное особенно справедливо в отношении предприятия, где любая модификация схемы, какой бы незначительной она ни была, может потребовать проведения полного тестирования приложения, использующего схему.
В этой статье рассказывается, как можно управлять управляемыми словарями при использовании W3C XML-схемы (W3C XML Schemas), поскольку именно эта спецификация является основным форматом XML-схем для ориентированного на данные XML. Обратите внимание на то, что под "словарями" мы будем понимать перечисляемые списки значений элементов-атрибутов, что отлично от других контекстов, в которых "словари" - это наборы имен элементов XML.
Прежде чем задуматься над тем, какой из управляемых словарей находится вне нашего контроля, первое, что необходимо сделать, это создать схему для обобщённых счётов, используя W3C XML-схему. В рамках этой статьи мы воспользуемся подмножеством трехсимвольных кодов валют ISO. Соответствующая схема будет иметь следующий вид:
<xsd:schema xmlns:xsd = "http://www.w3.org/2001/XMLSchema"
version = "1.0"
elementFormDefault = "qualified">
<xsd:element name = "accountSummary">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref = "timestamp"/>
<xsd:element ref = "currency"/>
<xsd:element ref = "balance"/>
<xsd:element ref = "interest"/>
</xsd:sequence>
<xsd:attribute name = "version" use = "required">
<xsd:simpleType>
<xsd:restriction base = "xsd:string">
<xsd:pattern value = "[1-9]+[0-9]*\.[0-9]+"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name = "timestamp" type = "xsd:dateTime"/>
<xsd:element name = "currency" type = "iso3currency"/>
<xsd:element name = "balance" type = "xsd:decimal"/>
<xsd:element name = "interest">
<xsd:complexType>
<xsd:simpleContent>
<xsd:extension base = "xsd:decimal">
<xsd:attribute name = "rounding" use = "required"
type = "roundingDirection"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
</xsd:element>
<xsd:simpleType name = "iso3currency">
<xsd:annotation>
<xsd:documentation>ISO-4217 3-letter currency codes,
as defined at (трехсимвольные коды валют ISO-4217, как определено по следующему адресу)
http://www.bsi-global.com/Technical+Information/Publications/_Publications/tig90.xalter
or available from (или доступно по следующему адресу)
http://www.xe.com/iso4217.htm
Only a subset are defined here (Здесь определяются только подмножества).</xsd:documentation>
</xsd:annotation>
<xsd:restriction base = "xsd:string">
<xsd:enumeration value = "AUD"/><!-- Australian Dollar -->
<xsd:enumeration value = "BRL"/><!-- Brazilian Real -->
<xsd:enumeration value = "CAD"/><!-- Canadian Dollar -->
<xsd:enumeration value = "CNY"/><!-- Chinese Yen -->
<xsd:enumeration value = "EUR"/><!-- Euro -->
<xsd:enumeration value = "GBP"/><!-- British Pound -->
<xsd:enumeration value = "INR"/><!-- Indian Rupee -->
<xsd:enumeration value = "JPY"/><!-- Japanese Yen -->
<xsd:enumeration value = "RUR"/><!-- Russian Rouble -->
<xsd:enumeration value = "USD"/><!-- US Dollar -->
<xsd:length value = "3"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name = "roundingDirection">
<xsd:annotation>
<xsd:documentation>Whether the interest is
rounded up, down or to the
nearest round value.(Округляется ли процент вверх, вниз
или до ближайшего округленного числа.)</xsd:documentation>
</xsd:annotation>
<xsd:restriction base = "xsd:string">
<xsd:enumeration value = "up"/>
<xsd:enumeration value = "down"/>
<xsd:enumeration value = "nearest"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
Обратите внимание на два управляемых словаря (перечисления): простые типы iso3currency и roundingDirection - длина iso3currency явно установлена равной 3, чтобы избежать в будущем досадных опечаток при редактировании, когда потребуется корректировать список валют.
Также стоит отметить, что значению факультативного атрибута version было задано значение "1.0". Дело в том, что при работе с ориентированными на данные XML-сообщениями, часто необходимо одновременно поддерживать многочисленные версии схемы сообщений, поскольку, по-видимому, невозможно одновременно "поднять" системы, использующие эту схему, до ее последней версии. Таким образом, крайне необходимо указать версию схемы, по которой проверялось на допустимость XML-сообщение. С учетом сказанного, мы обозначим нашу схему accountSummary-1.0.xsd, чтобы будущие версии не перезаписали текущую.
Кроме того, для того, чтобы экземпляры сообщения четко идентифицировали свою версию схемы, к элементу accountSummary был добавлен атрибут version. Предполагается, что эти номера версии записываются как M.N, где M - это основной номер версии, а N - дополнительный. В результате, приведенный выше код для обобщённого счёта можно переписать следующим образом:
<accountSummary
version = "1.0"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation = "accountSummary-1.0.xsd">
<timestamp>2003-01-01T12:25:00</timestamp>
<currency>USD</currency>
<balance>2703.35</balance>
<interest rounding = "down">27.55</interest>
</accountSummary>
При работе с управляемыми словарями (перечислениями) в схемах, важно оценить степень изменчивости каждого словаря. Изменчивый словарь - это словарь, изменения которого, как предполагается, не связаны с выходом версий схемы. Стабильный словарь - это словарь, который меняется (если такое вообще происходит), только если появляются новые редакции схемы. Если изменчивые словари включены в схему, то это чревато возникновением проблем, поскольку они требуют выпуска дополнительных версий всех зависимых приложений.
В нашем примере обобщённого счёта коды валют - это изменчивый словарь: они контролируются извне, международной организацией ISO, и ISO может добавлять или удалять валюты в любой момент времени. С другой стороны, набор правил округления {"up", "down", "nearest"}, вероятно, не будет меняться, поэтому он является стабильным словарем. Для человека, который осуществляет сопровождение приложения, оперирующего обобщёнными счётами, добавление нового правила округления означало бы написание, тестирование и внедрение новой версии этого приложения. Поэтому "политическое давление" привело бы к тому, что величины округления изменялись бы только как часть запланированного выхода редакции схемы. Таким образом, разумно оставить простой тип roundingDirection встроенным в эту схему.
Тем не менее, вряд ли было бы допустимо переписывать приложение, чтобы отрабатывать изменение в наборе кодов валют, в противном случае это свидетельствовало бы о негибкости разработки. Поскольку эти коды управляются извне, их необходимо изолировать: создадим для них отдельную схему словаря. Схема словаря - это схема, которая содержит единственное определение простого типа с перечисляемыми величинами и больше ничего. Такая схема, обозначенная как iso3currency-1.0.xsd, будет иметь следующий вид:
<xsd:schema xmlns:xsd = "http://www.w3.org/2001/XMLSchema"
version = "1.0"
elementFormDefault = "qualified">
<xsd:simpleType name = "iso3currency">
<xsd:annotation>
<xsd:documentation>ISO-4217 3-letter currency codes,
as defined at (трехсимвольные коды валют ISO-4217, как определено по следующему адресу)
http://www.bsi-global.com/Technical+Information/Publications/_Publications/tig90.xalter
or available from (или доступно по следующему адресу)
http://www.xe.com/iso4217.htm
Only a subset are defined here (Здесь определяются только подмножества).</xsd:documentation>
</xsd:annotation>
<xsd:restriction base = "xsd:string">
<xsd:enumeration value = "AUD"/><!-- Australian Dollar -->
<xsd:enumeration value = "BRL"/><!-- Brazilian Real -->
<xsd:enumeration value = "CAD"/><!-- Canadian Dollar -->
<xsd:enumeration value = "CNY"/><!-- Chinese Yen -->
<xsd:enumeration value = "EUR"/><!-- Euro -->
<xsd:enumeration value = "GBP"/><!-- British Pound -->
<xsd:enumeration value = "INR"/><!-- Indian Rupee -->
<xsd:enumeration value = "JPY"/><!-- Japanese Yen -->
<xsd:enumeration value = "RUR"/><!-- Russian Rouble -->
<xsd:enumeration value = "USD"/><!-- US Dollar -->
<xsd:length value = "3"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
Видно, словарь валют имеет свою собственную версию и, таким образом, период выхода очередной редакции. Эта схема словаря может быть включена в новую (1.1) версию основной схемы сообщения:
<xsd:schema xmlns:xsd = "http://www.w3.org/2001/XMLSchema"
version = "1.1"
elementFormDefault = "qualified">
<xsd:include schemaLocation = "iso3currency-1.0.xsd"/>
<xsd:element name = "accountSummary">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref = "timestamp"/>
<xsd:element ref = "currency"/>
<xsd:element ref = "balance"/>
<xsd:element ref = "interest"/>
</xsd:sequence>
<xsd:attribute name = "version" use = "required">
<xsd:simpleType>
<xsd:restriction base = "xsd:string">
<xsd:pattern value = "[1-9]+[0-9]*\.[0-9]+"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name = "timestamp" type = "xsd:dateTime"/>
<xsd:element name = "currency" type = "iso3currency"/>
<xsd:element name = "balance" type = "xsd:decimal"/>
<xsd:element name = "interest">
<xsd:complexType>
<xsd:simpleContent>
<xsd:extension base = "xsd:decimal">
<xsd:attribute name = "rounding" use = "required" type = "roundingDirection"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
</xsd:element>
<xsd:simpleType name = "roundingDirection">
<xsd:annotation>
<xsd:documentation>Whether the interest is
rounded up, down or to the
nearest round value.</xsd:documentation>
</xsd:annotation>
<xsd:restriction base = "xsd:string">
<xsd:enumeration value = "up"/>
<xsd:enumeration value = "down"/>
<xsd:enumeration value = "nearest"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
В соответствии с правилом обозначения, эта схема названа accountSummary-1.1.xsd. Обратите внимание на отсутствие кодов валют в основной схеме.
Сложность с accountSummary-1.1.xsd заключается в том, что она напрямую импортирует iso3currency-1.0.xsd. Так, при появлении новой версии схемы словаря валют ISO, по-прежнему требуется выпускать новую редакцию схемы обобщённого счёта. Необходим механизм развязывания версий схемы словаря от версий основной схемы. Простое решение - воспользоваться "проходной" схемой словаря, не имеющей версии:
<xsd:schema xmlns:xsd = "http://www.w3.org/2001/XMLSchema"
elementFormDefault = "qualified">
<xsd:include schemaLocation = "iso3currency-1.0.xsd"/>
</xsd:schema>
У этой схемы, обозначенной как iso3currency.xsd, отсутствует атрибут version. Для завершения развязывания схем выпускается новая версия основной схемы, accountSummary-1.2.xsd, - единственное ее отличие от версии 1.1 состоит в том, что элемент <xsd:include> изменился с
<xsd:include schemaLocation = "iso3currency-1.0.xsd"/>
на<xsd:include schemaLocation = "iso3currency.xsd"/>
Подобное развязывание схем словарей не лишено сложностей. Во-первых, по мере того, как будут появляться новые версии схемы словарей валют, существующие файлы экземпляров сделаются недопустимыми, если в них содержатся коды валют, которые были удалены ISO. В некоторых случаях такая ситуация неприемлема, но для нашего примера это возможно. Если файл экземпляра ссылается на код валюты, который более не существует, он станет семантически недопустимым; ему также ничто не мешает оказаться синтаксически недопустимым. Тогда можно воспользоваться синтаксической недопустимостью, чтобы обнаруживать такие экземпляры и направлять их для специальной обработки, чтобы код основного приложения смог заниматься допустимыми кодами валют. Убрав обработку ошибок из основного приложения, можно сократить код основного приложения и упростить его сопровождение.
Во-вторых, с учетом того, что коды валют могут менять в любой момент, необходимо обеспечить синхронизацию между кодами валют в схеме словаря валют и кодами валют, известным приложениям. Эту задачу можно решить двумя способами. В первом случае приложения могут воспользоваться схемой словаря как источником кодов валют. Если рассматривать схему словаря как XML-файл, быстрый SAX-парсер - это все, что требуется, чтобы вытащить элементы <xsd:enumeration>, содержащие допустимые величины. При втором подходе коды валют хранятся в центральной реляционной базе данных. Тогда приложения могут обращаться к ее таблице напрямую, а схема словаря может быть динамически генерироваться из этой же таблицы. Любой из описанных методов обеспечивает синхронность наборов допустимых величин по приложениям.
Наконец, в-третьих, использование подобных схем словарей допустимо только в том случае, если изменения, вносимые в них, сводятся либо к добавлению перечисляемой величины, либо ее удалению.
Структура схем словарей не должна изменяться ни при каких условиях. Если в схему словаря было бы добавлено новое определение простого или сложного типа или элемента, это могло изменить результаты проверки допустимости экземпляра по основной схеме и привести к сбою в работе базового приложения. Таким образом, необходимо "проверять на допустимость" схемы словарей, чтобы быть уверенным, что они содержат только одно определение простого типа с перечисляемыми величинами. Именно такую ситуацию описал Уилл Провост (Will Provost) в своей статье "Работа с метасхемой" ("Working with a Metaschema").
Очевидным решением было бы написание в качестве метасхемы схемы для схем словарей. На практике автор статьи не стал бы это делать. Известно, что существующая "Схема для схем" ("Schema for Schemas") не на все 100% верна при описании синтаксиса, и именно по этой причине редакторы схем используют ее в качестве справочного, а не нормативного руководства. По этой же причине, а также из-за того, что формат схемы словаря довольно простой, воспользуемся следующей схемой Schematron (Schematron schema):
<sch:schema
xmlns = "http://www.w3.org/2001/XMLSchema"
xmlns:sch = "http://www.ascc.net/xml/schematron"
xmlns:xsd = "http://www.w3.org/2001/XMLSchema"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation =
"http://www.ascc.net/xml/schematron schematron-1.5.xsd">
<sch:title>Controlled vocabulary validation (Проверка допустимости
управляемого словаря)</sch:title>
<!-- The input is assumed to be a valid W3C XML Schema. -->
<!-- (Предполагается, входные данные допустимая W3C XML-схема). -->
<!-- This just checks that it is also a valid -->
<!-- vocabulary Schema. -->
<!-- (Ниже просто проверяется допустимость Схемы словаря). -->
<sch:pattern name = "controlled-vocabulary-schema">
<sch:rule context = "schema">
<sch:assert test = "count(*) = count(simpleType[@name])"
>The schema must contain only a
single simple type definition
(Эта схема должна содержать только
одно определение простого типа).</sch:assert>
<sch:assert test = "count(simpleType[@name]) = 1"
>The schema must contain a single simpleType
definition or a single include
(Эта схема должна содержать одно определение
simpleType или один элемент include).</sch:assert>
</sch:rule>
<sch:rule context = "simpleType">
<sch:assert test = "@name"
>The simpleType must have a name
(simpleType должен иметь атрибут name)..</sch:assert>
<sch:assert test = "count(restriction) = 1"
>The simpleType must contain a
single restriction
(simpleType должен содержать одно ограничение).</sch:assert>
<sch:assert test = "count(*) = count(annotation)+count(restriction)"
>The simpleType may have an annotation as well as its
restriction, but no other structure
(У simpleType может быть аннотация, а также свои
ограничения, но никакая другая структура).</sch:assert>
</sch:rule>
<sch:rule context = "restriction">
<sch:assert test = "enumeration"
>A restriction must contain enumerated values
(Ограничение должно содержать перечисляемые величины).</sch:assert>
</sch:rule>
<sch:rule context = "enumeration">
<sch:key name = "enumerationsByValue" path = "@value"/>
<sch:assert test = "count(key('enumerationsByValue', @value)) = 1"
>An enumerated value must be unique
(Перечисляемая величина должны быть уникальной).</sch:assert>
</sch:rule>
</sch:pattern>
</sch:schema>
Чтобы в среде Windows проверить на допустимость схему словаря по этой схеме Schematron, вы можете воспользоваться бесплатным валидатором от Topologi (free validator from Topologi). Касательно других платформ, смотрите список инструментальных средств в Справочнике ресурсов Schematron (Schematron Resource Directory). Более подробную информацию о Schematron можно найти в статье Чимези Огбуджи (Chimezie Ogbuji) "Проверка допустимости с помощью Schematron" ("Validating XML with Schematron").
Утверждения в Schematron выражаются с помощью выражений, значением которых должна быть true. Если их значением оказывается false, генерируется ошибка проверки допустимости по Schematron. При рассмотрении нашей схемы Schematron стоит отметить следующее:
Поместив изменчивые управляемые словари (перечисления) в свои собственные схемы словарей, можно повысить управляемость W3C XML-схем. В этой статье было рассмотрено, как идентифицировать изменчивые управляемые словари, как отделять их от основной схемы, как развязывать версии и как проверять на допустимость схемы словарей. Разумеется, абсолютного рецепта - когда у управляемого словаря должна быть своя схема - нет. Поэтому применяя приведенные в этой статье рекомендации, всегда полагайтесь на здравый смысл и знание проблемной области.
Файл с примерами, приведенными в этой статье можно скачать в виде ZIP-архива (9 кБ).