2013-06-12 1 views
3

Я начинаю использовать XSLT, чтобы временно поддерживать текущую версию нашего веб-сервиса 1.0, в то время как клиенты переходят на 1.1, конвертируя старые вызовы в новый формат.Выход XSLT является подробным и беспорядочным

Для таких изменений мне нужно изменить пространство имен, включить узел и переименовать другое. Я новичок в XSLT, но после некоторых поисковых запросов я придумал рабочее решение, однако его работа и выход выглядят беспорядочно, и я не уверен, насколько это безопасно. Я бы хотел несколько советов по его усовершенствованию, что сделало его более чистым и более удобным (версия 1.2 потребует больше преобразования).

Пример ввода XML (мой клиент SOAP использует квалифицированные элементы):

<ns10:testRequest xmlns:ns10="namespace/1.0"> 
    <ns10:a> 
     <ns10:b1> 
      <ns10:c>cccc</ns10:c> 
      <ns10:d>dddd</ns10:d> 
      <ns10:e>eeee</ns10:e> 
     </ns10:b1> 
     <ns10:b2 attr="value"> 
      <ns10:f>false</ns10:f> 
      <ns10:g>2014-03-01</ns10:g> 
      <ns10:h>true</ns10:h> 
     </ns10:b2> 
     <ns10:b3> 
     </ns10:b3> 
    </ns10:a> 
    </ns10:testRequest> 

XSLT (с соответствующими комментариями):

<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:oldns="namespace/1.0" 
    xmlns:newns="namespace/1.1" 
    version="1.0"> 

    <xsl:param name="newnsParam">namespace/1.1</xsl:param> 

    <!-- copy all document --> 
    <xsl:template match="@*|node()"> 
     <xsl:copy> 
      <xsl:apply-templates select="@*|node()" /> 
     </xsl:copy> 
    </xsl:template> 

    <!-- rename 'g' to 'newName' --> 
    <xsl:template match="/oldns:testRequest/oldns:a/oldns:b1/oldns:g"> 
     <!-- if I don't set it with newns now, it will remain as oldns even after namespace change below --> 
     <xsl:element name="newName" namespace="{$newnsParam}"> 
      <xsl:apply-templates select="@*|node()" /> 
     </xsl:element> 
    </xsl:template> 

    <!-- add 'newElement' --> 
    <xsl:template match="/oldns:testRequest/oldns:a/oldns:b1"> 
     <!-- same as above, must set as newns now, and this one won't be qualified (why?) --> 
     <xsl:element name="b1" namespace="{$newnsParam}"> 
      <xsl:apply-templates select="@*|node()" /> 
      <xsl:element name="newElement" namespace="{$newnsParam}">NEW_VAL</xsl:element> 
     </xsl:element> 
    </xsl:template> 

    <!-- change namespace, but it makes every node redefine the namespace --> 
    <xsl:template match="@oldns:*"> 
     <xsl:attribute name="newns:{local-name()}" namespace="{$newnsParam}"> 
      <xsl:value-of select="."/> 
     </xsl:attribute> 
    </xsl:template> 
    <xsl:template match="oldns:*"> 
     <xsl:element name="newns:{local-name()}" namespace="{$newnsParam}"> 
      <xsl:apply-templates select="node()|@*"/> 
     </xsl:element> 
    </xsl:template> 

</xsl:stylesheet> 

Выходной XML:

<newns:testRequest xmlns:newns="namespace/1.1"> 
    <newns:a xmlns:newns="namespace/1.1"> 
     <newns:b1 xmlns:newns="namespace/1.1"> 
      <newns:c xmlns:newns="namespace/1.1">cccc</newns:c> 
      <newns:d xmlns:newns="namespace/1.1">dddd</newns:d> 
      <newns:e xmlns:newns="namespace/1.1">eeee</newns:e> 
     </newns:b1> 
     <newns:b2 xmlns="namespace/1.1" attr="value" xmlns:newns="namespace/1.1"> 
      <newns:f xmlns="namespace/1.1" xmlns:newns="namespace/1.1">false</newns:f> 
      <newns:newName xmlns="namespace/1.1" xmlns:newns="namespace/1.1">2014-03-01</newns:newName> 
      <newns:h xmlns="namespace/1.1" xmlns:newns="namespace/1.1">true</newns:h> 
      <newElement xmlns="namespace/1.1" xmlns:newns="namespace/1.1">NEW_VAL</newElement> 
     </newns:b2> 
     <newns:b3 xmlns:newns="namespace/1.1"> 
     </newns:b3> 
    </newns:a> 
    </newns:testRequest> 

Как видно, это довольно запутанно со всеми этими ненужными определениями пространства имен, плюс «newElement» странно неквалифицирован. Мы регистрируем вызовы для целей отладки и аудита, и такая подробность нежелательна.

Ожидаемый XML (или нечто подобное):

<newns:testRequest xmlns:newns="namespace/1.1"> 
    <newns:a> 
     <newns:b1> 
      <newns:c>cccc</newns:c> 
      <newns:d>dddd</newns:d> 
      <newns:e>eeee</newns:e> 
     </newns:b1> 
     <newns:b2 attr="value"> 
      <newns:f>false</newns:f> 
      <newns:newName>2014-03-01</newns:newName> 
      <newns:h>true</newns:h> 
      <newns:newElement>NEW_VAL</newns:newElement> 
     </newns:b2> 
     <newns:b3> 
     </newns:b3> 
    </newns:a> 
    </newns:testRequest> 

Важно, что XSLT быть надежными для обработки различных входных XML, как квалифицированных и неквалифицированных элементов (он принимает и с текущей XSLT).

Любая помощь приветствуется.

Окружающая среда: Java 1.6.0_14, Spring 3.1.3 веб-приложений с Spring WS 2.1.0, 2.2.6 JAXB и внутренней JRE Apache Xalan


EDIT 1: Предложение hr_117 дал это исключение :

javax.xml.transform.TransformerException: java.lang.RuntimeException: Namespace for prefix 'newns' has not been declared. 
    at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:717) ~[na:1.6.0_14] 
    at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:313) ~[na:1.6.0_14] 
    at org.springframework.ws.server.endpoint.interceptor.PayloadTransformingInterceptor.transformMessage(PayloadTransformingInterceptor.java:118) ~[spring-ws-core-2.1.0.RELEASE.jar:na] 
    at org.springframework.ws.server.endpoint.interceptor.PayloadTransformingInterceptor.handleRequest(PayloadTransformingInterceptor.java:92) ~[spring-ws-core-2.1.0.RELEASE.jar:na] 
    at org.springframework.ws.server.endpoint.interceptor.DelegatingSmartEndpointInterceptor.handleRequest(DelegatingSmartEndpointInterceptor.java:78) ~[spring-ws-core-2.1.0.RELEASE.jar:na] 
    at org.springframework.ws.server.MessageDispatcher.dispatch(MessageDispatcher.java:224) [spring-ws-core-2.1.0.RELEASE.jar:na] 
    at org.springframework.ws.server.MessageDispatcher.receive(MessageDispatcher.java:173) [spring-ws-core-2.1.0.RELEASE.jar:na] 
    at org.springframework.ws.transport.support.WebServiceMessageReceiverObjectSupport.handleConnection(WebServiceMessageReceiverObjectSupport.java:88) [spring-ws-core-2.1.0.RELEASE.jar:na] 
    at org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter.handle(WebServiceMessageReceiverHandlerAdapter.java:59) [spring-ws-core-2.1.0.RELEASE.jar:na] 
    at org.springframework.ws.transport.http.MessageDispatcherServlet.doService(MessageDispatcherServlet.java:221) [spring-ws-core-2.1.0.RELEASE.jar:na] 
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882) [org.springframework.web.servlet-3.1.3.RELEASE.jar:3.1.3.RELEASE] 
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:789) [org.springframework.web.servlet-3.1.3.RELEASE.jar:3.1.3.RELEASE] 
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:727) [javax.servlet_1.0.0.0_2-5.jar:2.5] 
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:820) [javax.servlet_1.0.0.0_2-5.jar:2.5] 
    at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:227) [weblogic.jar:10.3.2.0] 
    at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:125) [weblogic.jar:10.3.2.0] 
    at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:292) [weblogic.jar:10.3.2.0] 
    at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:175) [weblogic.jar:10.3.2.0] 
    at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.run(WebAppServletContext.java:3591) [weblogic.jar:10.3.2.0] 
    at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321) [com.bea.core.weblogic.security.identity_1.1.2.0.jar:1.1.2.0] 
    at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:121) [com.bea.core.weblogic.security.wls_1.0.0.0_5-2-0-0.jar:5.2.0.0] 
    at weblogic.servlet.internal.WebAppServletContext.securedExecute(WebAppServletContext.java:2202) [weblogic.jar:10.3.2.0] 
    at weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:2108) [weblogic.jar:10.3.2.0] 
    at weblogic.servlet.internal.ServletRequestImpl.run(ServletRequestImpl.java:1432) [weblogic.jar:10.3.2.0] 
    at weblogic.work.ExecuteThread.execute(ExecuteThread.java:201) [com.bea.core.weblogic.workmanager_1.7.0.0.jar:1.7.0.0] 
    at weblogic.work.ExecuteThread.run(ExecuteThread.java:173) [com.bea.core.weblogic.workmanager_1.7.0.0.jar:1.7.0.0] 
Caused by: java.lang.RuntimeException: Namespace for prefix 'newns' has not been declared. 
    at com.sun.org.apache.xalan.internal.xsltc.runtime.BasisLibrary.runTimeError(BasisLibrary.java:1518) ~[na:1.6.0_14] 
    at com.sun.org.apache.xalan.internal.xsltc.runtime.BasisLibrary.runTimeError(BasisLibrary.java:1522) ~[na:1.6.0_14] 
    at com.sun.org.apache.xalan.internal.xsltc.runtime.BasisLibrary.startXslElement(BasisLibrary.java:1408) ~[na:1.6.0_14] 
    at transformation_1_0_to_1_1.template$dot$4() ~[na:na] 
    at transformation_1_0_to_1_1.applyTemplates() ~[na:na] 
    at transformation_1_0_to_1_1.applyTemplates() ~[na:na] 
    at transformation_1_0_to_1_1.transform() ~[na:na] 
    at com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet.transform(AbstractTranslet.java:602) ~[na:1.6.0_14] 
    at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:710) ~[na:1.6.0_14] 
    ... 25 common frames omitted 

Эдир 2: в качестве теста, я заменил Xalan с Saxon, а выход был п как Майкл. Тем не менее, это большой барабан для нашего уже большого приложения, плюс он противоречил другим частям нашего приложения, поэтому он скорее является фактором нестабильности. Я хотел бы просто сосредоточиться на Xalan и заставить его работать правильно.

ответ

2

Я очень удивлен, что Xalan должны быть производить все эти избыточные объявления пространств имен. Вот выход Саксон для сравнения:

<?xml version="1.0" encoding="UTF-8"?><newns:testRequest xmlns:newns="namespace/1.1"> 
    <newns:a> 
     <b1 xmlns="namespace/1.1"> 
      <newns:c>cccc</newns:c> 
      <newns:d>dddd</newns:d> 
      <newns:e>eeee</newns:e> 
     <newElement>NEW_VAL</newElement></b1> 
     <newns:b2 attr="value"> 
      <newns:f>false</newns:f> 
      <newns:g>2014-03-01</newns:g> 
      <newns:h>true</newns:h> 
     </newns:b2> 
     <newns:b3> 
     </newns:b3> 
    </newns:a> 
    </newns:testRequest> 

Если вы хотите, чтобы доминирующие newns пространства имен будут пространством имен по умолчанию, чтобы дополнительно уменьшить беспорядок, вы можете изменить <xsl:copy> к <xsl:element name="{local-name()}" namespace="{namespace-uri()}">. Это дает выход:

<?xml version="1.0" encoding="UTF-8"?> 
<testRequest xmlns="namespace/1.1"> 
    <a> 
     <b1> 
      <c>cccc</c> 
      <d>dddd</d> 
      <e>eeee</e> 
     <newElement>NEW_VAL</newElement></b1> 
     <b2 attr="value"> 
      <f>false</f> 
      <g>2014-03-01</g> 
      <h>true</h> 
     </b2> 
     <b3> 
     </b3> 
    </a> 
    </testRequest> 

Вот измененная таблица стилей:

<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:oldns="namespace/1.0" 
    xmlns:newns="namespace/1.1" 
    version="1.0"> 

    <xsl:param name="newnsParam">namespace/1.1</xsl:param> 

    <!-- copy all document --> 
    <xsl:template match="*"> 
     <xsl:element name="{local-name()}" namespace="{namespace-uri()}"> 
      <xsl:apply-templates select="*" /> 
     </xsl:element> 
    </xsl:template> 

    <xsl:template match="@*"> 
     <xsl:copy-of select="."/> 
    </xsl:template> 

    <!-- rename 'g' to 'newName' --> 
    <xsl:template match="/oldns:testRequest/oldns:a/oldns:b1/oldns:g"> 
     <!-- if I don't set it with newns now, it will remain as oldns even after namespace change below --> 
     <xsl:element name="newName" namespace="{$newnsParam}"> 
      <xsl:apply-templates select="@*|node()" /> 
     </xsl:element> 
    </xsl:template> 

    <!-- add 'newElement' --> 
    <xsl:template match="/oldns:testRequest/oldns:a/oldns:b1"> 
     <!-- same as above, must set as newns now, and this one won't be qualified (why?) --> 
     <xsl:element name="b1" namespace="{$newnsParam}"> 
      <xsl:apply-templates select="@*|node()" /> 
      <xsl:element name="newElement" namespace="{$newnsParam}">NEW_VAL</xsl:element> 
     </xsl:element> 
    </xsl:template> 

    <!-- change namespace, but it makes every node redefine the namespace --> 
    <xsl:template match="@oldns:*"> 
     <xsl:attribute name="newns:{local-name()}" namespace="{$newnsParam}"> 
      <xsl:value-of select="."/> 
     </xsl:attribute> 
    </xsl:template> 

    <xsl:template match="oldns:*"> 
     <xsl:element name="{local-name()}" namespace="{$newnsParam}"> 
      <xsl:apply-templates select="node()|@*"/> 
     </xsl:element> 
    </xsl:template> 

</xsl:stylesheet> 
+0

Спасибо, Майкл, но ваше предложение все еще делало каждый узел с объявлением пространства имен. Это проблема конфигурации Xalan? Он используется глубоко внутри стека вызовов, я даже не уверен, что смогу его настроить. – user2479523

+0

Похож, ваша проблема специфична для Xalan. Время двигаться дальше ... –

+0

хорошо, пока это выглядит так ... после многих поисков ничего полезного не появилось. Xalan удобен в комплекте с JRE. Если бы я перешел в Саксон, не мог ли этот XSLT быть улучшен с использованием версии 2.0? – user2479523

1

Я не уверен, что это работает для вас, потому что я использую другой процессор xlst (xsltproc). Также мой вывод с вашим оригинальным xslt не полностью похож на ваш вывод.

Так или иначе:
В основном я удалил атрибут пространства имен из xlst и добавил два раза префикс пространства имен "newns".

Попробуйте это:

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:oldns="namespace/1.0" 
    xmlns:newns="namespace/1.1" 
    version="1.0"> 

    <xsl:param name="newnsParam">namespace/1.1</xsl:param> 

    <!-- copy all document --> 
    <xsl:template match="@*|node()"> 
     <xsl:copy> 
      <xsl:apply-templates select="@*|node()" /> 
     </xsl:copy> 
    </xsl:template> 

    <!-- rename 'g' to 'newName' --> 
    <xsl:template match="/oldns:testRequest/oldns:a/oldns:b1/oldns:g"> 
     <!-- if I don't set it with newns now, it will remain as oldns even after namespace change below --> 
     <xsl:element name="newns:newName"> 
      <xsl:apply-templates select="@*|node()" /> 
     </xsl:element> 
    </xsl:template> 

    <!-- add 'newElement' --> 
    <xsl:template match="/oldns:testRequest/oldns:a/oldns:b1"> 
     <!-- same as above, must set as newns now, and this one won't be qualified (why?) --> 
     <xsl:element name="newns:b1" > 
      <xsl:apply-templates select="@*|node()" /> 
      <xsl:element name="newns:newElement">NEW_VAL</xsl:element> 
     </xsl:element> 
    </xsl:template> 

    <!-- change namespace, but it makes every node redefine the namespace --> 
    <xsl:template match="@oldns:*"> 
     <xsl:attribute name="newns:{local-name()}"> 
      <xsl:value-of select="."/> 
     </xsl:attribute> 
    </xsl:template> 
    <xsl:template match="oldns:*"> 
     <xsl:element name="newns:{local-name()}"> 
      <xsl:apply-templates select="node()|@*"/> 
     </xsl:element> 
    </xsl:template> 

</xsl:stylesheet> 

который генерирует следующий вывод (по крайней мере, с xsltproc):

<?xml version="1.0"?> 
<newns:testRequest xmlns:newns="namespace/1.1"> 
    <newns:a> 
     <newns:b1> 
      <newns:c>cccc</newns:c> 
      <newns:d>dddd</newns:d> 
      <newns:e>eeee</newns:e> 
     <newns:newElement>NEW_VAL</newns:newElement></newns:b1> 
     <newns:b2 attr="value"> 
      <newns:f>false</newns:f> 
      <newns:g>2014-03-01</newns:g> 
      <newns:h>true</newns:h> 
     </newns:b2> 
     <newns:b3> 
     </newns:b3> 
    </newns:a> 
    </newns:testRequest> 
+0

Спасибо за ваше предложение. Раньше я пробовал что-то подобное, и ваше предложение также не работает для моего анализатора. Однако выход идеален. Я добавил исключение в орган вопроса. – user2479523

Смежные вопросы