jak przekonwertować tekst xml do DataTable lub swojej klasy

0

Witam. Czego użyć do przekonwertowania takiego tekstu na obiekt DataTable / DataSet lub do kolekcji obiektów Group[]

<tables>
	<table name="Groups">
		<fields>
			<field name="GroupID" type="Integer"/>
			<field name="Name" type="String"/>
			<field name="Custom1" type="String"/>
			<field name="Custom2" type="String"/>
			<field name="OutputBits" type="String"/>
			<field name="OutputBits1" type="String"/>
			<field name="OutputBits2" type="String"/>
			<field name="OutputBits3" type="String"/>
			<field name="DefaultHotelGroup" type="Boolean"/>
		</fields>
		<rows>
			<row>
				<f>1</f>
				<f>Użytkownicy</f>
				<f>null</f>
				<f>null</f>
				<f>0</f>
				<f>0</f>
				<f>0</f>
				<f>0</f>
				<f>False</f>
			</row>
		</rows>
	</table>
</tables>

Klasa Group:

/// <summary>
        /// Reprezentuje grupę dostępu
        /// </summary>
        public class Group
        {
            /// <summary>
            /// Identyfikator grupy
            /// </summary>
            public int GroupID { get; set; }
            /// <summary>
            /// Nazwa grupy
            /// </summary>
            public string Name { get; set; }
            /// <summary>
            /// Komentarz 1
            /// </summary>
            public string Custom1 { get; set; }
            /// <summary>
            /// Komentarz 2
            /// </summary>
            public string Custom2 { get; set; }

            public string OutputBits { get; set; }
            public string OutputBits1 { get; set; }
            public string OutputBits2 { get; set; }
            public string OutputBits3 { get; set; }
            /// <summary>
            /// Domyślna grupa gościa
            /// </summary>
            public bool DefaultHotelGroup { get; set; }
        }

Próbowałem:

internal static class XmlParser
    {
        public enum eEncodingType
        {
            UTF8,
            ASCII,
            UTF7,
            BigEndianUnicode,
            Unicode,
            UTF32,
            Default
        }
        private static MemoryStream ConvertXmlStringToStream(string xml, eEncodingType encodingType)
        {
            byte[] byteArray = new byte[] { };
            switch (encodingType)
            {
                case eEncodingType.UTF8:
                    byteArray = Encoding.UTF8.GetBytes(xml);
                    break;
                case eEncodingType.ASCII:
                    byteArray = Encoding.ASCII.GetBytes(xml);
                    break;
                case eEncodingType.UTF7:
                    byteArray = Encoding.UTF7.GetBytes(xml);
                    break;
                case eEncodingType.BigEndianUnicode:
                    byteArray = Encoding.BigEndianUnicode.GetBytes(xml);
                    break;
                case eEncodingType.Unicode:
                    byteArray = Encoding.Unicode.GetBytes(xml);
                    break;
                case eEncodingType.UTF32:
                    byteArray = Encoding.UTF32.GetBytes(xml);
                    break;
                default:
                    byteArray = Encoding.Default.GetBytes(xml);
                    break;
            }
            MemoryStream stream = new MemoryStream(byteArray);
            DataSet ds = new DataSet();
            return stream;
        }
        private static DataSet ConvertStreamToDataSet(MemoryStream stream)
        {
            DataSet dataSet = new DataSet();
            dataSet.ReadXml(stream);
            return dataSet;
        }

        public static Definitions.Group DeserializeGroup(string xml, eEncodingType encodingType = eEncodingType.UTF8)
        {
            MemoryStream stream = ConvertXmlStringToStream(xml, encodingType);
            DataSet dataSet = ConvertStreamToDataSet(stream);
            return new Definitions.Group();
        }
    }

Niestety dataSet zamiast jednej tabeli Groups posiada tabele:

  1. table
  2. fields
  3. field
  4. rows
  5. row
  6. f

co oznacza, że coś jest nie tak.

Wykombinowałem również coś takiego jednak im dalej w to brnę tym bardziej nie wiem co i jak:

public static Definitions.Group DeserializeGroup(string xml)
        {
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.LoadXml(xml);
            return new Definitions.Group();
        }
1

AFAIK nie ma automatu do czegoś takiego, będziesz musiał rzeźbić ręcznie - np. właśnie przez XmlDocument i jego GetElementsByTagName("row"), Children i inne InnerText.

Koszmarny ten XML, kompletne zaprzeczenie XML-a ;)

0

@Ktos: program PRmaster rejestruje bibliotekę COM której metody zwracają dane właśnie w takiej postaci. Będę musiał przez to przebrnąć ;D

2
	public class MyRoot
	{
		public List<Table> Tables { get; set; }
	}

	public class Table
	{
		public List<Row> Rows { get; set; }
	}

	public class Row
	{
		[XmlElement(Order = 1)]
		public int GroupID { get; set; }
		[XmlElement(Order = 2)]
		public string Name { get; set; }
		[XmlElement(Order = 3)]

		// dalej sobie poradzisz

		public string Custom1 { get; set; }
		public string Custom2 { get; set; }
		public string OutputBits { get; set; }
		public string OutputBits1 { get; set; }
		public string OutputBits2 { get; set; }
		public string OutputBits3 { get; set; }
		public bool DefaultHotelGroup { get; set; }
	}

Ja mam tak zrobioną deserializacje XMLa

        public static T DeserializeXML<T>(string filePath)
        {
            T obj;
            XmlSerializer xml = new XmlSerializer(typeof(T));
            using (FileStream sr = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                obj = (T)xml.Deserialize(sr);
            }
            return obj;
        }

Użycie:

Methods.DeserializeXML<Root>(filePath);

Możesz sobie to ewentualnie przerobić na deserializację samego stringa. Ja przeważnie operuje na plikach.

0

@AdamWox: przepraszam, ale nie potrafiłem zmodyfikować Twojego kodu :( Ponad to XmlSerializer.Deserialize() zgodnie z dokumentacją nie może być używany z listą i tablicą:
XmlSerializerNie można zdeserializować następujących elementów: tablice ArrayList i tablice List<T> .
https://docs.microsoft.com/pl-pl/dotnet/api/system.xml.serialization.xmlserializer.deserialize?view=net-5.0

Poszedłem ścieżką którą wskazał mi Pan @Ktos
iteruję całe drzewo xml, a znając budowę drzewa mogę odnaleźć interesujące mnie wartości. Kod jeszcze nie dokończony, na razie się bawię i testuję bo pierwszy raz parsuję z xmla. Oceńcie czy to ma ręce i nogi :)

internal static class XmlParser
    {
        private static XmlDocument ConvertXmlStringToXmlDocument(string xml)
        {
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xml);
            return doc;
        }
        private static XmlNode[] GetChildNodes (XmlDocument xmlDocument)
        {
            List<XmlNode> nodes = new List<XmlNode>();
            XmlNode root = xmlDocument.FirstChild;
            if (root.HasChildNodes)
                for (int i = 0; i < root.ChildNodes.Count; i++)
                    nodes.Add(root.ChildNodes[i]);
            return nodes.ToArray();
        }
        private static XmlNode[] GetChildNodes(XmlNode Node)
        {
            List<XmlNode> nodes = new List<XmlNode>();
            if (Node.HasChildNodes)
                for (int n = 0; n < Node.ChildNodes.Count; n++)
                    nodes.Add(Node.ChildNodes[n]);
            return nodes.ToArray();
        }

        public static Definitions.Group[] GetGroups(string xml)
        {
            XmlDocument doc = ConvertXmlStringToXmlDocument(xml);
            XmlNode[] tables = GetChildNodes(doc);
            List<Definitions.Group> Groups = new List<Definitions.Group>();
            string s = "> Dokument: " + doc.Name + Environment.NewLine;
            foreach (XmlNode table in tables)
            {
                s += ">>> Tabela: " + table.Name + Environment.NewLine;
                XmlNode[] rows = GetChildNodes(table);
                foreach(XmlNode row in rows)
                {
                    s += ">>>>> Wiersz: " + row.Name + Environment.NewLine;
                    XmlNode[] columns = GetChildNodes(row);
                    foreach (XmlNode column in columns)
                    {
                        s += ">>>>>>> Kolumna: " + column.Name + Environment.NewLine;
                        
                    }
                }
            }
            MessageBox.Show(s);
            return Groups.ToArray();
        }
    }

Wynik wywołania GetGroups, gdzie argumentem dla xml jest:

<tables>
	<table name="Groups">
		<fields>
			<field name="GroupID" type="Integer"/>
			<field name="Name" type="String"/>
			<field name="Custom1" type="String"/>
			<field name="Custom2" type="String"/>
			<field name="OutputBits" type="String"/>
			<field name="OutputBits1" type="String"/>
			<field name="OutputBits2" type="String"/>
			<field name="OutputBits3" type="String"/>
			<field name="DefaultHotelGroup" type="Boolean"/>
		</fields>
		<rows>
			<row>
				<f>1</f>
				<f>Użytkownicy</f>
				<f>null</f>
				<f>null</f>
				<f>0</f>
				<f>0</f>
				<f>0</f>
				<f>0</f>
				<f>False</f>
			</row>
		</rows>
	</table>
</tables>

screenshot-20201229135116.png

1

Robiłem to milion razy i mam kilka programów, które ogarniają listy w XML. Nie wiem skąd to wziąłeś z tej dokumentacji ale nigdy nie parsowałem XMLa "ręcznie". Zawsze tworzę tak obiekty, aby dało się jedna metodą serializowac i deserializowac.

Kod wrzuciłem z głowy. Zaraz sprawdzę jak to pójdzie u mnie.

2

Jeśli chcesz już iść w metodę ręczną, to ja bym to zrobił po linii najmniejszego oporu:

public static IEnumerable<Group> Deserialize(string xml)
{
    var result = new List<Group>();

    var xmldoc = new XmlDocument();
    xmldoc.LoadXml(xml);

    foreach (XmlNode row in xmldoc.GetElementsByTagName("row"))
    {
        var elem = new Group();

        elem.GroupID = Convert.ToInt32(row.ChildNodes[0].InnerText);
        elem.Name = row.ChildNodes[1].InnerText;
        elem.Custom1 = row.ChildNodes[2].InnerText;
        elem.Custom2 = row.ChildNodes[3].InnerText;
        elem.OutputBits = row.ChildNodes[4].InnerText;
        elem.OutputBits1 = row.ChildNodes[5].InnerText;
        elem.OutputBits2 = row.ChildNodes[6].InnerText;
        elem.OutputBits3 = row.ChildNodes[7].InnerText;
        elem.DefaultHotelGroup = Convert.ToBoolean(row.ChildNodes[8].InnerText);

        result.Add(elem);
    }

    return result;
}

Zakładając, ze kolejność elementów w wierszu się nie zmienia, oczywiście.

2

UPDATE

BUDOWA KLAS:

    [XmlRoot(ElementName = "table")]
    public class table
    {
        [XmlArray(ElementName = "rows")]
        public List<row> rows { get; set; }
    }

    [XmlRoot(ElementName = "row")]
    public class row
    {
        [XmlElement(Order = 1)]
        public int GroupID { get; set; }
        [XmlElement(Order = 2)]
        public string Name { get; set; }
        [XmlElement(Order = 3)]
        public string Custom1 { get; set; }
        [XmlElement(Order = 4)]
        public string Custom2 { get; set; }
        [XmlElement(Order = 5)]
        public string OutputBits { get; set; }
        [XmlElement(Order = 6)]
        public string OutputBits1 { get; set; }
        [XmlElement(Order = 7)]
        public string OutputBits2 { get; set; }
        [XmlElement(Order = 8)]
        public string OutputBits3 { get; set; }
        [XmlElement(Order = 9)]
        public bool DefaultHotelGroup { get; set; }
    }

PRZYKŁADOWY XML:

<tables>
    <table name="Groups">
        <fields>
            <field name="GroupID" type="Integer"/>
            <field name="Name" type="String"/>
            <field name="Custom1" type="String"/>
            <field name="Custom2" type="String"/>
            <field name="OutputBits" type="String"/>
            <field name="OutputBits1" type="String"/>
            <field name="OutputBits2" type="String"/>
            <field name="OutputBits3" type="String"/>
            <field name="DefaultHotelGroup" type="Boolean"/>
        </fields>
        <rows>
            <row>
                <f>1</f>
                <f>Użytkownicy</f>
                <f>null</f>
                <f>null</f>
                <f>0</f>
                <f>0</f>
                <f>0</f>
                <f>0</f>
                <f>False</f>
            </row>
        </rows>
    </table>
</tables>

METODA SERIALIZACJI (doszły 3 linijki) :

        public static T DeserializeXML<T>(string filePath)
        {
            T obj;

            XmlRootAttribute xRoot = new XmlRootAttribute();
            xRoot.ElementName = "tables";
            xRoot.IsNullable = true;

            XmlSerializer xml = new XmlSerializer(typeof(T), xRoot);

            using (FileStream sr = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                obj = (T)xml.Deserialize(sr);
            }
            return obj;
        }

WYWOŁANIE:

var tables = DeserializeXML<List<table>>(AppDomain.CurrentDomain.BaseDirectory + "test.xml");

REZULTAT:
deserialize_tables.png

1

Oczywiście to rozwiązanie od @AdamWox też wymaga niezmienności kolejności elementów <f>. Jeżeli chcesz mieć to zależne od tego, co jest zdefiniowane we <field> to trzeba by ręcznie się bardziej pobawić.

I moja metoda przypisze stringi o wartości null, a nie faktyczne nulle, bo nie zauważyłem, że tam jest null ;)

0

@AdamWox: Dane z row

<row>
                <f>1</f>
                <f>Użytkownicy</f>
                <f>null</f>
                <f>null</f>
                <f>0</f>
                <f>0</f>
                <f>0</f>
                <f>0</f>
                <f>False</f>
</row>

powinny zgadzać się z wyjściem?
screenshot-20201229145017.png

0

@AdamWox: Z tym boolem poradziłem sobie pobierając wartość jako string i dodatkowo robiąc getter konwertujący na bool. Jednak zacząłem mierzyć się z kolejną metodą i nastał problem nazewnictwa. Gdy zamiast

[XmlRoot(ElementName = "row")]
public class row
{
}

zmienię nazwę na:

[XmlRoot(ElementName = "row")]
public class group
{
}

tracę wszystkie rekordy. Cała procedura przebiega prawidłowo ale zwraca 0 wierszy. Czy nazwy klas muszą się zgadzać? Bo jeśli tak to muszę dla każdego pobieranego obiektu stworzyć klasę o nazwie row i klasę o nazwie table tak?

Próbowałem np. w ten sposób, ale zwraca również 0 wierszy:

[XmlRoot(ElementName = "table")]
        public class table<T>
        {
            [XmlArray(ElementName = "rows")]
            public List<T> rows { get; set; }
        }

var result = XmlParser.DeserializeXML<List<XmlParser.table<Definitions.row>>>(groupXml);
0

Dlaczego robisz takie głupoty?

        public class table<T>
        {
            [XmlArray(ElementName = "rows")]
            public List<T> rows { get; set; }
        }

Po co ci w tym miejscu generyk skoro wiesz jak wygląda model/xml? Ta klasa nigdzie indziej nie zostanie wykorzystana. List<Row> zawsze będzie listą typu Row.

Nie muszą się nazwy klas zgadzać.
Zmień

[XmlArray(ElementName = "rows")]

na

[XmlElement("rows")]
0

@AdamWox: właśnie sęk w tym, że mam teraz schemat

public class row
        {
            [XmlElement(Order = 1, ElementName = "f")]
            public string ZoneID { get; set; }
            [XmlElement(Order = 2, ElementName = "f")]
            public string Name { get; set; }
        }

Tych klas będzie tyle co funkcji i każda ma taką samą budowę tylko zmieniają się pola <f>. Bez sensu aby każda nazywała się row. Musi być jakieś obejście. Z tego co czytam atrybut

[XmlRoot(ElementName = "row")]
 public class group
{}
[XmlRoot(ElementName = "row")]
 public class zone
{}

ElementName="row" wskazuje na nazwę w pliku xml, ale to nie działa jak chcę, ponieważ zostawiam ten atrybut bez zmian, zmieniam tylko nazwę klasy i już mam błędnie zdeserializowany obiekt.

Wywoływanie wyglądałoby wtedy:

var result = XmlParser.DeserializeXML<List<XmlParser.table<Definitions.group>>>(groupXml);
var result = XmlParser.DeserializeXML<List<XmlParser.table<Definitions.zone>>>(zoneXml);

Edit:
gdy ustawiłem [XmlElement("rows")] zwracane obiekty są równe null

1

W tym wypadku musisz sobie poradzić sam, bo albo w całości opisujesz jaki jest problem i jak wygląda XML, albo z każdym postem dokładasz kolejne elementy, które burzą poprzedni zamysł.

Nie deserializujesz samych wierszy. Musisz ogarnąć całego XMLa do obiektu i potem operować danymi, które są ci potrzebne.

Robię u siebie, sprawdzam czy działa i przesyłam co trzeba zmienić. Jeśli masz inny zamysł i XML wygląda inaczej to skąd mam wiedzieć co robisz źle?

0

Okej. Na podstawie tego czego się od Was nauczyłem i jeszcze z kilku przykładów z neta najlepszą dla mnie będzie opcja wczytywania tabeli z określoną nazwą. Wykonałem to w następujący sposób i całkowicie spełnia moje oczekiwania :) Dziękuję wszystkim za pomoc!

private static MemoryStream GenerateStreamFromString(string s)
        {
            byte[] byteArray = Encoding.UTF8.GetBytes(s);
            var stream = new MemoryStream(byteArray);
            return stream;
        }
public static T DeserializeXML<T>(string xmlText)
        {
            T obj;
            XmlSerializer xml = new XmlSerializer(typeof(T));
            using (MemoryStream stream = GenerateStreamFromString(xmlText))
            {
                obj = (T)xml.Deserialize(stream);
            }
            return obj;
        }
public static List<T> DeserializeToList<T>(string xmlText, DBTableNamesEnum tableName)
        {
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.LoadXml(xmlText);
            XmlNode nodeRow = xmlDocument.SelectNodes("/tables/table[@name='" + tableName.ToString() + "']").Item(0)["rows"];
            Rows<T> rows = DeserializeXML<Rows<T>>(nodeRow.OuterXml);
            return rows.Items;
        }

Przykład użycia pobrania danych z tabeli Groups z ciągu xml:

<tables>
    <table name="Groups">
        <fields>
            <field name="GroupID" type="Integer"/>
            <field name="Name" type="String"/>
            <field name="Custom1" type="String"/>
            <field name="Custom2" type="String"/>
            <field name="OutputBits" type="String"/>
            <field name="OutputBits1" type="String"/>
            <field name="OutputBits2" type="String"/>
            <field name="OutputBits3" type="String"/>
            <field name="DefaultHotelGroup" type="Boolean"/>
        </fields>
        <rows>
            <row>
                <f>1</f>
                <f>Pracownicy</f>
                <f>null</f>
                <f>null</f>
                <f>0</f>
                <f>0</f>
                <f>0</f>
                <f>0</f>
                <f>False</f>
            </row>
        </rows>
    </table>
</tables>

Wywołanie:

public static Definitions.group[] GetGroups(string xml)
        {
            return DeserializeToList<Definitions.group>(xml, DBTableNamesEnum.Groups).ToArray();
        }

1 użytkowników online, w tym zalogowanych: 0, gości: 1