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

时间:2012-09-25 20:24:11

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

我在所有相同的架构/结构之后获得了数千个XML文件。 我实现了IXmlSerializable,因此我自己正在阅读元素和属性。

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

现在,如果我没有指定命名空间,它会抛出[xmlns:ns0 =“http://tempuri.org/abcd.xsd”不是预期的]错误。

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

这可能吗?

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

谢谢!

4 个答案:

答案 0 :(得分:1)

是的,有可能。当您调用Deserialize的{​​{1}}方法时,您可以指定XmlSerializer个实例。

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

XmlTextReader

注意:如果文档的默认命名空间不同(即各个标记不具有不同的命名空间),则使用标准{将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的{​​1}}就足够了。

Namespaces

答案 1 :(得分: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
/// 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));
}

答案 2 :(得分:0)

您可以使用此代码

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

答案 3 :(得分:0)

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

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

使用XmlWrappingReader(从Alterant在StackOverflow上的答案到How do I create a XmlTextReader that ignores Namespaces and does not check characters)简化解决方案。

/// <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文档具有不同的根名称空间,或者根本没有指定根名称空间,那么现在我仍然可以反序列化它。