У меня есть Java REST API, и мы недавно сменили домен. API версируется, хотя до сих пор это включало добавление элементов в разные версии.
Я хотел бы изменить пространства имен, если кто-то вернется к предыдущим версиям, но я борюсь. Теперь, после некоторого взлома, я понял, что это, вероятно, связано с тем, что я меняю пространство имен xml, на которое фактически ссылаются. Я думал об этом как о текстовом документе, но я полагаю, что это не инструмент?
Итак, глядя на этот xml с URL-адресом пространства имен n # veg.com ->
<?xml version = "1.0" encoding = "UTF-8" standalone = "no"?>
<ns2:apple xmlns:ns2 = "http://veg.com/app/api/apple" xmlns:ns1 = "http://veg.com/app/api" xmlns:ns3 = "http://veg.com/app/api/apple/red"
xmlns:ns4 = "http://veg.com/app/banana" xmlns:ns5 = "http://veg.com/app/api/pear" xmlns:ns6 = "http://veg.com/app/api/orange"
ns1:created = "2016-05-23T16:47:55+01:00" ns1:href = "http://falseserver:8080/app/api/apple/1" ns1:id = "1">
<ns2:name>granny smith</ns2:title>
<ns2:flavour>sweet</ns2:status>
<ns2:origin>southwest region</ns2:grantCategory>
...
</ns2:apple>
Я хотел бы изменить пространства имен на fruit.com. Это очень хакерский модульный тест, который показывает широкий подход, который я пробовал ...
@Test
public void testNamespaceChange() throws Exception {
Document appleDoc = load("apple.xml");
XPath xpath = XPathFactory.newInstance().newXPath();
org.w3c.dom.Node node = (org.w3c.dom.Node) xpath.evaluate("//*[local-name()='apple']", appleDoc , XPathConstants.NODE);
NamedNodeMap nodeMap = node.getAttributes();
for (int i = 0; i < nodeMap.getLength(); i++) {
if (nodeMap.item(i).getNodeName().startsWith("xmlns:ns")) {
nodeMap.item(i).setTextContent( nodeMap.item(i).getNodeValue().replace( "veg.com", "fruit.com"));
}
}
//Check values have been set
for (int i = 0; i < nodeMap.getLength(); i++) {
System.out.println(nodeMap.item(i).getNodeName());
System.out.println(nodeMap.item(i).getNodeValue());
System.out.println("----------------");
}
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.transform(new DOMSource(node), result);
System.out.println("XML IN String format is: \n" +
writer.toString());
}
Таким образом, в результате цикл элементов nodeMap показывает, что обновления выполняются.
т.е. все обновлено в соответствии с этими строками
xmlns:ns1
http://fruit.com/app/api
-------------------------------------------
xmlns:ns2
http://fruit.com/app/api/apple
-------------------------------------------
xmlns:ns3
http://fruit.com/app/api/apple/red
-------------------------------------------
...
но когда я распечатываю преобразованный документ, я получаю то, что вижу в ответе api ...
<?xml version = "1.0" encoding = "UTF-8" standalone = "no"?>
<ns2:apple xmlns:ns2 = "http://veg.com/app/api/apple" xmlns:ns1 = "http://veg.com/app/api" xmlns:ns3 = "http://fruit.com/app/api/apple/red"
xmlns:ns4 = "http://fruit.com/app/banana" xmlns:ns5 = "http://fruit.com/app/api/pear" xmlns:ns6 = "http://fruit.com/app/api/orange"
ns1:created = "2016-05-23T16:47:55+01:00" ns1:href = "http://falseserver:8080/app/api/apple/1" ns1:id = "1">
Близкие (и расположенные ниже по иерархии) пространства имен были изменены, но ns1 и ns2 остались неизменными.
Может ли кто-нибудь сказать мне, почему и есть ли у меня простой способ обновить их? Я предполагаю, что следующим шагом для меня может быть потоковая передача xml-документа в строку, обновление их как текста, а затем перезагрузка как xml-документ, но я надеюсь, что я поражен, и есть более элегантное решение?
На самом деле все, что я хочу сделать, это обновить атрибуты пространства имен в основном узле. Итак, я хочу обновить содержимое атрибутов xmlns: ns1, xmlns: ns2, xmlns: ns3 и т. д. (Измените URL-адрес на наш новый домен). Мне потребовалось довольно много времени, чтобы понять, что это какой-то код блокирует это изменение (org.w3c.dom или javax.xml.transform)?
Проблема в том, что вы подходите к этому с синтаксической точки зрения. Хотя это может сработать в тех же случаях, логически видно, что пространства имен - это не просто некоторые атрибуты xmlns:someprefix, разбросанные вокруг. Я думаю, что самый простой правильный способ решить эту проблему - это преобразование XSLT, которое переназначает определенные пространства имен.




Я бы решил это с помощью XSLT следующим образом:
<xsl:stylesheet version = "1.0" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform">
<xsl:template match = "*[namespace-uri()='http://veg.com/app/api/apple']" priority = "1">
<xsl:element name = "local-name()" namespace = "http://fruit.com/app/api/apple">
<xsl:apply-templates select = "@*|node()"/>
</xsl:element>
</xsl:template>
<xsl:template match = "@*|node()">
<xsl:copy>
<xsl:apply-templates select = "@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Эта таблица стилей объединяет преобразование идентичности с шаблоном, который изменяет пространство имен элементов в http://veg.com/app/api/apple на http://fruit.com/app/api/apple.
Я думаю, что это намного проще того Java-кода, который у вас есть. Вы также будете более гибкими, если обнаружите, что у вас больше различий между версиями вашего XML, кроме пространств имен.
Считайте, что это набросок. Я написал книгу по XSLT около 15 лет назад, но не использовал XSLT более 6 или 7 лет.
Хорошо, спасибо, я думаю, ты прав. Я немного борюсь с синтаксисом, но я потрачу день, пытаясь изучить xslt, и, если необходимо, опубликую еще один вопрос, когда я сделаю респектабельную попытку.
Я решил проблему с этим. Единственное изменение, которое мне пришлось сделать, это то, что имя элемента нужно было заключить в фигурные скобки для поддержки его вычислений. Пример: <xsl:element name = "{local-name()}" namespace = "http://fruit.com/app/api/apple">
Что именно вы имеете в виду под «изменением пространства имен»? Вы хотите преобразовать XML-документ в «старом» пространстве имен в XML-документ в «новом» пространстве имен?