Skip to content

LINQ to XML: Inconsistent namespace prefix handling #48400

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

Labels

area-System.XmluntriagedNew issue has not been triaged by the area owner

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions