How to remove all namespaces from XML with C#?

前端 未结 30 2668
悲哀的现实
悲哀的现实 2020-11-22 13:30

I am looking for the clean, elegant and smart solution to remove namespacees from all XML elements? How would function to do that look like?

Defined interface:

30条回答
  •  清歌不尽
    2020-11-22 13:47

    Without resorting to an XSLT-based solution, if you want clean, elegant and smart, you'd need some support from the framework, in particular, the visitor pattern could make this a breeze. Unfortunately, it's not available here.

    I've implemented it inspired by LINQ's ExpressionVisitor to have a similar structure to it. With this, you can apply the visitor pattern to (LINQ-to-) XML objects. (I've done limited testing on this but it works well as far as I can tell)

    public abstract class XObjectVisitor
    {
        public virtual XObject Visit(XObject node)
        {
            if (node != null)
                return node.Accept(this);
            return node;
        }
    
        public ReadOnlyCollection Visit(IEnumerable nodes)
        {
            return nodes.Select(node => Visit(node))
                .Where(node => node != null)
                .ToList()
                .AsReadOnly();
        }
    
        public T VisitAndConvert(T node) where T : XObject
        {
            if (node != null)
                return Visit(node) as T;
            return node;
        }
    
        public ReadOnlyCollection VisitAndConvert(IEnumerable nodes) where T : XObject
        {
            return nodes.Select(node => VisitAndConvert(node))
                .Where(node => node != null)
                .ToList()
                .AsReadOnly();
        }
    
        protected virtual XObject VisitAttribute(XAttribute node)
        {
            return node.Update(node.Name, node.Value);
        }
    
        protected virtual XObject VisitComment(XComment node)
        {
            return node.Update(node.Value);
        }
    
        protected virtual XObject VisitDocument(XDocument node)
        {
            return node.Update(
                node.Declaration,
                VisitAndConvert(node.Nodes())
            );
        }
    
        protected virtual XObject VisitElement(XElement node)
        {
            return node.Update(
                node.Name,
                VisitAndConvert(node.Attributes()),
                VisitAndConvert(node.Nodes())
            );
        }
    
        protected virtual XObject VisitDocumentType(XDocumentType node)
        {
            return node.Update(
                node.Name,
                node.PublicId,
                node.SystemId,
                node.InternalSubset
            );
        }
    
        protected virtual XObject VisitProcessingInstruction(XProcessingInstruction node)
        {
            return node.Update(
                node.Target,
                node.Data
            );
        }
    
        protected virtual XObject VisitText(XText node)
        {
            return node.Update(node.Value);
        }
    
        protected virtual XObject VisitCData(XCData node)
        {
            return node.Update(node.Value);
        }
    
        #region Implementation details
        internal InternalAccessor Accessor
        {
            get { return new InternalAccessor(this); }
        }
    
        internal class InternalAccessor
        {
            private XObjectVisitor visitor;
            internal InternalAccessor(XObjectVisitor visitor) { this.visitor = visitor; }
    
            internal XObject VisitAttribute(XAttribute node) { return visitor.VisitAttribute(node); }
            internal XObject VisitComment(XComment node) { return visitor.VisitComment(node); }
            internal XObject VisitDocument(XDocument node) { return visitor.VisitDocument(node); }
            internal XObject VisitElement(XElement node) { return visitor.VisitElement(node); }
            internal XObject VisitDocumentType(XDocumentType node) { return visitor.VisitDocumentType(node); }
            internal XObject VisitProcessingInstruction(XProcessingInstruction node) { return visitor.VisitProcessingInstruction(node); }
            internal XObject VisitText(XText node) { return visitor.VisitText(node); }
            internal XObject VisitCData(XCData node) { return visitor.VisitCData(node); }
        }
        #endregion
    }
    
    public static class XObjectVisitorExtensions
    {
        #region XObject.Accept "instance" method
        public static XObject Accept(this XObject node, XObjectVisitor visitor)
        {
            Validation.CheckNullReference(node);
            Validation.CheckArgumentNull(visitor, "visitor");
    
            // yay, easy dynamic dispatch
            Acceptor acceptor = new Acceptor(node as dynamic);
            return acceptor.Accept(visitor);
        }
        private class Acceptor
        {
            public Acceptor(XAttribute node) : this(v => v.Accessor.VisitAttribute(node)) { }
            public Acceptor(XComment node) : this(v => v.Accessor.VisitComment(node)) { }
            public Acceptor(XDocument node) : this(v => v.Accessor.VisitDocument(node)) { }
            public Acceptor(XElement node) : this(v => v.Accessor.VisitElement(node)) { }
            public Acceptor(XDocumentType node) : this(v => v.Accessor.VisitDocumentType(node)) { }
            public Acceptor(XProcessingInstruction node) : this(v => v.Accessor.VisitProcessingInstruction(node)) { }
            public Acceptor(XText node) : this(v => v.Accessor.VisitText(node)) { }
            public Acceptor(XCData node) : this(v => v.Accessor.VisitCData(node)) { }
    
            private Func accept;
            private Acceptor(Func accept) { this.accept = accept; }
    
            public XObject Accept(XObjectVisitor visitor) { return accept(visitor); }
        }
        #endregion
    
        #region XObject.Update "instance" method
        public static XObject Update(this XAttribute node, XName name, string value)
        {
            Validation.CheckNullReference(node);
            Validation.CheckArgumentNull(name, "name");
            Validation.CheckArgumentNull(value, "value");
    
            return new XAttribute(name, value);
        }
        public static XObject Update(this XComment node, string value = null)
        {
            Validation.CheckNullReference(node);
    
            return new XComment(value);
        }
        public static XObject Update(this XDocument node, XDeclaration declaration = null, params object[] content)
        {
            Validation.CheckNullReference(node);
    
            return new XDocument(declaration, content);
        }
        public static XObject Update(this XElement node, XName name, params object[] content)
        {
            Validation.CheckNullReference(node);
            Validation.CheckArgumentNull(name, "name");
    
            return new XElement(name, content);
        }
        public static XObject Update(this XDocumentType node, string name, string publicId = null, string systemId = null, string internalSubset = null)
        {
            Validation.CheckNullReference(node);
            Validation.CheckArgumentNull(name, "name");
    
            return new XDocumentType(name, publicId, systemId, internalSubset);
        }
        public static XObject Update(this XProcessingInstruction node, string target, string data)
        {
            Validation.CheckNullReference(node);
            Validation.CheckArgumentNull(target, "target");
            Validation.CheckArgumentNull(data, "data");
    
            return new XProcessingInstruction(target, data);
        }
        public static XObject Update(this XText node, string value = null)
        {
            Validation.CheckNullReference(node);
    
            return new XText(value);
        }
        public static XObject Update(this XCData node, string value = null)
        {
            Validation.CheckNullReference(node);
    
            return new XCData(value);
        }
        #endregion
    }
    
    public static class Validation
    {
        public static void CheckNullReference(T obj) where T : class
        {
            if (obj == null)
                throw new NullReferenceException();
        }
    
        public static void CheckArgumentNull(T obj, string paramName) where T : class
        {
            if (obj == null)
                throw new ArgumentNullException(paramName);
        }
    }
    

    p.s., this particular implementation uses some .NET 4 features to make implementation a bit easier/cleaner (usage of dynamic and default arguments). It shouldn't be too dificult to make it .NET 3.5 compatible, perhaps even .NET 2.0 compatible.

    Then to implement the visitor, here's a generalized one that can change multiple namespaces (and the prefix used).

    public class ChangeNamespaceVisitor : XObjectVisitor
    {
        private INamespaceMappingManager manager;
        public ChangeNamespaceVisitor(INamespaceMappingManager manager)
        {
            Validation.CheckArgumentNull(manager, "manager");
    
            this.manager = manager;
        }
    
        protected INamespaceMappingManager Manager { get { return manager; } }
    
        private XName ChangeNamespace(XName name)
        {
            var mapping = Manager.GetMapping(name.Namespace);
            return mapping.ChangeNamespace(name);
        }
    
        private XObject ChangeNamespaceDeclaration(XAttribute node)
        {
            var mapping = Manager.GetMapping(node.Value);
            return mapping.ChangeNamespaceDeclaration(node);
        }
    
        protected override XObject VisitAttribute(XAttribute node)
        {
            if (node.IsNamespaceDeclaration)
                return ChangeNamespaceDeclaration(node);
            return node.Update(ChangeNamespace(node.Name), node.Value);
        }
    
        protected override XObject VisitElement(XElement node)
        {
            return node.Update(
                ChangeNamespace(node.Name),
                VisitAndConvert(node.Attributes()),
                VisitAndConvert(node.Nodes())
            );
        }
    }
    
    // and all the gory implementation details
    public class NamespaceMappingManager : INamespaceMappingManager
    {
        private Dictionary namespaces = new Dictionary();
    
        public NamespaceMappingManager Add(XNamespace fromNs, XNamespace toNs, string toPrefix = null)
        {
            var item = new NamespaceMapping(fromNs, toNs, toPrefix);
            namespaces.Add(item.FromNs, item);
            return this;
        }
    
        public INamespaceMapping GetMapping(XNamespace fromNs)
        {
            INamespaceMapping mapping;
            if (!namespaces.TryGetValue(fromNs, out mapping))
                mapping = new NullMapping();
            return mapping;
        }
    
        private class NullMapping : INamespaceMapping
        {
            public XName ChangeNamespace(XName name)
            {
                return name;
            }
    
            public XObject ChangeNamespaceDeclaration(XAttribute node)
            {
                return node.Update(node.Name, node.Value);
            }
        }
    
        private class NamespaceMapping : INamespaceMapping
        {
            private XNamespace fromNs;
            private XNamespace toNs;
            private string toPrefix;
            public NamespaceMapping(XNamespace fromNs, XNamespace toNs, string toPrefix = null)
            {
                this.fromNs = fromNs ?? "";
                this.toNs = toNs ?? "";
                this.toPrefix = toPrefix;
            }
    
            public XNamespace FromNs { get { return fromNs; } }
            public XNamespace ToNs { get { return toNs; } }
            public string ToPrefix { get { return toPrefix; } }
    
            public XName ChangeNamespace(XName name)
            {
                return name.Namespace == fromNs
                    ? toNs + name.LocalName
                    : name;
            }
    
            public XObject ChangeNamespaceDeclaration(XAttribute node)
            {
                if (node.Value == fromNs.NamespaceName)
                {
                    if (toNs == XNamespace.None)
                        return null;
                    var xmlns = !String.IsNullOrWhiteSpace(toPrefix)
                        ? (XNamespace.Xmlns + toPrefix)
                        : node.Name;
                    return node.Update(xmlns, toNs.NamespaceName);
                }
                return node.Update(node.Name, node.Value);
            }
        }
    }
    
    public interface INamespaceMappingManager
    {
        INamespaceMapping GetMapping(XNamespace fromNs);
    }
    
    public interface INamespaceMapping
    {
        XName ChangeNamespace(XName name);
        XObject ChangeNamespaceDeclaration(XAttribute node);
    }
    

    And a little helper method to get the ball rolling:

    T ChangeNamespace(T node, XNamespace fromNs, XNamespace toNs, string toPrefix = null) where T : XObject
    {
        return node.Accept(
            new ChangeNamespaceVisitor(
                new NamespaceMappingManager()
                    .Add(fromNs, toNs, toPrefix)
            )
        ) as T;
    }
    

    Then to remove a namespace, you could call it like so:

    var doc = ChangeNamespace(XDocument.Load(pathToXml),
        fromNs: "http://schema.peters.com/doc_353/1/Types",
        toNs: null);
    

    Using this visitor, you can write a INamespaceMappingManager to remove all namespaces.

    T RemoveAllNamespaces(T node) where T : XObject
    {
        return node.Accept(
            new ChangeNamespaceVisitor(new RemoveNamespaceMappingManager())
        ) as T;
    }
    
    public class RemoveNamespaceMappingManager : INamespaceMappingManager
    {
        public INamespaceMapping GetMapping(XNamespace fromNs)
        {
            return new RemoveNamespaceMapping();
        }
    
        private class RemoveNamespaceMapping : INamespaceMapping
        {
            public XName ChangeNamespace(XName name)
            {
                return name.LocalName;
            }
    
            public XObject ChangeNamespaceDeclaration(XAttribute node)
            {
                return null;
            }
        }
    }
    

提交回复
热议问题