C# 重写XMLDocument以使用名称空间前缀

C# 重写XMLDocument以使用名称空间前缀,c#,xml,xml-namespaces,C#,Xml,Xml Namespaces,我有一个XMLDocument,当我保存到文件时,它会在大多数元素上重复名称空间,如中所示 <Test> <Test xmlns="http://example.com/schema1"> <Name xmlns="http://example.com/schema2">xyz</Name> <AddressInfo xmlns="http://example.com/schema2">

我有一个XMLDocument,当我保存到文件时,它会在大多数元素上重复名称空间,如中所示

<Test>
    <Test xmlns="http://example.com/schema1">

      <Name xmlns="http://example.com/schema2">xyz</Name>
      <AddressInfo xmlns="http://example.com/schema2">
        <Address>address</Address>
        <ZipCode>zzzz</ZipCode>
      </AddressInfo>
       ...

但是,虽然这会将名称空间添加到头中,但文件的主体是不变的。

使用LINQ to XML执行此操作的一种方法是将名称空间声明添加到根中,然后删除所有现有声明,例如:

var doc = XDocument.Parse(xml);

var existingDeclarations = doc.Descendants()
    .SelectMany(e => e.Attributes().Where(a => a.IsNamespaceDeclaration))
    .ToList();

doc.Root.Add(new XAttribute(XNamespace.Xmlns + "p", "http://example.com/schema2"));

existingDeclarations.Remove();
我不知道使用
XmlDocument
有这么简单的解决方案,但我始终建议使用LINQ to XML,除非您有充分的理由不这样做。

您可以简单地:


我发现了这个问题,它解决了我试图解决的一个问题,但我需要一个更通用的解决方案,所以我开发了下面的扩展方法。它的灵感来自查尔斯·麦格的回答,但正如你所看到的,它远远超出了这个范围。说实话,我很后悔我被卷入了这个问题,因为这是一个非常痛苦的问题,但其他人可能会从中受益

“ValueChangingAttributeNames”参数的业务可能是您可以忽略的。我必须处理它,因为System.Runtime.Serialization.DataContractSerializer生成I:type属性,这些属性将命名空间前缀嵌入属性值中

以下是显示扩展方法用法的代码段:

XName valueChangingAttribName = "{http://www.w3.org/2001/XMLSchema-instance}type";
var xDoc = XDocument.Load(stream);
xDoc.Root.ConsolidateNamespaces(new List<XName> { valueChangingAttribName });
XName值更改属性名=”{http://www.w3.org/2001/XMLSchema-instance}类型”;
var xDoc=XDocument.Load(流);
consolidatenamespace(新列表{valueChangingAttribName});
一个小警告:对于前缀冲突的情况,我设计了将字母表中的字母附加到现有前缀的方法。我觉得不太可能有超过26个不同的名称空间,所有名称空间都被分配了相同的前缀。尽管如此,如果您有这样的情况,那么您需要修改我生成唯一前缀的方法

    /// <summary>
    /// This method finds all namespace declarations on the descendants of the given root XElement
    /// and moves them from the decendant elements to the root element. It is thus possible to 
    /// make the XML string much smaller and more human-readable in cases when there are many
    /// redundant declarations of the same namespace.
    /// </summary>
    /// <param name="xRoot">The element whose descendants are to have their namespaces declarations
    /// removed. Also, the element that is to contain the consolidated namespace declarations</param>
    /// <param name="valueChangingAttribNames">A list of the names of XAttributes whose value
    /// must be updated to reflect changes to the namespace prefixes.
    /// This is designed to handle cases like d7p1 in the example:
    ///     xmlns:d7p1="http://www.w3.org/2001/XMLSchema" i:type="d7p1:int"
    /// generated by System.Runtime.Serialization.DataContractSerializer. In order to treat this
    /// example, the XName of the i:type attribute should be included in the list.  If the value
    /// of the 'valueChangingAttribNames' parameter is null, then no such changes are made to the
    /// values of XAttributes.  The default is null.
    /// </param>
    public static void ConsolidateNamespaces(this XElement xRoot,
                            List<XName> valueChangingAttribNames = null)
    {
        String letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        // Find all XAttributes that represent namespace declarations
        var nsAttributes = xRoot.Descendants().SelectMany(e => e.Attributes())
                    .Where(a => a.IsNamespaceDeclaration).ToList();
        // create groupings by common prefix
        var prefixGroups = nsAttributes.GroupBy(a => a.Name.LocalName);

        // Each time an XAttribute is resolved, we remove it from the list.
        // Loop until all XAttributes are resolved and removed from the list.
        while (nsAttributes.Any())
        {
            // Inner loop repeats until no XAttributes can be resolved without dealing with conflicting
            // prefixes (i.e. same prefix refers to different namespace in different XElements)
            while (nsAttributes.Any())
            {
                // Loop through each XAttribute
                foreach (var attr in nsAttributes.ToList())
                {
                    // If the root already contains a declaration of the same namespace
                    // (regardless of whether the prefix matches between the root and the descendant)
                    if (xRoot.Attributes().Any(a => a.Value == attr.Value))
                    {
                        // We have only to remove the XAttribute from the descendant, and Xml.Linq
                        // framework automatically rectifies prefix on the descendant if necessary.
                        TransferNamespaceToRoot(nsAttributes, xRoot, null, attr,
                                                    valueChangingAttribNames);
                    }
                }
                // Take the first remaining prefix group where there is no prefix name conflict
                var prefixGroup = prefixGroups
                          .FirstOrDefault(g => g.Select(a => a.Value).Distinct().Count() == 1);
                // If no such groups remain, then we're done with the inner loop
                if (prefixGroup == null) break;
                // The list of XAttributes that have matching prefix & namespace
                var attribs = prefixGroup.ToList();
                for (int j = 0; j < attribs.Count; j++)
                    // The first XAttribute must be added to the root, and the rest simply need
                    // to be removed from the descendant.
                    TransferNamespaceToRoot(nsAttributes, xRoot,
                                 (j == 0 ? attribs[j] : null), attribs[j], valueChangingAttribNames);
            }
            // Take the first remaining prefix group. We know there is a prefix name conflict since
            // this group was not processed in the above loop.
            var conflictGroup = prefixGroups.FirstOrDefault();
            if (conflictGroup == null) break;
            // The processing of the previous loop should guarantee that all namespaces in
            // this group are not yet declared in the root.  Each of the conflicting prefixes
            // must be renamed.  If we try to add the existing prefix to the root for any one
            // of the namespaces in this group, then the Xml.Linq framework will incorrectly match
            // the other prefixes in this group to that namespace.
            foreach (var attr in conflictGroup.ToList())
            {
                // If the root already contains a declaration of the same namespace
                // (could only be a previously-processed XAttribute from this same conflict group)
                if (xRoot.Attributes().Any(a => a.Value == attr.Value))
                {
                    // We have only to remove the XAttribute from the descendant, and Xml.Linq
                    // framework automatically rectifies prefix on the descendant if necessary.
                    TransferNamespaceToRoot(nsAttributes, xRoot, null, attr, valueChangingAttribNames);
                    continue;
                }
                // Find a new prefix that doesn't conflict with any existing prefixes
                String newPrefix = attr.Name.LocalName + "_A";
                for (int i = 1; xRoot.Attributes().Any(a => a.Name.LocalName == newPrefix); i++)
                    newPrefix = attr.Name.LocalName + "_" + letters[i];
                // Add the namespace declaration to the root, using the new prefix
                XNamespace ns = attr.Value;
                var newAttr = new XAttribute(XNamespace.Xmlns + newPrefix, attr.Value);
                TransferNamespaceToRoot(nsAttributes, xRoot, newAttr, attr, valueChangingAttribNames);
            }
        }

    }
    /// <summary>
    /// This private method is designed as a helper to public method ConsolidateNamespaces.
    /// The method is designed to remove a given XAttribute from a descendant XElement,
    /// and add a given XAttribute to the root XElement.  The XAttribute to add to the root
    /// may be the same as the item to remove, different than the item to remove, or none,
    /// as appropriate.
    /// </summary>
    /// <param name="nsAttributes">The list of XAttributes that the caller is to process.
    /// This method is designed to remove 'attrToRemove' from the list.</param>
    /// <param name="xRoot">The root XElement to which 'attrToAdd' is added.</param>
    /// <param name="attrToAdd">The XAttribute that is to be added to 'xRoot'.  This may be
    /// the same as or different than 'attrToRemove', or it may be null if the caller does
    /// not need to add anything to the root.</param>
    /// <param name="attrToRemove">The XAttribute that is to be removed from a descendant XElement
    /// and removed from 'nsAttributes'</param>
    /// <param name="valueChangingAttribNames">A list of the names of XAttributes whose value
    /// must be updated to reflect changes to the namespace prefixes.
    /// This is designed to handle cases like d7p1 in the example:
    ///     xmlns:d7p1="http://www.w3.org/2001/XMLSchema" i:type="d7p1:int"
    /// generated by System.Runtime.Serialization.DataContractSerializer. In order to treat this
    /// example, the XName of the i:type attribute should be included in the list.  If the value
    /// of the 'valueChangingAttribNames' parameter is null, then no such changes are made to the
    /// values of XAttributes.  The default is null.
    /// </param>
    private static void TransferNamespaceToRoot(List<XAttribute> nsAttributes, XElement xRoot,
                            XAttribute attrToAdd, XAttribute attrToRemove,
                            List<XName> valueChangingAttribNames)
    {
        if (valueChangingAttribNames != null)
        {
            // Change the value of any 'value changing attributes' that incorporate the prefix.
            // This is designed to handle cases like d7p1 in the example:
            //     <d2p1:Value xmlns:d7p1="http://www.w3.org/2001/XMLSchema" i:type="d7p1:int">
            // that are generated by System.Runtime.Serialization.DataContractSerializer.
            // The Xml.Linq framework does not rectify such cases where the prefix is
            // within the XAttribute vaue.

            String oldPrefix = attrToRemove.Name.LocalName;
            String newPrefix = oldPrefix;
            // If no XAttribute is to be added to the root
            if (attrToAdd == null)
            {
                // find the existing XAttribute in the root for the namespace,
                // and use the prefix that it declares.
                var srcAttr = xRoot.Attributes()
                        .FirstOrDefault(a => a.IsNamespaceDeclaration && a.Value == attrToRemove.Value);
                if (srcAttr != null)
                    newPrefix = srcAttr.Name.LocalName;
            }
            else
                // If a new XAttribute is to be added to the root, then use the prefix it declares
                newPrefix = attrToAdd.Name.LocalName;

            if (oldPrefix != newPrefix)
            {
                foreach (XName attribName in valueChangingAttribNames)
                {
                    // Do string replacement of the prefix in the XAttribute value
                    var vcAttrib = attrToRemove.Parent.Attribute(attribName);
                    vcAttrib.Value = vcAttrib.Value.Replace(attrToRemove.Name.LocalName, newPrefix);
                }
            }
        }

        // Add the XAttribute to the root element
        if (attrToAdd != null)
            xRoot.Add(attrToAdd);
        // Remove the namespace declaration from the descendant
        attrToRemove.Remove();
        // remove the XAttribute from the list of XAttributes to be processed
        nsAttributes.Remove(attrToRemove);
    }
//
///此方法查找给定根元素的子代上的所有命名空间声明
///并将它们从Decentant元素移动到根元素。因此,有可能
///在有许多XML字符串的情况下,使XML字符串更小,更易于人阅读
///相同命名空间的冗余声明。
/// 
///其后代将具有名称空间声明的元素
///删除。另外,要包含合并命名空间声明的元素
///其价值为
///必须更新以反映对命名空间前缀的更改。
///这是为处理示例中类似d7p1的情况而设计的:
///xmlns:d7p1=”http://www.w3.org/2001/XMLSchema“i:type=“d7p1:int”
///由System.Runtime.Serialization.DataContractSerializer生成。为了治疗这个
///例如,i:type属性的XName应该包含在列表中。如果值
///“ValueChangingAttributeNames”参数的值为null,则不会对
///贡品的价值。默认值为空。
/// 
公共静态void ConsolidateNamespaces(此XElement xRoot,
列表值ChangingAttributeNames=null)
{
字符串字母=“abcdefghijklmnopqrstuvxyz”;
//查找表示命名空间声明的所有属性
var nsAttributes=xRoot.subjects().SelectMany(e=>e.Attributes())
.Where(a=>a.IsNamespaceDeclaration).ToList();
//按公共前缀创建分组
var prefixGroups=nsAttributes.GroupBy(a=>a.Name.LocalName);
//每次解析XAttribute时,我们都会将其从列表中删除。
//循环,直到解析所有属性并将其从列表中删除。
while(nsAttributes.Any())
{
//内部循环重复,直到在不处理冲突的情况下无法解析任何属性
//前缀(即相同的前缀表示不同元素中的不同名称空间)
while(nsAttributes.Any())
{
//循环遍历每个属性
foreach(nsAttributes.ToList()中的var attr)
{
//如果根已包含相同命名空间的声明
//(无论前缀在根和子体之间是否匹配)
if(xRoot.Attributes().Any(a=>a.Value==attr.Value))
{
//我们只需从子体中删除XAttribute和Xml.Linq
//如果需要,框架会自动更正子体上的前缀。
TransferNamespaceToRoot(nsAttributes、xRoot、null、attr、,
值更改属性名称);
}
}
//以不存在前缀名称冲突的第一个剩余前缀组为例
var prefixGroup=prefixGroups
.FirstOrDefault(g=>g.Select(a=>a.Value).Distinct().Count()==1);
//如果没有剩下这样的组,那么我们就完成了内部循环
如果(prefixGroup==null)中断;
//具有匹配前缀和命名空间的属性列表
var attribs=prefixGroup.ToList();
对于(int j=0;jdoc.DocumentElement.SetAttribute("xmlns:p", "http://example.com/schema2");
//xpath for selecting all elements in specific namespace :
var xpath = "//*[namespace-uri()='http://example.com/schema2']";
foreach(XmlElement node in doc.SelectNodes(xpath))
{
    node.Prefix = "p";
}
doc.Save("path_to_file.xml");
XName valueChangingAttribName = "{http://www.w3.org/2001/XMLSchema-instance}type";
var xDoc = XDocument.Load(stream);
xDoc.Root.ConsolidateNamespaces(new List<XName> { valueChangingAttribName });
    /// <summary>
    /// This method finds all namespace declarations on the descendants of the given root XElement
    /// and moves them from the decendant elements to the root element. It is thus possible to 
    /// make the XML string much smaller and more human-readable in cases when there are many
    /// redundant declarations of the same namespace.
    /// </summary>
    /// <param name="xRoot">The element whose descendants are to have their namespaces declarations
    /// removed. Also, the element that is to contain the consolidated namespace declarations</param>
    /// <param name="valueChangingAttribNames">A list of the names of XAttributes whose value
    /// must be updated to reflect changes to the namespace prefixes.
    /// This is designed to handle cases like d7p1 in the example:
    ///     xmlns:d7p1="http://www.w3.org/2001/XMLSchema" i:type="d7p1:int"
    /// generated by System.Runtime.Serialization.DataContractSerializer. In order to treat this
    /// example, the XName of the i:type attribute should be included in the list.  If the value
    /// of the 'valueChangingAttribNames' parameter is null, then no such changes are made to the
    /// values of XAttributes.  The default is null.
    /// </param>
    public static void ConsolidateNamespaces(this XElement xRoot,
                            List<XName> valueChangingAttribNames = null)
    {
        String letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        // Find all XAttributes that represent namespace declarations
        var nsAttributes = xRoot.Descendants().SelectMany(e => e.Attributes())
                    .Where(a => a.IsNamespaceDeclaration).ToList();
        // create groupings by common prefix
        var prefixGroups = nsAttributes.GroupBy(a => a.Name.LocalName);

        // Each time an XAttribute is resolved, we remove it from the list.
        // Loop until all XAttributes are resolved and removed from the list.
        while (nsAttributes.Any())
        {
            // Inner loop repeats until no XAttributes can be resolved without dealing with conflicting
            // prefixes (i.e. same prefix refers to different namespace in different XElements)
            while (nsAttributes.Any())
            {
                // Loop through each XAttribute
                foreach (var attr in nsAttributes.ToList())
                {
                    // If the root already contains a declaration of the same namespace
                    // (regardless of whether the prefix matches between the root and the descendant)
                    if (xRoot.Attributes().Any(a => a.Value == attr.Value))
                    {
                        // We have only to remove the XAttribute from the descendant, and Xml.Linq
                        // framework automatically rectifies prefix on the descendant if necessary.
                        TransferNamespaceToRoot(nsAttributes, xRoot, null, attr,
                                                    valueChangingAttribNames);
                    }
                }
                // Take the first remaining prefix group where there is no prefix name conflict
                var prefixGroup = prefixGroups
                          .FirstOrDefault(g => g.Select(a => a.Value).Distinct().Count() == 1);
                // If no such groups remain, then we're done with the inner loop
                if (prefixGroup == null) break;
                // The list of XAttributes that have matching prefix & namespace
                var attribs = prefixGroup.ToList();
                for (int j = 0; j < attribs.Count; j++)
                    // The first XAttribute must be added to the root, and the rest simply need
                    // to be removed from the descendant.
                    TransferNamespaceToRoot(nsAttributes, xRoot,
                                 (j == 0 ? attribs[j] : null), attribs[j], valueChangingAttribNames);
            }
            // Take the first remaining prefix group. We know there is a prefix name conflict since
            // this group was not processed in the above loop.
            var conflictGroup = prefixGroups.FirstOrDefault();
            if (conflictGroup == null) break;
            // The processing of the previous loop should guarantee that all namespaces in
            // this group are not yet declared in the root.  Each of the conflicting prefixes
            // must be renamed.  If we try to add the existing prefix to the root for any one
            // of the namespaces in this group, then the Xml.Linq framework will incorrectly match
            // the other prefixes in this group to that namespace.
            foreach (var attr in conflictGroup.ToList())
            {
                // If the root already contains a declaration of the same namespace
                // (could only be a previously-processed XAttribute from this same conflict group)
                if (xRoot.Attributes().Any(a => a.Value == attr.Value))
                {
                    // We have only to remove the XAttribute from the descendant, and Xml.Linq
                    // framework automatically rectifies prefix on the descendant if necessary.
                    TransferNamespaceToRoot(nsAttributes, xRoot, null, attr, valueChangingAttribNames);
                    continue;
                }
                // Find a new prefix that doesn't conflict with any existing prefixes
                String newPrefix = attr.Name.LocalName + "_A";
                for (int i = 1; xRoot.Attributes().Any(a => a.Name.LocalName == newPrefix); i++)
                    newPrefix = attr.Name.LocalName + "_" + letters[i];
                // Add the namespace declaration to the root, using the new prefix
                XNamespace ns = attr.Value;
                var newAttr = new XAttribute(XNamespace.Xmlns + newPrefix, attr.Value);
                TransferNamespaceToRoot(nsAttributes, xRoot, newAttr, attr, valueChangingAttribNames);
            }
        }

    }
    /// <summary>
    /// This private method is designed as a helper to public method ConsolidateNamespaces.
    /// The method is designed to remove a given XAttribute from a descendant XElement,
    /// and add a given XAttribute to the root XElement.  The XAttribute to add to the root
    /// may be the same as the item to remove, different than the item to remove, or none,
    /// as appropriate.
    /// </summary>
    /// <param name="nsAttributes">The list of XAttributes that the caller is to process.
    /// This method is designed to remove 'attrToRemove' from the list.</param>
    /// <param name="xRoot">The root XElement to which 'attrToAdd' is added.</param>
    /// <param name="attrToAdd">The XAttribute that is to be added to 'xRoot'.  This may be
    /// the same as or different than 'attrToRemove', or it may be null if the caller does
    /// not need to add anything to the root.</param>
    /// <param name="attrToRemove">The XAttribute that is to be removed from a descendant XElement
    /// and removed from 'nsAttributes'</param>
    /// <param name="valueChangingAttribNames">A list of the names of XAttributes whose value
    /// must be updated to reflect changes to the namespace prefixes.
    /// This is designed to handle cases like d7p1 in the example:
    ///     xmlns:d7p1="http://www.w3.org/2001/XMLSchema" i:type="d7p1:int"
    /// generated by System.Runtime.Serialization.DataContractSerializer. In order to treat this
    /// example, the XName of the i:type attribute should be included in the list.  If the value
    /// of the 'valueChangingAttribNames' parameter is null, then no such changes are made to the
    /// values of XAttributes.  The default is null.
    /// </param>
    private static void TransferNamespaceToRoot(List<XAttribute> nsAttributes, XElement xRoot,
                            XAttribute attrToAdd, XAttribute attrToRemove,
                            List<XName> valueChangingAttribNames)
    {
        if (valueChangingAttribNames != null)
        {
            // Change the value of any 'value changing attributes' that incorporate the prefix.
            // This is designed to handle cases like d7p1 in the example:
            //     <d2p1:Value xmlns:d7p1="http://www.w3.org/2001/XMLSchema" i:type="d7p1:int">
            // that are generated by System.Runtime.Serialization.DataContractSerializer.
            // The Xml.Linq framework does not rectify such cases where the prefix is
            // within the XAttribute vaue.

            String oldPrefix = attrToRemove.Name.LocalName;
            String newPrefix = oldPrefix;
            // If no XAttribute is to be added to the root
            if (attrToAdd == null)
            {
                // find the existing XAttribute in the root for the namespace,
                // and use the prefix that it declares.
                var srcAttr = xRoot.Attributes()
                        .FirstOrDefault(a => a.IsNamespaceDeclaration && a.Value == attrToRemove.Value);
                if (srcAttr != null)
                    newPrefix = srcAttr.Name.LocalName;
            }
            else
                // If a new XAttribute is to be added to the root, then use the prefix it declares
                newPrefix = attrToAdd.Name.LocalName;

            if (oldPrefix != newPrefix)
            {
                foreach (XName attribName in valueChangingAttribNames)
                {
                    // Do string replacement of the prefix in the XAttribute value
                    var vcAttrib = attrToRemove.Parent.Attribute(attribName);
                    vcAttrib.Value = vcAttrib.Value.Replace(attrToRemove.Name.LocalName, newPrefix);
                }
            }
        }

        // Add the XAttribute to the root element
        if (attrToAdd != null)
            xRoot.Add(attrToAdd);
        // Remove the namespace declaration from the descendant
        attrToRemove.Remove();
        // remove the XAttribute from the list of XAttributes to be processed
        nsAttributes.Remove(attrToRemove);
    }