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

时间:2015-08-30 22:33:49

标签: c# xml linq entity-framework linq-to-xml

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

    <Schema Namespace="BBSF_Model" Alias="Self"
      p1:UseStrongSpatialTypes="false"
      xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation"
      xmlns:p1="http://schemas.microsoft.com/ado/2009/02/edm/annotation"
      xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
    <EntityType Name="Customer">
        <Property Name="Id" Type="Guid" Nullable="false" />
    </EntityType>
  </Schema>

我使用以下代码修改文档的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 Name="Id" Type="Guid" Nullable="false" p1:StoreGeneratedPattern="Computed" />

但我想要的是:

  <Property Name="Id" Type="Guid" Nullable="false" annotation:StoreGeneratedPattern="Computed" />

问题是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"

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

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

2 个答案:

答案 0 :(得分:1)

理论上,使用前缀p1:StoreGeneratedPattern="Computed"annotation:StoreGeneratedPattern="Computed"并不重要,因为这些完全相同 - 一个带有{http://schemas.microsoft.com/ado/2009/02/edm/annotation}StoreGeneratedPattern的{​​{3}}。如果您的接收XML解析器(或QA部门?)在处理此问题时遇到问题,最简单的解决方法可能是将解析器修复为expanded XML name

话虽如此,从XElement annotation开始,事实证明命名空间/前缀属性对在写入时按照添加的顺序被推送到conform with the standard,然后检查匹配来自堆栈的reference source的元素名称空间 - 有效地按照添加属性的相反顺序进行匹配。因此,如果您想使用前缀XmlWriter,则可以简单地置换根元素中重复名称空间的顺序。 (有关详细信息,请参阅push-down stack。)

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

首先,XmlWriter的一个非抽象子类包含了一个真实的&#34; 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<XName> elements = new Stack<XName>();
    readonly Func<string, string, string, Stack<XName>, string> attributePrefixMap;

    public PrefixSelectingXmlWriterProxy(XmlWriter baseWriter, Func<string, string, string, Stack<XName>, 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<XName> 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);

然后最后你会像:

一样使用它
test

正如您所看到的,这只会重新映射属性前缀,但可以通过覆盖top to bottom显式扩展为重新映射元素前缀。

工作here

答案 1 :(得分:0)

I found a solution all thanks to your ideas. The general Idea is I changed the value of p1 to anything added my new attribute then returned p1 to its original value even before saving the XDocument.

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/";

Another solution that worked is swapping the order or the declarations at the root node (declare p1 before annotation) like below:

<Schema Namespace="BBSF_Model" Alias="Self"
  p1:UseStrongSpatialTypes="false"
  xmlns:p1="http://schemas.microsoft.com/ado/2009/02/edm/annotation"
  xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" 
  xmlns="http://schemas.microsoft.com/ado/2009/11/edm">

In general both look like cheap solutions..