2011-09-06 25 views
12

Sto cercando un buon approccio che possa rimuovere i tag vuoti da XML in modo efficiente. Che cosa mi consiglia? Regex? XDocument? XmlTextReader?Rimuovi tag XML vuoti

Per esempio,

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>"; 

potrebbe diventare:

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! non regex! – JXG

+0

Ieri ho fatto un semplice test di perfermance, l'XDocument è di gran lunga migliore rispetto alla reisex in termini di peformance, non ho ancora capito come implementarlo usando XmlTextReader, in termini di complessità, XDocument è abbastanza buono per rispondere alle mie esigenze, quindi Vado su XDocument, grazie a tutti i tuoi aiuti! – Ming

+0

questo potrebbe aiutare http://stackoverflow.com/questions/14509188/remove-empty-blanks-elements-in-collection-of-xml-nodes –

risposta

25

Caricamento l'originale in un XDocument e utilizzando il seguente codice dà l'output desiderato:

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

Questa è un'ottima risposta, ma rimuoverà elementi che hanno attributi ma non contenuti, ad esempio '' verrebbe rimosso, il che potrebbe non essere desiderabile. Ho fornito un'altra risposta basata su questo per integrare questo. –

+0

@DanField vecchia domanda, ma aiuta ad aggiungere risposte aggiornate e/o migliori. Avresti anche potuto aggiornare la mia risposta, se avessi voluto. Ad ogni modo ho alzato la tua risposta. – Jamiec

0

XmlTextReader è preferibile se stiamo parlando di prestazioni (fornisce veloce, forward-only accesso a XML). È possibile determinare se il tag è vuoto utilizzando la proprietà XmlReader.IsEmptyElement.

approccio XDocument che produce output desiderato:

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 non funziona se l'elemento è . Funzionerebbe se l'elemento è Ming

+0

@Ming, È possibile implementare la stessa logica fornita per XDocument. –

0

Tutto ciò che utilizzare dovrà passare attraverso il file almeno una volta. Se è solo un singolo tag con il nome che conosci, allora regex è tuo amico altrimenti usa un approccio stack. Inizia con il tag principale e se ha un tag secondario, posizionalo nello stack. Se trovi un tag vuoto rimuovilo, dopo aver passato i tag secondari e aver raggiunto il tag finale di ciò che hai in cima allo stack, fai un pop e controllalo. Se è vuoto rimuovilo pure. In questo modo puoi rimuovere tutti i tag vuoti inclusi i tag con i bambini vuoti.

Se siete dopo utilizzare un reg ex espressione this

0

XDocument è probabilmente più semplice da implementare, e darà prestazioni adeguate se si conoscono i documenti sono abbastanza piccole.

XmlTextReader sarà più veloce e utilizzerà meno memoria di XDocument durante l'elaborazione di documenti molto grandi.

Regex è la soluzione migliore per gestire il testo anziché XML. Potrebbe non gestire tutti i casi limite come vorresti (ad esempio un tag all'interno di una sezione CDATA, un tag con un attributo xmlns), quindi probabilmente non è una buona idea per un'implementazione generale, ma potrebbe essere adeguato a seconda di quanto controllo hai avere l'XML di input.

+0

Grazie amico, mi piace XmlTextReader, ci gioco intorno ad esso ma riesco a trovare un modo per raggiungere il mio requisito. Ne hai un esempio per favore? – Ming

+1

@Ming, dare un'occhiata al seguente articolo di MSDN, che descrive come concatenare un XmlReader a XmlWriter, una tecnica che consente di filtrare l'XML nel modo desiderato: http://msdn.microsoft.com/en -us/library/aa302289.aspx – Joe

2

Come sempre, dipende dalle vostre esigenze.

Sapete come verrà visualizzato il tag vuoto? (ad esempio <pig />, <pig></pig>, ecc.) Di solito non è consigliabile utilizzare le espressioni regolari (sono davvero utili ma allo stesso tempo sono malvagie). Anche considerare l'approccio string.Replace sembra essere problematico a meno che il tuo XML non abbia una certa struttura.

Infine, si consiglia di utilizzare un approccio parser XML (assicurarsi che il codice sia XML valido).

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

Non è necessario l'ausilio di 'ForEach' e' Remove': il metodo remove agisce su ogni elemento dell'IEnumerable. – Jamiec

+0

Ha individuato l'errore. Modificato, grazie :) –

+0

+1 per fornire effettivamente la soluzione prima della risposta accettata, che è solo una versione leggermente più elegante di questa. –

14

Questo è pensato per essere un miglioramento rispetto alla risposta accettato di gestire gli attributi:

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(); 

L'idea è di verificare che tutti gli attributi su un elemento siano vuoti anche prima di rimuoverlo. C'è anche il caso in cui i discendenti vuoti possono avere attributi non vuoti.Ho inserito una terza condizione per verificare che l'elemento abbia tutti gli attributi vuoti tra i suoi discendenti. Considerando il seguente documento con node8 aggiunto:

<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> 

Questo sarebbe diventato:

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

L'originale e migliorato risposta a questa domanda sarebbe perdere i nodi node2 e node6 e node8. Il controllo di e.IsEmpty funzionerebbe se si desidera eliminare solo nodi come <node />, ma è ridondante se si sta utilizzando sia <node /> e <node></node>. Se anche è necessario rimuovere gli attributi vuoti, si potrebbe fare questo:

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(); 

che darebbe:

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