1

I have to read data from an XML file and the order is important

I have the following (simplified) XML data which I need to read:

<xmltest>
    <type1>
        <tekst>ABC</tekst>
    </type1>
    <type2>
        <tekst>DEF</tekst>
    </type2>
    <type1>
        <tekst>GHI</tekst>
    </type1>
    <type2>
        <tekst>JKL</tekst>
    </type2>
    <type3>
        <tekst>MNO</tekst>
    </type3>
</xmltest>

The following classes are made:

public class xmltest
    public property type1 as list(of type1)
    public property type2 as list(of type2)
    public property type3 as list(of type3)
end class

public class type1
    public property tekst as string
end class

public class type2
    public property tekst as string
end class

public class type3
    public property tekst as string
end class

I use the following code to read the XML:

Public Sub Indlaes
    Dim reader As New System.Xml.XmlTextReader("filename.txt")
    Dim subreader As System.Xml.XmlReader
    Dim xmlSer As System.Xml.Serialization.XmlSerializer
    Dim result = New cmltest
   
    Do While (reader.Read())
        Select Case reader.NodeType
            Case System.Xml.XmlNodeType.Element
                subreader = reader.ReadSubtree()
                xmlSer = New System.Xml.Serialization.XmlSerializer(result.GetType)
                result = xmlSer.Deserialize(subreader)
                subreader.Close()
        End Select
    Loop
    reader.Close()
End Sub

In the above example I end up with 3 lists inside the xmltest but can't recreate the order
I'm thinking about using a dictionary with an ID used across the 3 lists/dictionaries, but how do I get the ID's set? Or is there any other solution?
I would really want to keep using the Deserialize function as it is used for the rest of the xml data also

djv
  • 15,168
  • 7
  • 48
  • 72
jpi76
  • 13
  • 3

2 Answers2

0

You are using a hybrid method to deserialize, plus you are using a txt file(?) A basic deserialization method could involve the following

Model:

<XmlRoot>
Public Class xmltest
    <XmlElement>
    Public Property type1 As List(Of type1)
    <XmlElement>
    Public Property type2 As List(Of type2)
    <XmlElement>
    Public Property type3 As List(Of type3)
End Class
Public Class type1
    <XmlElement>
    Public Property tekst As String
End Class
Public Class type2
    <XmlElement>
    Public Property tekst As String
End Class
Public Class type3
    <XmlElement>
    Public Property tekst As String
End Class

Deserialization code:

Dim s As New XmlSerializer(GetType(xmltest))
Dim xmlTest As xmltest
Using fs As New FileStream("filename.xml", FileMode.Open)
    xmlTest = CType(s.Deserialize(fs), xmltest)
End Using

This results in the out-of-order lists, as you noted

enter image description here

You can make modifications to the model to use the same class to deserialize all the type nodes into the same array

<XmlRoot>
Public Class xmltest

    <XmlElement(ElementName:="type1", Type:=GetType(type1))>
    <XmlElement(ElementName:="type2", Type:=GetType(type1))>
    <XmlElement(ElementName:="type3", Type:=GetType(type1))>
    <XmlChoiceIdentifier("typesElementName")>
    Public Property types As type1()

    <XmlIgnore>
    Public Property typesElementName As types()

End Class

Public Class type1
    <XmlElement>
    Public Property tekst As String
End Class

<XmlType(IncludeInSchema:=False)>
Public Enum types
    type1
    type2
    type3
End Enum

using the same deserialization code, and you now get this array, in order

enter image description here

djv
  • 15,168
  • 7
  • 48
  • 72
0

The schema for your sequence of <typeN> elements looks to be a sequence of choice elements for each of possible <typeN> element name. As explained in Choice Element Binding Support, one way to implement this is via a list of polymorphic types, provided you know all all possible <typeN> element names at compile time, and apply the attributes <XmlElementAttribute(String, Type)> to the list for each possible type.

The following classes implement this approach:

' The root object with the polymorphic list corresponding to the possible types 
<XmlRoot("xmltest")> 
Public Class XmlTest
    <XmlElement("type1", GetType(Type1)), XmlElement("type2", GetType(Type2)), XmlElement("type3", GetType(Type3))>     
    Public Property TypeList As List(Of TypeBase) = New List(Of TypeBase) ()
End Class

' The <TypeN> polymorphic type hierarchy
Public Class TypeBase 
    <XmlElement("tekst")>
    Public Property Tekst As String
End Class

<XmlType("type1")>
Public Class Type1 
    Inherits TypeBase
End Class

<XmlType("type2")>
Public Class Type2
    Inherits TypeBase
End Class

<XmlType("type3")>
Public Class Type3 
    Inherits TypeBase
End Class

With these classes, you can deserialize automatically using XmlSerializer as follows, without any need for manual reading with XmlReader, using the following generic function:

Public Function DeserializeFromFile(Of T)(filename As String) As T
    Using stream = File.OpenRead(filename)
        Return DirectCast(New XmlSerializer(GetType(T)).Deserialize(stream), T)
    End Using
End Function

Putting it all together, to deserialize your XML file, loop through the <typeN> items in their original order, and determine the type of each, you could do the following:

Dim test = DeserializeFromFile(Of XmlTest)(filename)

For Each item in test.TypeList
    Select Case item.GetType()
        Case GetType(Type1)
            Console.WriteLine("Type is type 1, value is {0}.", item.Tekst)
        Case GetType(Type2)
            Console.WriteLine("Type is type 3, value is {0}.", item.Tekst)
        Case GetType(Type3)
            Console.WriteLine("Type is type 3, value is {0}.", item.Tekst)
        Case Else
            Throw New Exception(String.Format("Unknown type {0}", item.GetType()))
    End Select
Next

Notes:

Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340