Удалить пустые теги XML

Я ищу хороший подход, который может эффективно удалять пустые теги из XML. Что вы порекомендуете? Регулярное выражение? XDocument используется? Класс xmltextreader?

например,

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

может стать:

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

6 ответов


загрузка оригинала в XDocument и использование следующего кода дает желаемый результат:

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

это означает улучшение принятого ответа для обработки атрибутов:

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

идея здесь состоит в том, чтобы проверить, что все атрибуты элемента также пусты перед его удалением. существует также случай, когда пустые потомки могут иметь непустые атрибуты. Я вставил третье условие, чтобы проверить, что элемент имеет все пустые атрибуты среди своих потомков. учитывая следующий документ С node8 добавлено:

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

это:

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

оригинал и улучшена ответ на этот вопрос потеряет node2 и node6 и node8 узлы. Проверка на e.IsEmpty будет работать, если вы хотите только удалить узлы, как <node />, но это redunant, если вы собираетесь для обоих <node /> и <node></node>. Если вам также нужно удалить пустые атрибуты, вы можете сделать следующее:

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

что бы дать ты:

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

как всегда, это зависит от ваших требований.

знаете ли вы, как будет отображаться пустой тег? (например,<pig />, <pig></pig>, etc.) Я обычно не рекомендую использовать регулярные выражения (они действительно полезны, но в то же время они являются злом). Также учитывая string.Replace подход кажется проблематичным, если ваш XML не имеет определенной структуры.

наконец, я бы рекомендовал использовать подход синтаксического анализа XML (убедитесь, что ваш код действителен XML.)

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

все, что вы используете, должно будет пройти через файл по крайней мере один раз. Если это только один именованный тег, который вы знаете, то regex-ваш друг, иначе используйте подход стека. Начните с родительского тега, и если у него есть sub-тег, поместите его в стек. Если вы нашли пустой тег, удалите его, а затем, как только вы прошли через дочерние теги и достигли конечного тега того, что у вас есть поверх стека, затем вставьте его и проверьте. Если его пустой, удалите его. Таким образом, вы можете удалить все пустые теги, включая теги с пустыми детьми.

Если вы после выражения reg ex используйте этой


XDocument вероятно, проще всего реализовать и даст адекватную производительность, если вы знаете, что ваши документы достаточно малы.

XmlTextReader будет быстрее и использовать меньше памяти, чем XDocument используется при обработке больших документов.

регулярное выражение Лучше всего подходит для обработки текста, а не XML. Он может не обрабатывать все крайние случаи, как вы хотели бы (например, тег в разделе CDATA; тег с атрибутом xmlns), поэтому, вероятно, не является хорошей идеей для общего реализация, но может быть адекватной в зависимости от того, насколько вы контролируете входной XML.


XmlTextReader предпочтительнее, если речь идет о производительности (он обеспечивает быстрый, прямой доступ только к XML). Вы можете определить, пуст ли тег, используя XmlReader.IsEmptyElement собственность.

подход XDocument, который производит желаемый результат:

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