Чтобы сделать это для всех пустых дочерних узлов, используйте цикл for (не foreach) и в обратном порядке. Я решил это как:
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(@"<node1 attrib1=""abc"">
<node1_1>
<node1_1_1 />
</node1_1>
<node1_2 />
<node1_3 />
</node1>
<node2 />
");
RemoveEmptyNodes(xmlDocument );
private static bool RemoveEmptyNodes(XmlNode node)
{
if (node.HasChildNodes)
{
for(int I = node.ChildNodes.Count-1;I >= 0;I--)
if (RemoveEmptyNodes(node.ChildNodes[I]))
node.RemoveChild(node.ChildNodes[I]);
}
return
(node.Attributes == null ||
node.Attributes.Count == 0) &&
node.InnerText.Trim() == string.Empty;
}
Рекурсивные вызовы (аналогично другим решениям) исключают обработку дублированных документов в подходе xPath. Что еще более важно код более читабелен и более готов к редактированию. Win-Win.
Итак, это решение удалит <node2>
, но также правильно удалит <node1_2>
и <node1_3>
.
Обновление: Обнаружено заметное увеличение производительности благодаря использованию следующей реализации Linq.
string myXml = @"<node1 attrib1=""abc"">
<node1_1>
<node1_1_1 />
</node1_1>
<node1_2 />
<node1_3 />
</node1>
<node2 />
");
XElement xElem = XElement.Parse(myXml);
RemoveEmptyNodes2(xElem);
private static void RemoveEmptyNodes2(XElement elem)
{
int cntElems = elem.Descendants().Count();
int cntPrev;
do
{
cntPrev = cntElems;
elem.Descendants()
.Where(e =>
string.IsNullOrEmpty(e.Value.Trim()) &&
!e.HasAttributes).Remove();
cntElems = elem.Descendants().Count();
} while (cntPrev != cntElems);
}
Цикл обрабатывает случаи, когда родительский элемент должен быть удален, поскольку был удален только его дочерний элемент. Использование XContainer
или производных имеет тенденцию к аналогичному увеличению производительности из-за реализаций IEnumerable
за кулисами. Это моя новая любимая вещь.
В произвольном 68-мегабайтном XML-файле RemoveEmptyNodes
обычно занимает около 90 с, а RemoveEmptyNodes2
- около 1 с.