2011-09-06 27 views
12

Estoy buscando un buen enfoque que pueda eliminar etiquetas vacías de XML de manera eficiente. ¿Que recomiendas? Regex? XDocument? XmlTextReader?Eliminar etiquetas XML vacías

Por ejemplo,

const string original = 
    @"<?xml version=""1.0"" encoding=""utf-16""?> 
    <pet> 
     <cat>Tom</cat> 
     <pig /> 
     <dog>Puppy</dog> 
     <snake></snake> 
     <elephant> 
      <africanElephant></africanElephant> 
      <asianElephant>Biggy</asianElephant> 
     </elephant> 
     <tiger> 
      <tigerWoods></tigerWoods>  
      <americanTiger></americanTiger> 
     </tiger> 
    </pet>"; 

podría llegar a ser:

const string expected = 
    @"<?xml version=""1.0"" encoding=""utf-16""?> 
     <pet> 
     <cat>Tom</cat> 
     <dog>Puppy</dog>   
     <elephant>            
      <asianElephant>Biggy</asianElephant> 
     </elephant>         
    </pet>"; 
+1

Argh! no regex! – JXG

+0

Hice una prueba de perfermancia simple ayer, el XDocument es mucho mejor que la expresión regular en términos de rendimiento, todavía no he descubierto cómo implementarlo usando XmlTextReader, en términos de complejidad, XDocument es lo suficientemente bueno para hacer frente a mis necesidades, por lo voy por XDocument, ¡gracias a todos por tu ayuda! – Ming

+0

esto podría ayudar http://stackoverflow.com/questions/14509188/remove-empty-blanks-elements-in-collection-of-xml-nodes –

Respuesta

25

Cargando su original en una XDocument y usando el siguiente código le da a su salida deseada:

var document = XDocument.Parse(original); 
document.Descendants() 
     .Where(e => e.IsEmpty || String.IsNullOrWhiteSpace(e.Value)) 
     .Remove(); 
+3

Esta es una gran respuesta, pero eliminará elementos que tienen atributos pero no contenido, por ejemplo '' se eliminarán, lo que puede no ser deseable. He proporcionado otra respuesta basada en esta para complementar eso. –

+0

@DanField pregunta anterior, pero ayuda a agregar respuestas actualizadas y/o mejores. También podría haber actualizado mi respuesta, si le hubiera gustado. De todos modos, he votado a favor su respuesta. – Jamiec

0

XMLTextReader es preferible si estamos hablando de rendimiento (que proporciona acceso de sólo avance rápido, a XML). Puede determinar si la etiqueta está vacía usando la propiedad XmlReader.IsEmptyElement.

enfoque XDocument que produce la salida deseada:

public static bool IsEmpty(XElement n) 
{ 
    return n.IsEmpty 
     || (string.IsNullOrEmpty(n.Value) 
      && (!n.HasElements || n.Elements().All(IsEmpty))); 
} 

var doc = XDocument.Parse(original); 
var emptyNodes = doc.Descendants().Where(IsEmpty); 
foreach (var emptyNode in emptyNodes.ToArray()) 
{ 
    emptyNode.Remove(); 
} 
+1

IsEmptyElement no funciona si el elemento es . Funcionaría si el elemento es Ming

+0

@Ming, puede implementar la misma lógica que proporcioné para XDocument. –

0

Cualquier cosa que utilice tendrá que pasar por el archivo una vez al menos. Si solo tiene una etiqueta con nombre que conoces, entonces tu amigo es regex, de lo contrario usa un enfoque de pila. Comience con la etiqueta padre y si tiene una etiqueta secundaria colóquela en la pila. Si encuentras una etiqueta vacía, quítala y una vez que hayas revisado las etiquetas secundarias y hayas llegado a la etiqueta final de lo que tienes arriba de la pila, preséntala y revístela también. Si está vacío eliminarlo también. De esta forma, puede eliminar todas las etiquetas vacías, incluidas las etiquetas con hijos vacíos.

Si estás después de una expresión reg ex utilizar this

0

XDocument es probablemente más sencillo de implementar, y dará un rendimiento adecuado si conoce sus documentos son razonablemente pequeño.

XmlTextReader será más rápido y utilizará menos memoria que XDocument al procesar documentos muy grandes.

Regex es mejor para manejar texto en lugar de XML. Es posible que no maneje todos los casos extremos como desee (por ejemplo, una etiqueta dentro de una sección CDATA, una etiqueta con un atributo xmlns), por lo que probablemente no sea una buena idea para una implementación general, pero puede ser adecuada dependiendo de cuánto control tener del XML de entrada.

+0

Gracias amigo, me gusta XmlTextReader, juego un poco, pero puedo encontrar la manera de cumplir mis requisitos. ¿Tienes un ejemplo para eso, por favor? – Ming

+1

@Ming, eche un vistazo al siguiente artículo de MSDN, que describe cómo encadenar un XmlReader a un XmlWriter, una técnica que le permite filtrar el XML de la manera que desee: http://msdn.microsoft.com/en -us/library/aa302289.aspx – Joe

2

Como siempre, depende de sus requisitos.

¿Sabes cómo se mostrará la etiqueta vacía? (por ejemplo, <pig />, <pig></pig>, etc.) Normalmente no recomiendo usar expresiones regulares (son realmente útiles pero al mismo tiempo son malas). También considerar un enfoque string.Replace parece ser problemático a menos que su XML no tenga una cierta estructura.

Finalmente, recomendaría utilizar un enfoque de analizador XML (asegúrese de que su código sea XML válido).

var doc = XDocument.Parse(original); 
var emptyElements = from descendant in doc.Descendants() 
        where descendant.IsEmpty || string.IsNullOrWhiteSpace(descendant.Value) 
        select descendant; 
emptyElements.Remove(); 
+1

No necesita el 'ForEach' extra y' Remove '- el método remove actúa en cada elemento de IEnumerable. – Jamiec

+0

Descubrió el 'error'. Editado, gracias :) –

+0

+1 por proporcionar la solución antes de la respuesta aceptada, que es una versión ligeramente más elegante de esta. –

14

Esto está destinado a ser una mejora en la respuesta aceptada para manejar atributos:

XDocument xd = XDocument.Parse(original); 
xd.Descendants() 
    .Where(e => (e.Attributes().All(a => a.IsNamespaceDeclaration || string.IsNullOrWhiteSpace(a.Value)) 
      && string.IsNullOrWhiteSpace(e.Value) 
      && e.Descendants().SelectMany(c => c.Attributes()).All(ca => ca.IsNamespaceDeclaration || string.IsNullOrWhiteSpace(ca.Value)))) 
    .Remove(); 

La idea aquí es verificar que todos los atributos en un elemento también estén vacíos antes de eliminarlo. También existe el caso de que los descendientes vacíos pueden tener atributos no vacíos.Inserté una tercera condición para verificar que el elemento tenga todos los atributos vacíos entre sus descendientes. Teniendo en cuenta el siguiente documento con node8 añade:

<root> 
    <node /> 
    <node2 blah='' adf='2'></node2> 
    <node3> 
    <child /> 
    </node3> 
    <node4></node4> 
    <node5><![CDATA[asdfasdf]]></node5> 
    <node6 xmlns='urn://blah' d='a'/> 
    <node7 xmlns='urn://blah2' /> 
    <node8> 
    <child2 d='a' /> 
    </node8> 
</root> 

Esto se convertiría en:

<root> 
    <node2 blah="" adf="2"></node2> 
    <node5><![CDATA[asdfasdf]]></node5> 
    <node6 xmlns="urn://blah" d="a" /> 
    <node8> 
    <child2 d='a' /> 
    </node8> 
</root> 

El original y mejorado respuesta a esta pregunta perdería los node2 y node6 y node8 nodos. La comprobación de e.IsEmpty funcionaría si solo desea eliminar nodos como <node />, pero es redundante si va a <node /> y <node></node>. Si también necesita quitar atributos vacíos, usted puede hacer esto:

xd.Descendants().Attributes().Where(a => string.IsNullOrWhiteSpace(a.Value)).Remove(); 
xd.Descendants() 
    .Where(e => (e.Attributes().All(a => a.IsNamespaceDeclaration)) 
      && string.IsNullOrWhiteSpace(e.Value)) 
    .Remove(); 

que le daría:

<root> 
    <node2 adf="2"></node2> 
    <node5><![CDATA[asdfasdf]]></node5> 
    <node6 xmlns="urn://blah" d="a" /> 
</root> 
Cuestiones relacionadas