Skip to content

LINQ to XML: Inconsistent namespace prefix handling #48400

@philippdolder

Description

@philippdolder

Description

Note: this post has been edited to summarize the whole discussion.

I'm experiencing inconsistent behavior regarding XML namespace prefixing. When I create an XElement via new XElement() the code behaves differently from when I use XElement.Parse() to parse the equivalent XML structure.
See code sample below. The first assert succeeds. The second assert fails.
The root element knows about the namespace prefixing. After adding both elements to it, only the one created with the constructor gets prefixed properly. See string representations of each object as they are at the end of the Fact method.

original: <mn:p xmlns:mn="http://my-namespace.com/">text</mn:p>
deserialized: <p xmlns="http://my-namespace.com/">text</p>
root:

<html xmlns:mn="http://my-namespace.com/">
  <mn:p>text</mn:p>
  <p xmlns="http://my-namespace.com/">text</p>
</html>
namespace MyNamespace
{
    using System.Xml.Linq;
    using Xunit;

    public class XmlParsing
    {
        private const string Prefix = "mn";
        private static readonly XNamespace Namespace = "http://my-namespace.com/";

        [Fact]
        public void Fact()
        {
            var root = new XElement("html", new XAttribute(XNamespace.Xmlns + Prefix, Namespace));
            var original = new XElement(Namespace + "p", "text");

            var deserialized = XElement.Parse(original.ToString());

            // original: [<p xmlns="http://my-namespace.com/">text</p>]
            // deserialized: [<p xmlns="http://my-namespace.com/">text</p>]
            Assert.Equal(original.ToString(), deserialized.ToString()); // succeeds

            root.Add(original);
            root.Add(deserialized);

            // original: [<mn:p xmlns:mn="http://my-namespace.com/">text</mn:p>]
            // deserialized: [<p xmlns="http://my-namespace.com/">text</p>]
            Assert.Equal(original.ToString(), deserialized.ToString()); // fails
        }
    }
}

I would expect the namespace prefixing to work identical no matter how the XElement is created.

Using XDocument instead, leads to the same error.

        public class XmlParsing
        {
            private const string Prefix = "mn";
            private static readonly XNamespace Namespace = "http://my-namespace.com/";

            [Fact]
            public void Fact()
            {
                var root = new XElement("html", new XAttribute(XNamespace.Xmlns + Prefix, Namespace));
                var document = new XDocument();
                document.Add(root);
                var original = new XElement(Namespace + "p", "text");

                var deserialized = XDocument.Parse(original.ToString()).Root;

                // original: [<p xmlns="http://my-namespace.com/">text</p>]
                // deserialized: [<p xmlns="http://my-namespace.com/">text</p>]
                Assert.Equal(original.ToString(), deserialized.ToString()); // succeeds

                root.Add(original);
                root.Add(deserialized);

                // original: [<mn:p xmlns:mn="http://my-namespace.com/">text</mn:p>]
                // deserialized: [<p xmlns="http://my-namespace.com/">text</p>]
                Assert.Equal(original.ToString(), deserialized.ToString()); // fails
            }
        }

Would be nice to add some justification why do you think this behavior is better and comments with what are the values on each assert
My expectation would be that the XElement implementation behaves exactly the same. Independent of how the XElement is created. Be it using the constructor or the XElement.Parse() method.

I also created a gist containing above code to demonstrate the issue.

Configuration

Above code is running in a netcoreappe3.1 with C# 8.0 nullable enabled on a Windows 10 Pro x64

Regression?

Other information

  • Do you know of any workarounds?
    The only work around I found is to explicitly add the new XAttribute(XNamespace.Xmlns + Prefix, Namespace) attribute to the original XElement. Since the xml I'm trying to parse is not 100% under our control, this will not work in the real world scenario.
    Doesn't really work, as the resulting xml will contain unnecessary namespace prefix declarations

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions