【问题标题】:.NET XML Deserialization ignore namespaces.NET XML 反序列化忽略命名空间
【发布时间】:2012-09-17 09:58:12
【问题描述】:

我得到了数千个遵循相同架构/结构的 XML 文件。 我实现了 IXmlSerializable,因此我自己读取元素和属性。

我的问题是这些文件每个都使用不同的假命名空间。这些文件来自其他来源,所以我无法更改:D 此外,这些命名空间太多了,我无法构建一个可能的命名空间数组并将其传递给 xmlserializer。

现在,如果我不指定命名空间,它会抛出 [xmlns:ns0="http://tempuri.org/abcd.xsd" was not expected] 错误。

我希望能够告诉序列化程序在反序列化我的对象时简单地忽略命名空间并触发 ReadXML。或者只是能够告诉它接受任何“http://tempuri.org/”命名空间。

这可能吗?

我想尽可能避免修改文件。

谢谢!

【问题讨论】:

  • 您是否考虑过先加载 XML 以获取命名空间,然后将其传递给 XmlSerializer?
  • @StevenDoggart 是的,我做到了,但我想知道在我开始解决它之前是否有更“合适”的方法来做到这一点。你不能忽略命名空间而不得到异常似乎很愚蠢:S
  • 是的,这是一个很好的问题,我很好奇是否也有答案。

标签: xml vb.net xml-serialization xml-namespaces


【解决方案1】:

是的,这是可能的。当您调用XmlSerializerDeserialize 方法时,您可以指定一个XmlTextReader 实例。

This answer by Cheeso on a related C# question 展示了如何创建一个XmlTextReader,它会忽略 XML 文件中出现的任何命名空间。我冒昧地将他的想法转化为 VB,并根据您的要求创建了一个简单的概念验证示例:

Imports System.IO
Imports System.Text
Imports System.Xml
Imports System.Xml.Serialization

' Helper class
Class NamespaceIgnorantXmlTextReader
    Inherits XmlTextReader

    Public Sub New(stream As Stream)
        MyBase.New(stream)
    End Sub

    Public Overrides ReadOnly Property NamespaceURI As String
        Get
            Return ""
        End Get
    End Property
End Class

' Serializable class
Public Class ExampleClass
    Public Property MyProperty As String
End Class

' Example
Module Module1
    Sub Main()
        Dim testXmlStream = New MemoryStream(Encoding.UTF8.GetBytes(
            "<ExampleClass xmlns=""http://tempuri.org/SomePhonyNamespace1.xsd"" 
                           xmlns:ph2=""http://tempuri.org/SomePhonyNamespace2.xsd"">
                 <ph2:MyProperty>SomeValue</ph2:MyProperty>
             </ExampleClass>"))

        Dim serializer As New XmlSerializer(GetType(ExampleClass))
        Dim reader As New NamespaceIgnorantXmlTextReader(testXmlStream)
        Dim example = DirectCast(serializer.Deserialize(reader), ExampleClass)

        Console.WriteLine(example.MyProperty)   ' prints SomeValue
    End Sub
End Module

注意:如果只是文档的默认命名空间不同(即各个标签没有不同的命名空间),请使用标准TextXmlReader 并将Namespaces 属性设置为False 就够了。

Imports System.IO
Imports System.Text
Imports System.Xml
Imports System.Xml.Serialization

' Serializable Class
Public Class ExampleClass
    Public Property MyProperty As String
End Class

' Example
Module Module1
    Sub Main()
        Dim testXmlStream = New MemoryStream(Encoding.UTF8.GetBytes(
            "<ExampleClass xmlns=""http://tempuri.org/SomePhonyNamespace1.xsd"">
                 <MyProperty>SomeValue</MyProperty>
             </ExampleClass>"))

        Dim serializer As New XmlSerializer(GetType(ExampleClass))
        Dim reader As New XmlTextReader(testXmlStream)
        reader.Namespaces = False
        Dim example = DirectCast(serializer.Deserialize(reader), ExampleClass)

        Console.WriteLine(example.MyProperty)   ' prints SomeValue
    End Sub
End Module

【讨论】:

    【解决方案2】:

    这不是您关于如何告诉 XmlSerialiser 忽略名称空间的问题的答案,而是一种解决方法。在序列化之前,您可以使用 xslt 转换从 xml 中剥离命名空间。

    <?xml version="1.0" encoding="utf-8"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
      <xsl:template match="/|comment()|processing-instruction()">
        <xsl:copy>
          <xsl:apply-templates/>
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match="*">
        <xsl:element name="{local-name()}">
          <xsl:apply-templates select="@*|node()"/>
        </xsl:element>
      </xsl:template>
    
      <xsl:template match="@*">
        <xsl:attribute name="{local-name()}">
          <xsl:value-of select="."/>
        </xsl:attribute>
      </xsl:template>
    
    </xsl:stylesheet>
    

    几个扩展方法作为这个的助手,可能会有点棘手,但我会尝试:

    /// <summary>
    /// Transforms the xmldocument to remove all namespaces using xslt
    /// http://stackoverflow.com/questions/987135/how-to-remove-all-namespaces-from-xml-with-c
    /// http://msdn.microsoft.com/en-us/library/42d26t30.aspx
    /// </summary>
    /// <param name="xmlDocument"></param>
    /// <param name="indent"></param>
    public static XmlDocument RemoveXmlNameSpaces(this XmlDocument xmlDocument, bool indent = true)
    {
        return xmlDocument.ApplyXsltTransform(Properties.Resources.RemoveNamespaces, indent);
    }
    
    public static XmlDocument ApplyXsltTransform(this XmlDocument xmlDocument, string xsltString,bool indent= true)
    {
        var xslCompiledTransform = new XslCompiledTransform();
        Encoding encoding;
        if (xmlDocument.GetEncoding() == null)
        {
            encoding = DefaultEncoding;
        }
        else
        {
            encoding = Encoding.GetEncoding(xmlDocument.GetXmlDeclaration().Encoding);
        }
        using (var xmlTextReader = xsltString.GetXmlTextReader())
        {
            xslCompiledTransform.Load(xmlTextReader);
        }
        XPathDocument xPathDocument = null;
        using (XmlTextReader xmlTextReader = xmlDocument.OuterXml.GetXmlTextReader())
        {
            xPathDocument = new XPathDocument(xmlTextReader);
        }
        using (var memoryStream = new MemoryStream())
        {
            using (XmlWriter xmlWriter = XmlWriter.Create(memoryStream, new XmlWriterSettings()
                {
                    Encoding = encoding,
                    Indent = indent
                }))
            {
                xslCompiledTransform.Transform(xPathDocument, xmlWriter);
            }
            memoryStream.Position = 0;
            using (var streamReader = new StreamReader(memoryStream, encoding))
            {
                string readToEnd = streamReader.ReadToEnd();
                return readToEnd.ToXmlDocument();
            }
        }
    }
    
    public static Encoding GetEncoding(this XmlDocument xmlDocument)
    {
        XmlDeclaration xmlDeclaration = xmlDocument.GetXmlDeclaration();
        if (xmlDeclaration == null)
            return null;
        return Encoding.GetEncoding(xmlDeclaration.Encoding);
    }
    
    public static XmlDeclaration GetXmlDeclaration(this XmlDocument xmlDocument)
    {
        XmlDeclaration xmlDeclaration = null;
        if (xmlDocument.HasChildNodes)
            xmlDeclaration = xmlDocument.FirstChild as XmlDeclaration;
        return xmlDeclaration;
    }
    
    public static XmlTextReader GetXmlTextReader(this string xml)
    {
        return new XmlTextReader(new StringReader(xml));
    }
    

    【讨论】:

      【解决方案3】:

      为了解释 Heinzi 的回答,我需要更改默认命名空间(从技术上讲,是根元素的命名空间),以便可以使用应用于我无法控制的类层次结构的 XmlAttributeOverrides 反序列化文档。作为其中的一部分,我必须为第一类分配一个XmlRootAttribute 属性。问题是 XmlSerializer 期望 Namespace 值与 XmlRootAttribute 的命名空间匹配,以便反序列化文档,这是无法保证的。

      使用以下派生自XmlReader 的类,可以为反序列化程序为根元素的命名空间分配一个已知值。读者的命名空间可以强制匹配XmlRootAttribute属性的命名空间(即使它是一个空字符串)。

      简化解决方案是使用 Alterant 在 StackOverflow 上对 How do I create a XmlTextReader that ignores Namespaces and does not check characters 的回答中的 XmlWrappingReader

      /// <summary>
      /// XML document reader replaces the namespace of the root element.
      /// </summary>
      public class MyXmlReader : Mvp.Xml.Common.XmlWrappingReader
      {
          // Namespace of the document's root element. Read from document.
          private string rootNamespace = "";
      
          /// <summary>
          /// Get or set the target namespace to use when deserializing.
          /// </summary>
          public string TargetNamespace { get; set; }
      
          /// <summary>
          /// Initialize a new instance of the MXmlReader class.
          /// </summary>
          /// <param name="reader">XmlReader instance to modify.</param>
          public MyXmlReader(XmlReader reader) : base(reader)
          {
              TargetNamespace = "";
          }
      
          /// <summary>
          /// Return the namespace of the XML node. Substitute the target namespace if it matches the namespace of the root element.
          /// </summary>
          public override string NamespaceURI
          {
              get
              {
                  if (Depth == 0 && NodeType == XmlNodeType.Element)
                  {
                      // Save the namespace from the document's root element.
                      rootNamespace = base.NamespaceURI;
                  }
      
                  if (base.NamespaceURI == rootNamespace)
                  {
                      // Substitute the schema's targetNamespace for the root namespace.
                      return TargetNamespace;
                  }
      
                  // Use the native namespace of the XML node.
                  return base.NamespaceURI;
              }
          }
      }
      

      我实例化了 MyXmlReader 并使用它反序列化为一个标有 XmlRootAttribute(ElementName = "DocumentRoot", Namespace = "http://my.target.namespace") 的对象:

      var reader = new MyXmlReader(XmlReader.Create(stream));
      reader.TargetNamespace = "http://my.target.namespace";
      
      // Deserialize using the defined XML attribute overrides that can
      // supply XML serialization attributes to types at runtime.
      Type t = typeof(SomeDeserializedObject);
      var xo = SomeDeserializedObject.GetXmlAttributeOverrides();
      XmlSerializer serializer = new XmlSerializer(t, xo);
      SomeDeserializedObject o = (SomeDeserializedObject)serializer.Deserialize(reader);
      

      如果我导入的 XML 文档有不同的根命名空间,或者根本没有指定一个,现在我仍然可以反序列化它。

      【讨论】:

        【解决方案4】:

        您可以使用此代码从 xml 文件中删除命名空间

        using (FileStream stream = new FileStream("FilePath",FileMode.Create))
                        {
                            XmlSerializer serializer = new XmlSerializer(typeof(YourClass));
                            XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
                            ns.Add("", "");                    
                            serializer.Serialize(stream," Your Object to Serialize",ns);
                        }
        

        【讨论】:

          猜你喜欢
          • 2021-02-17
          • 1970-01-01
          • 2015-11-03
          • 2011-12-05
          • 2015-02-18
          • 2021-01-23
          • 1970-01-01
          • 2010-10-26
          • 2013-08-17
          相关资源
          最近更新 更多