2013-12-19 35 views
3

我有一個XSD架構和用於在運行時構建組件的XML文件。這些對象可以具有自定義設置。在XSD文件,這被定義爲:如何使用<x/>而非<x>來反序列化一個空的XML元素</x>

<xs:complexType name="ComponentSettings"> 
    <xs:sequence> 
    <xs:any minOccurs="0"/> 
    </xs:sequence> 
</xs:complexType> 

,並在XSD實例化爲:

<xs:complexType name="Component"> 
    <xs:sequence> 
    <xs:element name="Version" type="VersionRecord"/> 
    <xs:element name="Settings" type="ComponentSettings"/> 
    </xs:sequence> 
    <xs:attribute name="Name" type="xs:string" use="required"/> 
</xs:complexType> 

反序列化XML文件時,我邂逅了一個有趣的問題。如果我有一個沒有設置的組件,並且XML看起來像這樣:

<Settings></Settings> 

我沒有問題。

然而,如果在XML看起來是這樣的:

<Settings/> 

然後反序列化將失敗默默跟隨這個標記和我結束了一個不完整的記錄。

我使用xsd2code產生下面的代碼:

<System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.18060"), _ 
System.SerializableAttribute(), _ 
System.ComponentModel.DesignerCategoryAttribute("code"), _ 
System.Xml.Serialization.XmlRootAttribute([Namespace]:="", IsNullable:=True)> _ 
Partial Public Class Component 
    Implements System.ComponentModel.INotifyPropertyChanged 

    Private versionField As VersionRecord 

    Private settingsField As System.Xml.XmlElement 

    Private nameField As String 

    Private Shared sSerializer As System.Xml.Serialization.XmlSerializer 

    '''<summary> 
    '''Component class constructor 
    '''</summary> 
    Public Sub New() 
     MyBase.New() 
     Me.versionField = New VersionRecord() 
    End Sub 

    <System.Xml.Serialization.XmlElementAttribute(Order:=0)> _ 
    Public Property Version() As VersionRecord 
     Get 
      Return Me.versionField 
     End Get 
     Set(value As VersionRecord) 
      If (Not (Me.versionField) Is Nothing) Then 
       If (versionField.Equals(value) <> True) Then 
        Me.versionField = value 
        Me.OnPropertyChanged("Version") 
       End If 
      Else 
       Me.versionField = value 
       Me.OnPropertyChanged("Version") 
      End If 
     End Set 
    End Property 

    <System.Xml.Serialization.XmlElementAttribute(Order:=2)> _ 
    Public Property Settings() As System.Xml.XmlElement 
     Get 
      Return Me.settingsField 
     End Get 
     Set(value As System.Xml.XmlElement) 
      If (Not (Me.settingsField) Is Nothing) Then 
       If (settingsField.Equals(value) <> True) Then 
        Me.settingsField = value 
        Me.OnPropertyChanged("Settings") 
       End If 
      Else 
       Me.settingsField = value 
       Me.OnPropertyChanged("Settings") 
      End If 
     End Set 
    End Property 

    <System.Xml.Serialization.XmlAttributeAttribute()> _ 
    Public Property Name() As String 
     Get 
      Return Me.nameField 
     End Get 
     Set(value As String) 
      If (Not (Me.nameField) Is Nothing) Then 
       If (nameField.Equals(value) <> True) Then 
        Me.nameField = value 
        Me.OnPropertyChanged("Name") 
       End If 
      Else 
       Me.nameField = value 
       Me.OnPropertyChanged("Name") 
      End If 
     End Set 
    End Property 

#Region "Serialize/Deserialize" 
    '''<summary> 
    '''Serializes current Component object into an XML document 
    '''</summary> 
    '''<returns>string XML value</returns> 
    Public Overridable Overloads Function Serialize(ByVal encoding As System.Text.Encoding) As String 
     Dim streamReader As System.IO.StreamReader = Nothing 
     Dim memoryStream As System.IO.MemoryStream = Nothing 
     Try 
      memoryStream = New System.IO.MemoryStream() 
      Dim xmlWriterSettings As System.Xml.XmlWriterSettings = New System.Xml.XmlWriterSettings() 
      xmlWriterSettings.Encoding = encoding 
      Dim xmlWriter As System.Xml.XmlWriter = xmlWriter.Create(memoryStream, xmlWriterSettings) 
      Serializer.Serialize(xmlWriter, Me) 
      memoryStream.Seek(0, System.IO.SeekOrigin.Begin) 
      streamReader = New System.IO.StreamReader(memoryStream) 
      Return streamReader.ReadToEnd 
     Finally 
      If (Not (streamReader) Is Nothing) Then 
       streamReader.Dispose() 
      End If 
      If (Not (memoryStream) Is Nothing) Then 
       memoryStream.Dispose() 
      End If 
     End Try 
    End Function 

    Public Overridable Overloads Function Serialize() As String 
     Return Serialize(Encoding.UTF8) 
    End Function 

    '''<summary> 
    '''Deserializes workflow markup into an Component object 
    '''</summary> 
    '''<param name="xml">string workflow markup to deserialize</param> 
    '''<param name="obj">Output Component object</param> 
    '''<param name="exception">output Exception value if deserialize failed</param> 
    '''<returns>true if this XmlSerializer can deserialize the object; otherwise, false</returns> 
    Public Overloads Shared Function Deserialize(ByVal xml As String, ByRef obj As Component, ByRef exception As System.Exception) As Boolean 
     exception = Nothing 
     obj = CType(Nothing, Component) 
     Try 
      obj = Deserialize(xml) 
      Return True 
     Catch ex As System.Exception 
      exception = ex 
      Return False 
     End Try 
    End Function 

    Public Overloads Shared Function Deserialize(ByVal xml As String, ByRef obj As Component) As Boolean 
     Dim exception As System.Exception = Nothing 
     Return Deserialize(xml, obj, exception) 
    End Function 

    Public Overloads Shared Function Deserialize(ByVal xml As String) As Component 
     Dim stringReader As System.IO.StringReader = Nothing 
     Try 
      stringReader = New System.IO.StringReader(xml) 
      Return CType(Serializer.Deserialize(System.Xml.XmlReader.Create(stringReader)), Component) 
     Finally 
      If (Not (stringReader) Is Nothing) Then 
       stringReader.Dispose() 
      End If 
     End Try 
    End Function 

    '''<summary> 
    '''Serializes current Component object into file 
    '''</summary> 
    '''<param name="fileName">full path of outupt xml file</param> 
    '''<param name="exception">output Exception value if failed</param> 
    '''<returns>true if can serialize and save into file; otherwise, false</returns> 
    Public Overridable Overloads Function SaveToFile(ByVal fileName As String, ByVal encoding As System.Text.Encoding, ByRef exception As System.Exception) As Boolean 
     exception = Nothing 
     Try 
      SaveToFile(fileName, encoding) 
      Return True 
     Catch e As System.Exception 
      exception = e 
      Return False 
     End Try 
    End Function 

    Public Overridable Overloads Function SaveToFile(ByVal fileName As String, ByRef exception As System.Exception) As Boolean 
     Return SaveToFile(fileName, Encoding.UTF8, exception) 
    End Function 

    Public Overridable Overloads Sub SaveToFile(ByVal fileName As String) 
     SaveToFile(fileName, Encoding.UTF8) 
    End Sub 

    Public Overridable Overloads Sub SaveToFile(ByVal fileName As String, ByVal encoding As System.Text.Encoding) 
     Dim streamWriter As System.IO.StreamWriter = Nothing 
     Try 
      Dim xmlString As String = Serialize(encoding) 
      streamWriter = New System.IO.StreamWriter(fileName, False, encoding.UTF8) 
      streamWriter.WriteLine(xmlString) 
      streamWriter.Close() 
     Finally 
      If (Not (streamWriter) Is Nothing) Then 
       streamWriter.Dispose() 
      End If 
     End Try 
    End Sub 

    '''<summary> 
    '''Deserializes xml markup from file into an Component object 
    '''</summary> 
    '''<param name="fileName">string xml file to load and deserialize</param> 
    '''<param name="obj">Output Component object</param> 
    '''<param name="exception">output Exception value if deserialize failed</param> 
    '''<returns>true if this XmlSerializer can deserialize the object; otherwise, false</returns> 
    Public Overloads Shared Function LoadFromFile(ByVal fileName As String, ByVal encoding As System.Text.Encoding, ByRef obj As Component, ByRef exception As System.Exception) As Boolean 
     exception = Nothing 
     obj = CType(Nothing, Component) 
     Try 
      obj = LoadFromFile(fileName, encoding) 
      Return True 
     Catch ex As System.Exception 
      exception = ex 
      Return False 
     End Try 
    End Function 

    Public Overloads Shared Function LoadFromFile(ByVal fileName As String, ByRef obj As Component, ByRef exception As System.Exception) As Boolean 
     Return LoadFromFile(fileName, Encoding.UTF8, obj, exception) 
    End Function 

    Public Overloads Shared Function LoadFromFile(ByVal fileName As String, ByRef obj As Component) As Boolean 
     Dim exception As System.Exception = Nothing 
     Return LoadFromFile(fileName, obj, exception) 
    End Function 

    Public Overloads Shared Function LoadFromFile(ByVal fileName As String) As Component 
     Return LoadFromFile(fileName, Encoding.UTF8) 
    End Function 

    Public Overloads Shared Function LoadFromFile(ByVal fileName As String, ByVal encoding As System.Text.Encoding) As Component 
     Dim file As System.IO.FileStream = Nothing 
     Dim sr As System.IO.StreamReader = Nothing 
     Try 
      file = New System.IO.FileStream(fileName, FileMode.Open, FileAccess.Read) 
      sr = New System.IO.StreamReader(file, encoding) 
      Dim xmlString As String = sr.ReadToEnd 
      sr.Close() 
      file.Close() 
      Return Deserialize(xmlString) 
     Finally 
      If (Not (file) Is Nothing) Then 
       file.Dispose() 
      End If 
      If (Not (sr) Is Nothing) Then 
       sr.Dispose() 
      End If 
     End Try 
    End Function 
#End Region 

#Region "Clone method" 
    '''<summary> 
    '''Create a clone of this Component object 
    '''</summary> 
    Public Overridable Function Clone() As Component 
     Return CType(Me.MemberwiseClone, Component) 
    End Function 
#End Region 
End Class 

我很好奇爲什麼發生這種情況,以及是否有辦法不遇到此。

+0

作爲一個說明,我知道我可以做之前,反序列化一個正則表達式替換。我正在尋找更具體的東西。 – VoteCoffee

回答

0

根據http://msdn.microsoft.com/en-us/library/system.xml.xmlreader.isemptyelement%28v=vs.110%29.aspx,「不會爲空元素生成相應的EndElement節點。」

看來,微軟有意識地對待這兩種情況,公然無視XML標準。

我創建了下面的方法來修補XML文本:

 Public Function XMLReaderPatch(rawXML As String) As String 
      If String.IsNullOrEmpty(rawXML) Then Return rawXML 

      'Pattern for finding items similar to <name*/> where * may represent whitespace followed by text and/or whitespace 
      Dim pattern As String = "<(\S+)(\s[^<|>]*)?/>" 
      'Pattern for replacing with items similar to <name*></name> where * may represent whitespace followed by text and/or whitespace 
      Dim replacement As String = "<$1$2></$1>" 
      Dim rgx As New Text.RegularExpressions.Regex(pattern) 

      Return rgx.Replace(rawXML, replacement) 
     End Function 
0

<設置> </Settings>和< Settings />在語義上在XML中是相同的。如果您的代碼對另一個的響應不同,則代碼已損壞。

+0

這就是問題的關鍵。我知道它們在XML中的語義相同。但代碼的確有不同的反應。它使用標準的反序列化代碼,沒有太多需要破解的地方。我將在短時間內將此代碼添加到問題中。 – VoteCoffee

+0

你可以看到所有我現在用的就是: 私人共享sSerializer作爲System.Xml.Serialization.XmlSerializer Serializer.Deserialize(System.Xml.XmlReader.Create(stringReader))我有 – VoteCoffee

+0

最好的建議是藉此與xsd2code的作者合作,因爲我認爲他們是提供/生成Serializer類的人。 – keshlam