2009-03-05 121 views
52

Tengo un documento XML y deseo cambiar los valores de uno de los atributos.XSLT: ¿Cómo cambiar el valor de un atributo durante <xsl:copy>?

En primer lugar he copiado todo, desde la entrada hasta la salida usando:

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

y ahora quiero cambiar el valor del atributo "type" en cualquier elemento llamado "property".

+0

Para aquellos que quieren una solución general: algún valor nuevo aquí astonia

+2

Su solución es innecesariamente prolija, y parcialmente errónea. Debería haber '' http: // www.'' al principio del espacio de nombres 'xsl'. Además, hacer coincidir/seleccionar 'node() | comment() | processing-instruction() | text()' es superfluo, ya que los comentarios, las instrucciones de procesamiento y los nodos de texto se corresponden con 'node()'. – Flynn1179

+0

@ Flynn1179 Mi solución funciona bien para todas las situaciones. No sé por qué http: // falta después de copiar/pegar, eso es un error, gracias por señalar. Acabo de dar una posible solución, no la perfecta. Lo más importante es que mi solución funciona para casi todas las situaciones, aunque "es superfluo" como dijiste. Mientras que, por otro lado, la mayoría de las otras respuestas, incluida la que dio "el experto xslt", no funcionan en absoluto. Pero ellos no lo admitieron. – astonia

Respuesta

33

Probado en un ejemplo sencillo, funciona bien:

<xsl:template match="@*|node()"> 
    <xsl:copy> 
    <xsl:apply-templates select="@*|node()"/> 
    </xsl:copy> 
</xsl:template> 
<xsl:template match="@type[parent::property]"> 
    <xsl:attribute name="type"> 
    <xsl:value-of select="'your value here'"/> 
    </xsl:attribute> 
</xsl:template> 

vez haya incluido en la sugerencia de Tomalak.

+1

Una versión alternativa sería Tomalak

+0

De acuerdo. Su camino es probablemente más intuitivo, ya que coincide más lógicamente con lo que es la plantilla. – Welbog

+1

Eso es lo que quería decir en el comentario original también, pero olvidé escribirlo realmente. ;-) – Tomalak

4

Necesita una plantilla que coincida con su atributo de destino, y nada más.

<xsl:template match='XPath/@myAttr'> 
    <xsl:attribute name='myAttr'>This is the value</xsl:attribute> 
</xsl:template> 

Esto se suma a la "copia todos" ya tiene (y es en realidad siempre presente de forma predeterminada en XSLT). Tener una coincidencia más específica se usará de preferencia.

+0

Lo he intentado sin la parte "copiar todo" y solo obtuve lo que estaba entre las etiquetas. Ninguna de las etiquetas o los atributos se copiaron. – tomato

+0

@coderx: Necesitaría ver una muestra, no estoy seguro de lo que quiere decir. – Richard

1

Para el siguiente código XML:

<?xml version="1.0" encoding="utf-8"?> 
<root> 
    <property type="foo"/> 
    <node id="1"/> 
    <property type="bar"> 
     <sub-property/> 
    </property> 
</root> 

yo era capaz de conseguir que funcione con la siguiente XSLT:

<?xml version="1.0" encoding="utf-8"?> 
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:template match="@*|node()"> 
     <xsl:copy> 
      <xsl:apply-templates select="@*|node()"/> 
     </xsl:copy> 
    </xsl:template> 
    <xsl:template match="//property"> 
     <xsl:copy> 
      <xsl:attribute name="type"> 
       <xsl:value-of select="@type"/> 
       <xsl:text>-added</xsl:text> 
      </xsl:attribute> 
      <xsl:copy-of select="child::*"/> 
     </xsl:copy> 
    </xsl:template> 
</xsl:stylesheet> 
59

Este problema tiene una solución clásica: Uso y primordial the identity template es uno de los patrones de diseño XSLT más fundamentales y potentes:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:output omit-xml-declaration="yes" indent="yes"/> 

    <xsl:param name="pNewType" select="'myNewType'"/> 

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

    <xsl:template match="property/@type"> 
     <xsl:attribute name="type"> 
      <xsl:value-of select="$pNewType"/> 
     </xsl:attribute> 
    </xsl:template> 
</xsl:stylesheet> 

Cuando se aplica en este documento XML:

<t> 
    <property>value1</property> 
    <property type="old">value2</property> 
</t> 

el resultado deseado se produce:

<t> 
    <property>value1</property> 
    <property type="myNewType">value2</property> 
</t> 
+1

Esta solución no funciona si hay una definición de espacio de nombres. He escrito un comentario hace algunos días, y el escritor de la respuesta respondió. Pero ahora se han ido, así que tengo que volver a publicar el comentario a los que vienen aquí para no dejarse llevar por esas respuestas equivocadas, especialmente por aquellos escritores que tienden a equivocarse. – astonia

+0

Quizás también se está centrando en la teoría en lugar del problema en sí. Google me llevó hasta aquí, tu respuesta es útil, pero no puede resolver mi problema en absoluto. Así que finalmente conseguí uno mejor, sea lo que sea teóricamente correcto o incorrecto, o puede hacer que alguien enloquezca por los espacios de nombres. Lo que sí me importa es encontrar una manera de resolver mi problema y espero que mi experiencia pueda ayudar a otras personas que tienen situaciones similares. Su respuesta es realmente útil, y usted es realmente un entusiasta que responde aquí. Pero debo decir que la solución que proporcionó para esta pregunta no funciona en absoluto. – astonia

+0

Esta solución no funciona para mí si hay una definición de espacio de nombres en el elemento raíz. – dps

2

que tenía un caso similar en el que quería eliminar un atributo de un simple nodo, y no pude determinar qué eje me permitiría leer el nombre del atributo. Al final, todo lo que tenía que hacer era utilizar

@*[name(.)!='AttributeNameToDelete']

+1

+1 porque esta construcción es útil si se quiere cambiar un atributo dentro de una copia. pero la respuesta es incompleta Vea esta respuesta para lo que quiero decir: http://stackoverflow.com/a/12919373/520567 – akostadinov

5

Los dos mejores respuestas no va a funcionar si no hay una definición xmlns en el elemento raíz:

<?xml version="1.0"?> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
    <property type="old"/> 
</html> 

Todas las soluciones se no funciona para el xml anterior.

La solución posible es como:

<?xml version="1.0"?> 

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

    <xsl:output omit-xml-declaration="yes" indent="yes"/> 
    <xsl:template match="node()[local-name()='property']/@*[local-name()='type']"> 
     <xsl:attribute name="{name()}" namespace="{namespace-uri()}"> 
       some new value here 
      </xsl:attribute> 
    </xsl:template> 

    <xsl:template match="@*|node()|comment()|processing-instruction()|text()"> 
     <xsl:copy> 
      <xsl:apply-templates select="@*|node()|comment()|processing-instruction()|text()"/> 
     </xsl:copy> 
    </xsl:template> 
</xsl:stylesheet> 
+0

Usted está haciendo esto mucho más complicado de lo necesario. He publicado una respuesta que muestra cómo hacer que esas dos respuestas principales funcionen en su situación. –

+0

Tu respuesta es mucho más complicada que la mía. No puedo ver por qué das la respuesta extra después de mi publicación. Lo que debes hacer es agregar mi respuesta. Y hablando francamente, su respuesta es incorrecta si el atributo también tiene un espacio de nombres. – astonia

1

Si el documento XML de origen tiene su propio espacio de nombres, es necesario declarar el espacio de nombres en la hoja de estilos, asignarle un prefijo, y utilizar ese prefijo cuando se refiere a la elementos del XML de origen - por ejemplo:

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

<xsl:output method="xml" encoding="utf-8" indent="yes" omit-xml-declaration="yes" /> 

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

<!-- exception-->  
<xsl:template match="xhtml:property/@type"> 
    <xsl:attribute name="type"> 
     <xsl:text>some new value</xsl:text> 
    </xsl:attribute> 
</xsl:template> 

</xsl:stylesheet> 

O, si lo prefiere:

... 
<!-- exception-->  
<xsl:template match="@type[parent::xhtml:property]"> 
    <xsl:attribute name="type"> 
     <xsl:text>some new value</xsl:text> 
    </xsl:attribute> 
</xsl:template> 
... 

Adición: En el caso muy poco probable que el espacio de nombres XML no se sabe de antemano, que podía hacer:

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

<xsl:output method="xml" encoding="utf-8" indent="yes" omit-xml-declaration="yes" /> 

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

<!-- exception --> 
<xsl:template match="*[local-name()='property']/@type"> 
    <xsl:attribute name="type"> 
     <xsl:text>some new value</xsl:text> 
    </xsl:attribute> 
</xsl:template> 

Por supuesto, es muy difícil imaginar un escenario en el que lo haría sepa de antemano que el documento XML fuente contiene un elemento llamado "propiedad", con un atributo llamado "tipo" que necesita ser reemplazado, pero que todavía no conoce el espacio de nombre del documento. He agregado esto principalmente para mostrar cómo su propia solución podría ser optimizada.

+0

El escenario de espacio de nombres desconocido no es un caso poco probable. Al menos puede escribir un xslt para manejar todos los xml, independientemente de cuáles sean sus espacios de nombres. Por ejemplo, necesito transformar el atributo src de en una imagen vacía para las páginas de miles de sitios web que se rastrearon desde Internet. Obviamente, sus definiciones de espacio de nombres son indeterminadas. Y cada vez que se une a un nuevo proyecto si se necesita xslt, la plantilla general puede ser una de sus herramientas básicas. No tiene que cambiar el espacio de nombres para diferentes proyectos. – astonia

+0

Y su respuesta es incorrecta si el atributo también tiene espacio de nombres. No sé por qué me das otra respuesta incorrecta después de mi publicación. – astonia

0

también me encontré mismo tema y lo resolví de la siguiente manera:

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

<!-- copy property element while only changing its type attribute --> 
<xsl:template match="property"> 
    <xsl:copy> 
    <xsl:attribute name="type"> 
     <xsl:value-of select="'your value here'"/> 
    </xsl:attribute> 
    <xsl:apply-templates select="@*[not(local-name()='type')]|node()"/> 
    <xsl:copy> 
</xsl:template> 
Cuestiones relacionadas