.NET XML反序列化忽略命名空间

12

我有成千上万个遵循相同模式/结构的XML文件。我实现了IXmlSerializable,因此自己读取元素和属性。

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

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

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

这可行吗?

我希望尽可能避免修改文件。

谢谢!


1
你考虑过先加载XML以获取命名空间,然后将其传递到XmlSerializer吗? - Steven Doggart
1
@StevenDoggart 是的,我试过了,但在开始绕开它之前,我想知道是否有更“合适”的方法。看起来很傻,如果你不忽略命名空间就会出现异常:S - user1698428
是的,这是一个非常好的问题,我也非常好奇是否有答案。 - Steven Doggart
4个回答

2
是的,这是可能的。当您调用XmlSerializerDeserialize方法时,可以指定一个XmlTextReader实例。这个Cheeso在相关C#问题上的回答展示了如何创建一个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

注意:如果只是文档的默认命名空间不同(即,各个标签没有不同的命名空间),使用带有 Namespaces 属性设置为 False 的标准 TextXmlReader 就足够了。
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

0
为了阐述Heinzi的答案,我需要更改默认命名空间(技术上,根元素的命名空间),以便我可以使用XmlAttributeOverrides对我没有控制权的类层次结构进行反序列化。作为其中的一部分,我不得不将XmlRootAttribute属性分配给第一个类。问题在于,XmlSerializer期望Namespace值与XmlRootAttribute的命名空间匹配,以便反序列化文档,这是无法保证的。
通过以下继承自XmlReader的类,可以为反序列化器分配已知值的根元素命名空间。可以强制阅读器的命名空间与XmlRootAttribute属性的命名空间匹配(即使它是空字符串)。
简化解决方案是使用Alterant在StackOverflow上的答案中的XmlWrappingReader(如何创建忽略命名空间并且不检查字符的XmlTextReader)
/// <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文档具有不同的根命名空间或根本没有指定命名空间,现在我仍然可以将其反序列化。

0

这不是关于如何告诉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
/// https://dev59.com/rnNA5IYBdhLWcg3wX8rk
/// 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));
}

-1

你可以使用以下代码从 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);
                }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接