Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Klassen dynamisch mit xsd verändern? (https://www.delphipraxis.net/144207-klassen-dynamisch-mit-xsd-veraendern.html)

jfheins 2. Dez 2009 14:19


Klassen dynamisch mit xsd verändern?
 
Hallo,

Ich habe gerade eine tolle Aufgabe bekommen :stupid:

Und zwar gibt es eine datenstruktur (der Einfachheit halber sagen wir mal: eine Liste) in der strukturierte Daten drin sind:
XML-Code:
<liste>
<item>
  <id>8</id>
  <bla>Hallo</bla>
</item>
<item>
  <id>9</id>
  <bla>Welt</bla>
</item>
</liste>
Das lese ich Im Moment einfach in eine List<MyDataClass> ein.
Dazu gibt es (seit heute) auch eine xsd, die diese Struktur beschreibt :)

Soweit, so gut. Jetzt soll es aber möglich sein, dass der Benutzer (ohne Quellcode) die xsd verändert und bspw. eine color-Eigenschaft vom Typ int mit Wertebereich 0-255 hinzufügt. Das soll dann quasi vollautomatisch übernommen werden.

XML-Code:
<liste>
<item>
  <id>8</id>
  <bla>Hallo</bla>
  <color>243</color>
</item>
<item>
  <id>9</id>
  <bla>Welt</bla>
  <color>145</color>
</item>
</liste>
Jetzt ist natürlich die Klasse "MyDataClass" zur Laufzeit unveränderlich. Erste Lösung wäre also sowas wir ein Dictionary<string, object> wo man dann alle Elemente reinhauen kann. Aber dagegen spricht, dass dann die Typen, die in der xsd ja festgelegt wurden nicht berücksichtigt werden und außerdem könnte man ja auf die Idee kommen, kein einfaches Element zu nehmen sondern in diesem Element noch zu verschachteln.

Deshalb die Frage: Wie würdet ihr sowas angehen?
Btw.: Ich verwende C# (.net 3.5)

Medium 2. Dez 2009 14:29

Re: Klassen dynamisch mit xsd verändern?
 
Wenn es derart generisch sein soll, würde sich fast schon eine Implementierung von etwas lohnen, was einem Variant ähnelt, der zusätzlich sich selbst beinhalten kann. Ich bin in .NET 3.5 allerdings nicht fit genug um sagen zu können ob es da nicht sogar schon etwas in der Art gibt - XML ist ja "hypig" genug um drauf hoffen zu können dass ein Framework da was bietet :stupid:.

jfheins 2. Dez 2009 14:38

Re: Klassen dynamisch mit xsd verändern?
 
Damit wäre aber jede Art der Typsicherheit dahin ...

Der Clou soll ja sein, dass man im xsd definiert "ganzzahl zwischen 0 und 255" und dann im Programm beim bearbeiten (benutze dafür im Moment n PropertyGrid) die Eigenschaft "Color" sieht, und auch einen Fehler bekommt wenn man da keine Zahl eintippt ;)

und nein, ich habe keine besondere Lust darauf, einen Variant nachzuprogrammieren. Vorher versuche ich lieber, ein XElement-Objekt mit xsd im Hintergrund zu halten ...

alzaimar 2. Dez 2009 14:49

Re: Klassen dynamisch mit xsd verändern?
 
Das Problem ist ja nicht, wie man die Daten im Speicher hält (Ne einfache rekursive Baumstruktur reicht ja und ist einsfixdrei hingebapselt), sondern wie man mit Daten arbeitet, deren Struktur man nicht kennt.

Welchen Wert hat die hinzugefügte Eigenschaft 'Color'? Wo soll sie auftauchen, wie mit ihr 'gerechnet' (im weitesten Sinne) werden? Soll da etwa nur etwas eingegeben werden?

Wenn der Anwender gerade mal eine Spalte zu einer Tabelle hinzufügen kann, geht das ja noch, aber was ist, wenn er auch ganze Unterstrukturen hinzufügen darf? Wie willst Du das dann darstellen und in der Darstellung handhaben?

XSD / XML bietet dir ja diese Möglichkeiten, und um die so gestellte Aufgabe korrekt umzusetzen, kannst du schon mal fast komplett auf klassische Dialoge mit Eingabefeldern und Tabellen / Grids zur Darstellung verzichten. Du kannst also nur mit Baumstrukturen arbeiten, denn nur die bilden eine XML-Struktur korrekt ab, wobei Attribute wieder anders behandelt werden müssten. Nur bei den Attributen, die in ihrer Struktur nicht hierarchisch sind, kannst du flache Strukturen (Dialoge, Grids) verwenden

Machbar ist alles, aber die Vorgabe scheint mir von jemandem zu stammen, der eigentlich keine Ahnung hat.

Ich würde mal bei Altova schauen, wie die soetwas umgesetzt haben und mich eventuell da bedienen.

Wenn es wirklich in dieser Mächtigkeit umgesetzt werden soll, dann würde ich eine Standardlösung vorschlagen (Altova macht da ganz gute Sachen).

Neutral General 2. Dez 2009 14:57

Re: Klassen dynamisch mit xsd verändern?
 
Hi,

Vielleicht brauchst du ja (soetwas wie) TField..

Delphi-Quellcode:
var f: TField;
begin
  f.DataType := ftInteger;
  f.Size := 255;
end;
Oh es geht ja um C#... Aber da wirds ja sicher auch sowas geben..

?

Medium 2. Dez 2009 15:07

Re: Klassen dynamisch mit xsd verändern?
 
Wenn das XSD den Typ definiert ist's doch nur halb so wild. Deine Knoten müssen ja nur prinzipiell jeden Typ halten können, und dies nicht implizit bei einer Zuweisung anpassen. Dann wird der Knoten-Sub-Typ eben ausm XSD anhand der ID gesetzt, und der Wert entsprechend zugewiesen. Passen die Typen nicht, gibt's ne Exception. Dass das keine Freude macht denk ich mir 8-)

Eine andere Variante (hübscher denk ich), ist eine Knoten-Basisklasse die lediglich definiert dass sie einen Eltern- und N Kindknoten haben kann. Davon dann je Typ eine Klasse ableiten, den Baum anhand der ID und dem XSD aufbauen, und mit den Werten befüllen. Wenn man noch eine Priese Generics einstreut, kann man sich denke ich auch die Ableiterei sparen. Also so ca.:

Code:
class MyNode<T>
{
  public MyNode Parent;
  public List<MyNode> Children;
  public T Data;
}
.
.
.
  MyNode<TypAusXSD> nd = new MyNode<TypAusXSD>;
  nd.Data = DatenAusXML; // Excecption wenn der Typ nicht passt frei Haus
Ich weiss nur grad nicht, ob man in der Klasse MyNode ohne Typfestlegung in der Liste so deklarieren kann :gruebel:

jfheins 2. Dez 2009 15:20

Re: Klassen dynamisch mit xsd verändern?
 
Danke für die Antworten :)

Eine gute Nachricht - ich habe kurz gemeckert und das mit den komplexen verschachtelten Typen ist erstmal vom Tisch :stupid:

Ich mache dann erstmal so ne Art Tabelle mit 3 Spalten "Name", "Typ", "Inhalt" - die ersten 2 Spalten lese ich dann aus der xsd, die letzte wird dann mit den Daten befüllt ...
(verschachtelte Sachen kommen dann mit "ComplexType" und ihrem XML-String da rein und sind nicht editierbar)

alzaimar 2. Dez 2009 15:24

Re: Klassen dynamisch mit xsd verändern?
 
Wieso nimmst Du nicht gleich ein XmlDocument? Das liest ein, strukturiert, validiert etc. Alles, was du brauchst.

jfheins 2. Dez 2009 15:29

Re: Klassen dynamisch mit xsd verändern?
 
Im Moment verwende ich XDocument (Namespace System.Xml.Linq) - aber ich werde mir das direkt mal anschauen, danke :)

Hmmm ... sieht so aus als wären das einfach nur 2 verschiedene Arten, ein XML-Dokument zu bearbeiten - wobei mir die LINQ-Variante mehr zusagt :angel:

jfheins 15. Dez 2009 11:43

Re: Klassen dynamisch mit xsd verändern?
 
So, falls das nochmal wen interessiert, hier meine Lösung:

Der Graph (Containerklasse für Kanten und Knoten) implementiert das Interface ICustomTypeDescriptor - damit kann man dynamisch Zeilen im PropertyGrid hinzufügen.

Ein Feld in der Klasse dient als Sammelbecken für alle nicht-erkannten Attribute:
Code:
        private List<XAttribute> Attrib;
Dann muss man mindestens eine Methode wie folgt überschreiben:
Code:
        public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            var normal = TypeDescriptor.GetProperties(this, attributes, true);

            var all = new List<PropertyDescriptor>(normal.Count + Attrib.Count);

            foreach (PropertyDescriptor item in normal)
            {
                all.Add(item);
            }

            foreach (var item in Attrib)
            {
                if (VariantDescriptor.Supports(item))
                {
                    all.Add(new VariantDescriptor(item));
                }
            }
           
            return new PropertyDescriptorCollection(all.ToArray());
        }
Den Rest kann man einfach weiterleiten:
Code:
        public string GetClassName()
        {
            return TypeDescriptor.GetClassName(this, true);
        }
        public string GetComponentName()
        {
            return TypeDescriptor.GetComponentName(this, true);
        }
        public TypeConverter GetConverter()
        {
            return TypeDescriptor.GetConverter(this, true);
        }
/// etc...
Und hier kommt der Interessante Teil: Der PropertyDescriptor der das typsichere Bearbeiten ermöglicht:
Code:
    /*  Diese Klasse leistet die Darstellung der verschidenen XAttribute
     *  im PropertyGrid. Es werden einfache Typen unterschieden, und zum
     *  Editieren in ihre C#-Equivalente überführt.
     *  Getestet mit int, float, double, decimal, string und DateTime, ggf. switches erweitern.
     *  Unbekannte Typen sollen nicht editierbar gemacht werden,
     *  wenn TypeCodeToType() den Wert null zurückgibt. (kann mit Supports() geprüft werden)
     * 
     *  Klasse benötigt Schema-Infos.
     * 
     */
    class VariantDescriptor : PropertyDescriptor
    {
        private XAttribute attr;
        private XmlTypeCode type;

        public VariantDescriptor(XAttribute attribute)
            : base(attribute.Name.ToString(), null)
        {
            attr = attribute;
            var info = attr.GetSchemaInfo();

            if (info == null)
                throw new ArgumentException("Schema-Info is required!\r\nUse XDocument.Validate(..., ..., true) or equivalent.");
           
            type = attr.GetSchemaInfo().SchemaType.TypeCode;
           
            if (TypeCodeToType(type) == null)
                throw new NotImplementedException(string.Format("This type (\"{0}\") is not supported.", type.ToString()));
        }

        public override bool CanResetValue(object component)
        {
            return false;
        }

        public override Type ComponentType
        {
            get { return typeof(Graph); }
        }

        public override object GetValue(object component)
        {
            switch (type)
            {
                case XmlTypeCode.Time:
                case XmlTypeCode.Date:
                case XmlTypeCode.DateTime:
                    return XmlConvert.ToDateTime(attr.Value, XmlDateTimeSerializationMode.RoundtripKind);

                // Konvertierung nötig, um das Dezimaltrennzeichen korrekt zu wandeln
                case XmlTypeCode.Decimal:
                    return XmlConvert.ToDecimal(attr.Value);
                case XmlTypeCode.Double:
                    return XmlConvert.ToDouble(attr.Value);
                case XmlTypeCode.Float:
                    return XmlConvert.ToSingle(attr.Value);

                default:
                    return attr.Value;
            }
        }

        public override bool IsReadOnly
        {
            get { return false; }
        }

        public override Type PropertyType
        {
            get
            {
                return TypeCodeToType(type);
            }
        }

        private static Type TypeCodeToType(XmlTypeCode TypeCode)
        {
            switch (TypeCode)
            {
                case XmlTypeCode.Boolean:
                    return typeof(Boolean);
                case XmlTypeCode.UnsignedByte:
                case XmlTypeCode.Byte:
                    return typeof(byte);
                case XmlTypeCode.Date:
                case XmlTypeCode.Time:
                case XmlTypeCode.DateTime:
                    return typeof(DateTime);
                case XmlTypeCode.Decimal:
                    return typeof(decimal);
                case XmlTypeCode.Double:
                    return typeof(double);
                case XmlTypeCode.Float:
                    return typeof(float);
                case XmlTypeCode.Int:
                    return typeof(int);
                case XmlTypeCode.NonPositiveInteger:
                case XmlTypeCode.NegativeInteger:
                case XmlTypeCode.Integer:
                case XmlTypeCode.Long:
                    return typeof(long);
                case XmlTypeCode.Short:
                    return typeof(short);
                case XmlTypeCode.Name:
                case XmlTypeCode.NCName:
                case XmlTypeCode.NmToken:
                case XmlTypeCode.Text:
                case XmlTypeCode.String:
                    return typeof(string);
                case XmlTypeCode.UnsignedInt:
                    return typeof(uint);
                case XmlTypeCode.NonNegativeInteger:
                case XmlTypeCode.PositiveInteger:
                case XmlTypeCode.UnsignedLong:
                    return typeof(ulong);
                case XmlTypeCode.UnsignedShort:
                    return typeof(ushort);
                default:
                    return null;
            }
        }

        public override void ResetValue(object component)
        {
        }

        public override void SetValue(object component, object value)
        {
            switch (type)
            {
                case XmlTypeCode.Time:
                case XmlTypeCode.Date:
                case XmlTypeCode.DateTime:
                    attr.Value = XmlConvert.ToString((DateTime)value, XmlDateTimeSerializationMode.RoundtripKind);
                    break;

                // Konvertierung nötig, um das Dezimaltrennzeichen korrekt zu wandeln
                case XmlTypeCode.Decimal:
                    attr.Value = XmlConvert.ToString((decimal)value);
                    break;
                case XmlTypeCode.Double:
                    attr.Value = XmlConvert.ToString((double)value);
                    break;
                case XmlTypeCode.Float:
                    attr.Value = XmlConvert.ToString((float)value);
                    break;

                default:
                    attr.Value = value.ToString();
                    break;
            }
        }

        public override bool ShouldSerializeValue(object component)
        {
            return false;
        }

        internal static bool Supports(XAttribute item)
        {
            return TypeCodeToType(item.GetSchemaInfo().SchemaType.TypeCode) != null;
        }
    }
Wenn jetzt ein objekt der Klasse graph einem properyGrid zugewiesen wird, erscheinen die zusätzlichen Attribute wie normale Eigenschaften und sind ebenso editierbar. Typsicherheit ist gegeben (z.B. "100000" ist kein gültiger wert für System.Short)


Alle Zeitangaben in WEZ +1. Es ist jetzt 16:54 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz