XDocument使用不同的本地名称重复命名空间

我有一个XML文档,如下所示:

     

我使用下面的代码来修改文档的Property Element:

  XElement csdlEentity = csdlDoc.Root.Descendants() .Where(d => d.Name.LocalName == "EntityType") .FirstOrDefault(e => e.Attribute("Name").Value == "Customer"); var csdlProperty = csdlEentity.Descendants() .Where(d => d.Name.LocalName == "Property") .FirstOrDefault(e => e.Attribute("Name").Value == "Id"); XNamespace annotation = "http://schemas.microsoft.com/ado/2009/02/edm/annotation"; var attrib = new XAttribute(annotation + "StoreGeneratedPattern", "Computed"); csdlProperty.Add(attrib); 

当我保存XDocument时,Property Element看起来像:

   

不过我想要的是:

   

问题是xmlns“ http://schemas.microsoft.com/ado/2009/02/edm/annotation ”在文档的根节点中被引用两次,带有两个不同的别名/ LocalNames(注释,p1)

  xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" xmlns:p1="http://schemas.microsoft.com/ado/2009/02/edm/annotation" 

我无法更改或篡改根节点。

如何保存文档或更新属性元素以提供所需的输出?

从理论上讲,使用前缀p1:StoreGeneratedPattern="Computed"annotation:StoreGeneratedPattern="Computed" ,因为这些意思完全相同 – 一个扩展的XML名称为{http://schemas.microsoft.com/ado/2009/02/edm/annotation}StoreGeneratedPattern的元素{http://schemas.microsoft.com/ado/2009/02/edm/annotation}StoreGeneratedPattern 。 如果您的接收XML解析器(或QA部门?)在处理此问题时遇到问题,最简单的修复方法可能是修复解析器以符合标准 。

话虽这么说,从XElement的引用源 ,事实certificate,命名空间/前缀属性对在写入时按照添加的顺序被推送到下推式堆栈 ,然后从上到下检查与元素命名空间的匹配。 stack – 以相反的顺序有效地进行匹配,其中添加了属性。 因此,如果您想使用前缀annotation ,则可以简单地置换根元素中重复名称空间的顺序。 (有关详细信息,请参阅此处 。)

但是,您写道,您无法更改或篡改根节点 。 因此,您将被迫做一些工作:您将需要创建自己的XmlWriter包装器子类并自己重新映射前缀。

首先, XmlWriter一个非抽象子类包装了一个“真正的” XmlWriter

 public class XmlWriterProxy : XmlWriter { readonly XmlWriter baseWriter; public XmlWriterProxy(XmlWriter baseWriter) { if (baseWriter == null) throw new ArgumentNullException(); this.baseWriter = baseWriter; } protected virtual bool IsSuspended { get { return false; } } public override void Close() { baseWriter.Close(); } public override void Flush() { baseWriter.Flush(); } public override string LookupPrefix(string ns) { return baseWriter.LookupPrefix(ns); } public override void WriteBase64(byte[] buffer, int index, int count) { if (IsSuspended) return; baseWriter.WriteBase64(buffer, index, count); } public override void WriteCData(string text) { if (IsSuspended) return; baseWriter.WriteCData(text); } public override void WriteCharEntity(char ch) { if (IsSuspended) return; baseWriter.WriteCharEntity(ch); } public override void WriteChars(char[] buffer, int index, int count) { if (IsSuspended) return; baseWriter.WriteChars(buffer, index, count); } public override void WriteComment(string text) { if (IsSuspended) return; baseWriter.WriteComment(text); } public override void WriteDocType(string name, string pubid, string sysid, string subset) { if (IsSuspended) return; baseWriter.WriteDocType(name, pubid, sysid, subset); } public override void WriteEndAttribute() { if (IsSuspended) return; baseWriter.WriteEndAttribute(); } public override void WriteEndDocument() { if (IsSuspended) return; baseWriter.WriteEndDocument(); } public override void WriteEndElement() { if (IsSuspended) return; baseWriter.WriteEndElement(); } public override void WriteEntityRef(string name) { if (IsSuspended) return; baseWriter.WriteEntityRef(name); } public override void WriteFullEndElement() { if (IsSuspended) return; baseWriter.WriteFullEndElement(); } public override void WriteProcessingInstruction(string name, string text) { if (IsSuspended) return; baseWriter.WriteProcessingInstruction(name, text); } public override void WriteRaw(string data) { if (IsSuspended) return; baseWriter.WriteRaw(data); } public override void WriteRaw(char[] buffer, int index, int count) { if (IsSuspended) return; baseWriter.WriteRaw(buffer, index, count); } public override void WriteStartAttribute(string prefix, string localName, string ns) { if (IsSuspended) return; baseWriter.WriteStartAttribute(prefix, localName, ns); } public override void WriteStartDocument(bool standalone) { baseWriter.WriteStartDocument(standalone); } public override void WriteStartDocument() { baseWriter.WriteStartDocument(); } public override void WriteStartElement(string prefix, string localName, string ns) { if (IsSuspended) return; baseWriter.WriteStartElement(prefix, localName, ns); } public override WriteState WriteState { get { return baseWriter.WriteState; } } public override void WriteString(string text) { if (IsSuspended) return; baseWriter.WriteString(text); } public override void WriteSurrogateCharEntity(char lowChar, char highChar) { if (IsSuspended) return; baseWriter.WriteSurrogateCharEntity(lowChar, highChar); } public override void WriteWhitespace(string ws) { if (IsSuspended) return; baseWriter.WriteWhitespace(ws); } } 

接下来,允许重新映射属性名称空间前缀的子类:

 public class PrefixSelectingXmlWriterProxy : XmlWriterProxy { readonly Stack elements = new Stack(); readonly Func, string> attributePrefixMap; public PrefixSelectingXmlWriterProxy(XmlWriter baseWriter, Func, string> attributePrefixMap) : base(baseWriter) { if (attributePrefixMap == null) throw new NullReferenceException(); this.attributePrefixMap = attributePrefixMap; } public override void WriteStartAttribute(string prefix, string localName, string ns) { prefix = attributePrefixMap(prefix, localName, ns, elements); base.WriteStartAttribute(prefix, localName, ns); } public override void WriteStartElement(string prefix, string localName, string ns) { base.WriteStartElement(prefix, localName, ns); elements.Push(XName.Get(localName, ns)); } public override void WriteEndElement() { base.WriteEndElement(); elements.Pop(); // Pop after base.WriteEndElement() lets the base class throw an exception on a stack error. } } 

最后你会用它像:

  string xml; using (var sw = new StringWriter()) { using (var xmlWriter = XmlWriter.Create(sw, new XmlWriterSettings { Indent = true, IndentChars = " " })) using (var xmlWriterProxy = new PrefixSelectingXmlWriterProxy(xmlWriter, (string prefix, string localName, string ns, Stack parents) => { if (localName == "StoreGeneratedPattern" && ns == annotation && parents.Peek() == XName.Get("Property", "http://schemas.microsoft.com/ado/2009/11/edm")) return "annotation"; return prefix; }) ) { csdlDoc.WriteTo(xmlWriterProxy); } xml = sw.ToString(); } Debug.WriteLine(xml); 

正如您所看到的,这只重新映射了属性前缀,但它可以通过重写WriteStartElement(string prefix, string localName, string ns )显然可以扩展为重新映射元素前缀。

工作小提琴 。

我找到了解决方案,这完全归功于您的想法。 一般的想法是我将p1的值更改为添加了我的新属性的任何内容,然后在保存XDocument之前将p1返回到其原始值。

 XAttribute p1 = csdlDoc.Root.Attributes() .First(a => a.IsNamespaceDeclaration && a.Name.LocalName == "p1"); p1.Value = "http://schemas.microsoft.com/ado/2009/02/edm/annotation/TEMP"; ...... // the same update now works as there is only one xmlns XNamespace annotation = "http://schemas.microsoft.com/ado/2009/02/edm/annotation"; var attrib = new XAttribute(annotation + "StoreGeneratedPattern", "Computed"); csdlProperty.Add(attrib); p1.Value = "http://schemas.microsoft.com/ado/2009/02/edm/annotation/"; 

另一个有效的解决方案是交换根节点上的顺序或声明(在注释之前声明p1),如下所示:

  

一般来说,两者看起来像廉价的解