Вопрос или проблема
У меня есть следующий XML:
<?xml version="1.0"?>
<x:books xmlns:x="urn:collection" xmlns:p="urn:property">
<book id="bk001">
<p:author>Автор</p:author>
<p:title>Первая книга</p:title>
<p:genre>Художественная литература</p:genre>
</book>
</x:books>
который разбирается с помощью парсера, не учитывающего пространства имен:
DocumentBuilderFactory factory = DocumentBuilderFactory.newDefaultInstance(); // реализация по умолчанию JDK
factory.setNamespaceAware(false);
DocumentBuilder builder = factory.newDocumentBuilder();
Document dom = builder.parse(new InputSource(new StringReader(xmlString)));
Когда он преобразуется обратно в строку с использованием преобразователя идентичности по умолчанию…
TransformerFactory factory = TransformerFactory.newDefaultInstance(); // реализация по умолчанию JDK
Transformer tr0 = factory.newTransformer();
Writer w0 = new StringWriter();
tr0.transform(new DOMSource(dom), new StreamResult(w0));
…я получаю первоначальный XML без изменений (со всеми префиксами пространств имен x:
и p:
сохраненными).
Однако, когда я пытаюсь использовать преобразование XSLT идентичности для преобразования его…
<transform version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform">
<output method="xml" encoding="UTF-8" indent="no" omit-xml-declaration="no" />
<template match="*|@*|node()">
<copy><apply-templates select="*|@*|node()" /></copy>
</template>
</transform>
Transformer tr1 = factory.newTransformer(new StreamSource(new StringReader(xsltString)));
Writer w1 = new StringWriter();
tr1.transform(new DOMSource(dom), new StreamResult(w1));
… я получаю следующий XML (со всеми префиксами пространств имен удаленными):
<?xml version="1.0" encoding="UTF-8"?><books xmlns:p="urn:property" xmlns:x="urn:collection">
<book id="bk001">
<author>Автор</author>
<title>Первая книга</title>
<genre>Художественная литература</genre>
</book>
</books>
Учитывая, что в моей кодовой базе я не могу контролировать, как создается DOM (он может быть с учетом или без учета пространств имен, последний в большинстве случаев), как я могу написать XSLT 1.0 преобразование, которое всегда будет сохранять все префиксы пространств имен XML?
(Преобразование идентичности – это пример, я хотел бы написать более сложные преобразования в будущем)
Существует ли какое-либо свойство, которое я могу установить в TransformerFactory, или какие-либо изменения в атрибуте XSLT match="..."
, которые я могу сделать для достижения этого?
Ответ или решение
Различие между поведением стандартного трансформатора и трансформацией по идентичности XSLT
При работе с XML-документами, содержащими пространства имен, различия в поведении стандартного трансформатора и XSLT трансформации по идентичности могут вызывать недоразумения, особенно когда дело касается сохранения префиксов пространств имен. Рассмотрим конкретный случай, чтобы прояснить данную ситуацию и рассмотреть способы сохранения префиксов пространств имен с помощью XSLT.
Исходные данные
У вас есть следующий XML-документ:
<?xml version="1.0"?>
<x:books xmlns:x="urn:collection" xmlns:p="urn:property">
<book id="bk001">
<p:author>Writer</p:author>
<p:title>The First Book</p:title>
<p:genre>Fiction</p:genre>
</book>
</x:books>
Этот документ разбирается с помощью парсера, не учитывающего пространства имен:
DocumentBuilderFactory factory = DocumentBuilderFactory.newDefaultInstance();
factory.setNamespaceAware(false);
DocumentBuilder builder = factory.newDocumentBuilder();
Document dom = builder.parse(new InputSource(new StringReader(xmlString)));
Примеры трансформации
Когда вы используете стандартный идентичный трансформатор:
TransformerFactory factory = TransformerFactory.newDefaultInstance();
Transformer tr0 = factory.newTransformer();
Writer w0 = new StringWriter();
tr0.transform(new DOMSource(dom), new StreamResult(w0));
Результат
В этом случае вы получаете оригинальный XML с сохраненными префиксами пространства имен:
<?xml version="1.0"?>
<x:books xmlns:x="urn:collection" xmlns:p="urn:property">
<book id="bk001">
<p:author>Writer</p:author>
<p:title>The First Book</p:title>
<p:genre>Fiction</p:genre>
</book>
</x:books>
Однако, если вы используете XSLT для идентичной трансформации:
<transform version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform">
<output method="xml" encoding="UTF-8" indent="no" omit-xml-declaration="no" />
<template match="*|@*|node()">
<copy><apply-templates select="*|@*|node()" /></copy>
</template>
</transform>
При этом результирующий XML будет выглядеть следующим образом (с удаленными префиксами):
<?xml version="1.0" encoding="UTF-8"?>
<books xmlns:p="urn:property" xmlns:x="urn:collection">
<book id="bk001">
<author>Writer</author>
<title>The First Book</title>
<genre>Fiction</genre>
</book>
</books>
Причины различий
-
Непонимание пространств имен: Стандартный трансформатор не учитывает пространство имен при сериализации, тогда как XSLT обрабатывает элементы на основе их имени, что может привести к потере префиксов.
-
Однообразие пространства имен: В XSLT при создании новых элементов происходит использование "пустого" пространства имен, что и приводит к удалению префиксов.
Решение проблемы
Для того чтобы сохранить префиксы пространств имен в XSLT трансформации, необходимо явно определить их в вашем XSLT. Возможное решение заключается в добавлении пространств имен, как показано ниже:
<transform version="1.0"
xmlns="http://www.w3.org/1999/XSL/Transform"
xmlns:x="urn:collection"
xmlns:p="urn:property">
<output method="xml" encoding="UTF-8" indent="no" omit-xml-declaration="no" />
<template match="*|@*|node()">
<copy>
<xsl:copy-of select="namespace::*"/>
<apply-templates select="*|@*|node()" />
</copy>
</template>
</transform>
В этом случае, добавляя xsl:copy-of select="namespace::*"
, мы сохраняем все пространства имен, включая префиксы.
Заключение
В результате применения указанных методов вы сможете успешно сохранять все префиксы пространств имен при использовании XSLT для более сложных трансформаций. Это важно как с точки зрения интеграции данных, так и для обеспечения совместимости с другими системами, которые могут зависеть от структуры и формата XML.