В предыдущей статье, посвящённой Рекомендации W3C "Канонический XML" (Canonical XML), рассказывалось, почему и когда необходимо канонизировать XML-файлы. Кроме того, в ней последовательно — шаг за шагом — был показан процесс получения канонической формы XML-документа.
Во второй и завершающей статье данного цикла мы продолжим изучение этой концепции и рассмотрим требования, предъявляемые к разделам CDATA, инструкциям обработки (processing instruction), комментариям, ссылкам на внешние сущности (external entity) и подмножествам XML-документов.
Начнём с примера. Листинг 1 является XML-файлом, который содержит, помимо всего прочего, раздел CDATA, комментарии, инструкцию по обработке и ссылку на внешнюю сущность. Тринадцати шагов канонизации, о которых шла речь в первой статье, уже недостаточно для того, чтобы канонизировать этот документ. Нам потребуется выполнить ещё несколько дополнительных действий.
Каноническая форма требует, чтобы все разделы CDATA были заменены своим эквивалентным XML-содержимым PCDATA. Именно это и сделано в Листинге 2. Если вы сравните эти два листинга, то обнаружите, что разметка для раздела CDATA ("<![CDATA[" в начале и "]]>" в конце) была удалена, а вместо символа "<" в разделе CDATA Листинга 1 в Листинге 2 была добавлена эквивалентная управляющая последовательность (<).
Согласно этому требованию, необходимо нормализовать свободные места внутри инструкций обработки. Это означает, что всё свободное место между объектом (target) и его данными должно быть сокращено до единственного пробела (символ #x20).
В XML-файле Листинга 2 присутствует только одна инструкция обработки. Объект в этой инструкции - xml-stylesheet, за которым следует строка данных. Листинг 3 отличается от Листинга 2 только тем, что всё свободное место между объектом и его данными было нормализовано.
Вспомните соответствующий раздел первой статьи, в котором было показано, как канонизировать ссылки на разобранные внутренние сущности. Аналогичным образом ссылки на разобранные внешние сущности заменяются содержимым, на которое они ссылаются (см. Листинг 4).
Спецификация "Канонический XML" позволяет как сохранять, так и удалять комментарии из XML-файла. Процессор канонизации XML будет получать вместе с XML-файлом, подлежащим канонизации, булевский параметр (признак), с помощью которого он сможет определить включать или не включать XML-комментарии в каноническую форму.
Например, в Листинге 5 удалены комментарии, присутствующие в Листинге 4 (каноническая форма без комментариев).
Теперь к Листингу 5 можно применить все тринадцать операций (описанных в первой статье). Результат этого процесса приведён в Листинге 6.
Подмножества или фрагменты (части целых XML-файлов) XML-документов представляют очень интересный случай. Когда мы извлекаем из XML-файла некоторую его часть, мы неизбежно отделяем узел потомок (child node) от его родителя (назовём его "осиротевший" узел [orphan node]). Это отделение может привести к недопустимости контекста пространства имён потомка, если контекст пространства имён "осиротевшего" потомка был объявлен в родителе, который не был включён в подмножество документа.
Спецификация "Канонический XML" предусматривает метод, позволяющий сохранить контекст пространств имён при извлечении подмножества документа. Однако, существуют такие сценарии, в которых сохранение контекста пространств имён может приводить к ряду проблем. Поэтому консорциум W3C издал отдельную рекомендацию - "Исключающая канонизация XML" (Exclusive XML Canonicalization) - которая предназначена для управления такими сценариями.
Единственное различие между спецификациями "Канонический XML" и "Исключающая канонизация XML" состоит в том, что предшествующий контекст (ancestor context) либо сохраняется, либо удаляется.
Рассмотрим Листинг 7, который является сообщением SOAP. Предположим, что нам необходимо канонизировать элемент booking, значение атрибута unitCharge которого равно 50. Первым шагом будет написание выражения XPath, которое будет извлекать требуемый фрагмент документа из XML-файла:
(//. | //@* | //namespace::*) [ancestor-or-self::booking[@unitCharge="50"]]
Приведённое выражение XPath извлечёт требуемый элемент из XML-файла, представленного в Листинг 7. Часть выражения, заключённая в первые скобки (//. | //@* | //namespace::*), отбирает все элементы, атрибуты и узлы пространств имён XML-файла. Выражение во внешних квадратных скобках (ancestor-or-self::booking) выбирает все элементы booking (вместе с их потомками); часть выражения, заключённая во внутренние квадратные скобки (@unitCharge="50"), отбирает элемент booking, значение атрибута unitCharge которого равно 50.
Листинг 8 является подмножеством Листинга 7 и включает элемент booking. Возможно, часть читателей испытает искушение попробовать канонизировать Листинг 8, применив к нему последовательность из тринадцати шагов, описанных в первой статье. Однако, прежде чем сделать это, необходимо выполнить дополнительную обработку, чтобы устранить пару проблем:
Объявления пространств имён для префиксов bs и hs были выполнены в родительском теге (элемента booking), который не вошёл в подмножество документа Листинга 8.
В Листинге 7 атрибут xml:lang элемента bookingPackage применим для всех своих потомков. Этот атрибут также отсутствует в подмножестве документа Листинга 8.
Указанные сложности свидетельствуют о том, что при извлечении фрагментов документа необходимо придерживаться некоторых правил, которые позволят сохранить контекст пространства имён и смысл атрибутов из пространства имён xml:. Согласно спецификации "Канонический XML", при канонизации подмножеств документа необходимо выполнить следующие требования (помимо всех остальных, касающихся канонизации целых XML-файлов):
Объявления пространств имён в пропущенных предках подмножеств документов должны быть включены в каноническую форму.
Атрибуты в пространстве имён также включаются в каноническую форму, если только они уже не присутствуют в канонизируемом фрагменте.
Эти два положения предназначены для сохранения предшествующего контекста подмножества документа. Так, в Листинг 9 вошли четыре объявления пространств имён, выполненых в предках элемента booking в Листинге 7. Листинг 9 также содержит атрибут xml:lang. Обратите внимание, что каноническая форма подмножества документа не включает ни одного символа перевода строки (#xA), то есть весь файл - это одна строка.
После добавления предшествующего контекста порядок объявлений пространств имён и атрибутов должен быть таким же, как при канонизации целого XML-файла.
Итак, мы рассмотрели, как включать предшествующий контекст при канонизации подмножеств XML-документов. Однако, при определённых обстоятельствах это может привести к ряду проблем. Чтобы детально разобраться в сценариях, при которых включение предшествующего контекста чревато возникновением проблем, рассмотрим сначала данных в конверт (enveloping) - концепцию, которая имеет первостепенное значение для обеспечения возможности взаимодействия Web-служб.
Технология SOAP стремительно превращается в фактический стандарт передачи XML-сообщений через Интернет. SOAP определяет формат для заключения XML-данных в конверты.
В Листинге 7 элемент PackageBooking "завёрнут" внутри элемента SOAP:Body. Листинг 7 демонстрирует простой механизм упаковки данных в конверт, при котором полезная нагрузка сообщения (то есть сообщение, которое необходимо отправить через Интернет) заворачивается в элемент SOAP:Body, а сам элемент SOAP:Body целиком заключается в элемент SOAP:Envelope.
Преимущество такой простой упаковки данных состоит в том, что она позволяют активировать вертикальное вложение (vertical stacking) протоколов, основанных на XML. Вертикальное вложение означает, что для определённых низкоуровневых задач (таких как подписание, шифрование, маршрутизация и так далее) могут быть определены протоколы и форматы сообщения; уровни более высоких протоколов будут использовать сервис, предлагаемый более низкими уровнями. Например, WS-Security, высокоуровневый протокол безопасности XML, разрабатываемый Техническим комитетом консорциума OASIS, использует протокол SOAP для реализации механизмов подписи и шифрования, поддерживаемых спецификациями консорциума W3C "Электронная подпись XML" (XML Digital Signature) и "Шифрование XML" (XML Encryption), соответственно.
Помимо элемента SOAP:Body Листинг 7 также включает элемент SOAP:Header. Этот элемент SOAP:Header является необязательным и предназначен для информации, касающейся протокола. Это фактически означает, что полезная нагрузка сообщения находится внутри элементов SOAP:Body, а заголовки протокола располагаются в элементе SOAP:Header. Например, WS-Security использует SOAP Header, чтобы упаковать информацию о подписи.
Приложение, которое получит SOAP-сообщение, вероятно, "разорвёт" конверт (обёртку) и извлечёт полезную нагрузку XML (XML-сообщение), чтобы обработать полученное сообщение. Этот разрыв конверта SOAP и извлечение полезной нагрузки XML называется распечатывание конверта (de-enveloping). Кроме того, получающему приложению, возможно, потребуется заново упаковать в конверт (re-envelope) XML-сообщение, полученное в новом конверте.
Потребность в повторной упаковке в конверт может возникнуть в объединённых Web-службах, в которых часть работы выполняется партнёрскими приложениями - это могут быть службы, которые интегрируют слабосвязанные системы.
В качестве примера объединённых приложений давайте рассмотрим сценарий функционирования туристической компании, в которой работа может осуществляться по схеме "бизнес-бизнес" (B2B). Так, клиент этой турфирмы может захотеть получить подробную информацию о туре, который предлагает Web-служба данного туроператора. Турист посылает XML-сообщение, в котором содержится информация о тех местах, которые он хотел бы посетить, а также даты возможного отъезда.
Разумеется, это XML-сообщение будет составлено каким-нибудь поддерживающим XML и SOAP клиентским приложением, которое запишет и упакует всю информацию внутри конверта SOAP - клиент же при этом может даже и не подозревать о существовании XML и SOAP.
При получении этого SOAP-сообщения Web-служба туристической фирмы извлечёт информацию о месте и дате отдыха из конверта SOAP. Далее, этой службе потребуется разослать части этой информации в различные отели, сотрудничающие с турфирмой, и компании, занимающиеся сдачей машин в аренду. Следовательно, Web-служба туропратора составит новые конверты SOAP, которые будут содержать соответствующие части информации, и отправит их в указанные отели и компании.
Подобным образом при получении ответа из этих отелей и бюро проката Web-служба заново упакует в конверт полученную информацию, а затем отошлёт этот новый конверт своему клиенту.
Перейдём теперь к рассмотрению Листинга 10, который является SOAP-сообщением, полученным из вымышленного отеля в ответ на запрос от Web-службы туроператора.
Помимо этого сообщения, туристическая фирма также получает ответы и из других отелей и бюро проката. Поэтому для получения окончательного варианта тура необходимо объединить все эти сообщения.
Листинг 7 фактически является тем вариантом тура, который будет отправлен клиенту туристической компании. Первый элемент booking в Листинге 7 (значение атрибута unitCharge которого равно 50 и который мы, канонизировав, преобразовали в Листинг 9) точно такой же , как элемент booking в Листинге 10.
Чтобы показать значимость канонизации с точки зрения применения в объединённых Web-службах, давайте предположим, что, отправляя SOAP-сообщение туроператору, работники отеля решат подписать элемент booking в Листинге 10 с тем, чтобы турист мог убедиться, что процесс резервирования номера в гостинице (booking) действительно имеет место.
В Листинге 11 приведена каноническая форма элемента booking Листинга 10. Это та каноническая форма, которой воспользуются работники отеля для того, чтобы создать профиль сообщения. С другой стороны, вспомните о том, что Листинг 9 являлся канонической формой того же элемента booking, когда он был частью Листинга 7. Вот почему клиент турфирмы воспользуется Листингом 9, чтобы проверить профиль сообщения, полученный из отеля.
Если вы сравните Листинги 11 и 9 , вы заметите, что они отличаются друг от друга. Причина этого различия заключается в том, что, канонизируя один и тот же элемент booking, мы сохранили предшествующий контекст из двух различных XML-документов.
Поэтому клиентское приложение (клиента турфирмы) не сможет проверить подпись и профиль сообщения. Это является исчерпывающим доказательством того, что при реализации концепции канонизации в случае объединённых Web-служб необходимо исключать предшествующий контекст. Именно с этой целью и была опубликована рекомендация "Исключающая канонизация XML".
Исключающая канонизация применяется только при канонизации фрагментов XML-файлов; ниже приведены её основных отличия от рекомендации "Канонический XML":
Атрибуты из пространства имён не импортируются из предков в "сиротские узлы".
Пропущенные объявления пространств имён включается в исключающей канонической форме в элемент только в том случае если:
объявление пространства имён используется этим элементом или любыми атрибутами или атрибутами его потомков;
объявление пространства имён уже используется в исключающей канонической форме.
Обратите внимание на то, что второе правило относится также и к объявлениям пустых используемых по умолчанию пространств имён (xmlns=""). Это означает, что исключающая каноническая форма элемента будет включать объявления xmlns="", если этот элемент принадлежит к пустому используемому по умолчанию пространству имён, а в объявлении используемого по умолчанию пространства имён для ближайшего предшественника в исключающей канонической форме присутствует непустое используемое по умолчанию пространство имён (xmlns="http://someURI...").
Применив эти правила к элементу Листинга 10, можно получить исключающую каноническую форму, представленную в Листинге 12. Обратите внимание на то, что исключающая каноническая форма первого элемента Листинга 7 (значение атрибута unitCharge равно 50) точно такая же, как в Листинге 12.
Следует заметить, что использование спецификации "Канонический XML" может привести к возникновению следующих двух проблем:
Как отмечалось выше - в разделе "Ссылки на внешние сущности", если канонизируемый XML-файл содержит ссылки на внешние разобранные сущности, то в процессе канонизации ссылки на внешние сущности будут заменены их содержимым. Однако, если содержимое включает некоторые относительные URIs, то после такой замены эти URIs могут стать нерабочими (поскольку во время канонизации объявления DTD удаляются, и по её завершении внешнее содержимое окажется недоступным).Эти нерабочие URIs могут создать сложности при работе с подписью, поскольку невозможно установить, что предназначались для подписания: первоначальный рабочий XML-документ или его нерабочая каноническая форма. Поэтому, если складывается впечатление, что функционирование приложений, используемых для подписания, может быть нарушено такой неясностью, подобный сценарий должен быть разрешён до начала канонизации (например, относительные URIs следует преобразовать к абсолютным URIs).
Возможно, существуют некоторые специфические случаи применения, которые не могут быть описаны в обобщённой спецификации. Например, каноническая форма XML-файла, содержащего счёт-фактуру с ценами, указанными в франках, будет отлична от канонической формы того же счета-фактуры с ценами в евро (хотя обе накладные являются логическим эквивалентом). Следовательно, в таких случаях подобные вопросы должны быть разрешены с учётом рассматриваемой специфики.
Познакомьтесь с первой статьёй.
Официальная спецификация Exclusive XML Canonicalization доступна на сайте консорциума W3C.
В этой статье упоминалось о SOAP и WS-Security. Кто интересуется этими вопросами, может скачать и прочитать официальные спецификации, чтобы понять, как WS-Security Электронную подпись XML упаковывает в заголовки SOAP.
На этой странице приведены ссылки на некоторые реализации спецификации "Канонический XML"
В статье "Используя XPath для беспроводных устройств" (Implementing XPath for Wireless Devices), написанной Билал Сиддикуи, рассказывается о синтаксисе XPath и о том, как его можно использовать этот язык.