XDocument duplicate namespace with different Local Name

后端 未结 2 1231
深忆病人
深忆病人 2021-01-21 23:11

I have an XML document that looks like this:

    

        
相关标签:
2条回答
  • 2021-01-21 23:26

    In theory, it shouldn't matter whether you use the prefix p1:StoreGeneratedPattern="Computed" or annotation:StoreGeneratedPattern="Computed", since these mean exactly the same thing -- an element with an expanded XML name of {http://schemas.microsoft.com/ado/2009/02/edm/annotation}StoreGeneratedPattern. If your receiving XML parser (or QA department?) has a problem dealing with this, the easiest fix may be to fix the parser to conform with the standard.

    That being said, from the reference source for XElement, it turns out that the namespace/prefix attribute pairs are pushed onto a push-down stack in order of addition while writing, then checked for matches against the element namespace from top to bottom of the stack -- effectively doing the match in reverse order in which the attributes are added. So if you wanted to use the prefix annotation, you could simply permute the order of duplicated namespaces in the root element. (For details, see here.)

    But, you wrote that you cannot change or tamper the root node. Thus, you're going to be forced to do a bit of work: you're going to need to create your own wrapper subclass of XmlWriter and do the prefix remapping yourself.

    First, a non-abstract subclass of XmlWriter that wraps a "real" 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);
        }
    }
    

    Next, subclass that allows attribute namespace prefixes to be remapped:

    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.
        }
    }
    

    Then finally you would use it like:

            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);
    

    As you can see this only remaps attribute prefixes, but it could clearly be extended to remap element prefixes by overriding WriteStartElement(string prefix, string localName, string ns).

    Working fiddle.

    0 讨论(0)
  • 2021-01-21 23:41

    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..

    0 讨论(0)
提交回复
热议问题