我有一个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>
...
是否可以修改此文件,以便在整个文档中使用名称空间前缀,例如
<Test xmlns="http://example.com/schema1" xmlns:p="http://example.com/schema2" >
<p:Name>xyz</p:Name>
<p:AddressInfo">
<p:Address>address</p:Address>
<p:ZipCode>zzzz</p:ZipCode>
</p:AddressInfo>
...
我尝试过添加
doc.DocumentElement.SetAttribute("xmlns:p", "http://example.com/schema2");
但是虽然这会将名称空间添加到标题中,但文件的主体不会改变。
答案 0 :(得分:1)
使用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,除非你有充分的理由不这样做。
答案 1 :(得分:1)
您可以简单地change XmlElement.Prefix
property value:
doc.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");
答案 2 :(得分:0)
我找到了这个问题,并且解决了我试图解决的问题,但是我需要一个更通用的解决方案,因此我在下面开发了扩展方法。它的灵感来自查尔斯·马格(Charles Mager)的答案,但是正如您所看到的,它远远超出了这个范围。坦白说,我很后悔被这个问题困扰,因为很难弄清楚,但其他人可能会从中受益。
您可能会忽略'valueChangingAttribNames'参数的事务。我不得不处理它,因为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 });
一个小警告:对于前缀冲突的情况,我设计了将字母添加到现有前缀的方法。如果您有超过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);
}