Kieran Ojakangas
Kieran Ojakangas

Reputation: 535

XDocument - Convert Open and Close Tags to Self-Closing Tag

(Note: This is NOT a semantic question. This isn't asking about what self closing tags mean. This is about doing the opposite with XDocument than what is mentioned in this example post with C#: Remove self-closing tags (e.g. />) in an XmlDocument).

I'm writing this because I've found so many posts on here and elsewhere that ask about a good way to change self-closing tags in a C# XDocument object to an open and close tag with an empty value.

I want to do the opposite. I want to change this:

<GovtID id="1"></GovtID>

to this:

<GovtID id="1"/>

I want to preserve all attributes and their values that were in the element from before, EXCEPT empty values.

Here's the latest thing I've tried:

private static void CleanUpXElements(XDocument doc)
{
    foreach (XElement childElement in
        from x in doc.DescendantNodes().OfType<XElement>()
        where x.Value == string.Empty
        select x)
    {
        childElement.ReplaceWith(new XElement(childElement.Name.LocalName, childElement.Attributes()));
    };
}

However, when I use this approach, I get a Null Exception when the child element in question gets replaced this way.

Is there a way I can do this without throwing a Null Reference Exception? I see the element change to a self closing tag, but then the Null Exception happens.

Upvotes: 1

Views: 82

Answers (1)

dbc
dbc

Reputation: 116516

I think your problem may be that you are modifying the document's element tree while iterating through it, causing the iterator to throw an exception when it tries to proceed past the modification.

As an alternative, you could iterate through the elements and, for every element that has no child elements and an empty value, completely remove all child nodes:

public static void CleanUpXElements(this XDocument doc)
{
    foreach (XElement childElement in
             from x in doc.Descendants()
             where !x.IsEmpty && !x.Elements().Any() && string.IsNullOrEmpty(x.Value)
             select x)
    {
        childElement.RemoveNodes();
    };
}

This avoids modifying the element tree while iterating through it and results in <GovtID id="1" />

Demo fiddle here.

Be aware that, as noted by kjhughes, the two empty element forms <GovtID id="1"></GovtID> and <GovtID id="1" /> are semantically identical. As such it should not be necessary to convert one form to the other, and any code that depends on the difference between them is likely broken.

Finally, if your underlying problem is that

  1. You are deserializing using XmlSerializer.
  2. The value of <GovtID> is defined to be an int or int? or Guid? or other primitive (non-string) value type in your data model.
  3. The serializer is throwing an exception attempting to deserialize the empty value
    <GovtID></GovtID>.

Then see Deserializing empty xml attribute value into nullable int property using XmlSerializer. This answer by bvdb to Self-closing tags in XML files would also apply.

Upvotes: 1

Related Questions