Using GXML for deserializing XML to GObject

@esodan ping.
Explanation for others, this is a continuation of this discussion.


One more time. I don’t understand why I might need deserialization from XML to GObject. If I work with unknown data. (It’s not possible to create a GObject with certain fields on the fly) ((Maybe I don’t understand something, I’m really new to this all.))

PS I will use GXML anyway because it looks like it will be much more convenient to list nodes with it.
image
Now I see something like this.

  • Get a list of all nodes via child_nodes() at the root.
  • Display them all in GTK.
  • Repeat for each item in this list recursively.

Lets say you have the following XML document:

<gtk:Entry Text="This is a label"/>

This can be mapped to:

class UiEntry : GXml.Element {
    [Description(nick="::Text")]
    public string text { get; set; }
    construct {
         initialize_with_namespace ("gtk", "https://www.gtk.org/ui", "Entry");
    }
}

Now you can use GXml.DomElement.read_from_string() to read above XML document and the UiEntry.text property will get the value of the attribute Text, using the following code:

var e = new UiEntry ();
e.read_from_string("<Gtk:Entry Text=\"This is a label\"/>");

Pay attention to ::Text, because you should use :: in order to help GXml to find the properties you want to map as attributes and what name to use.

Now lets say you have an entry in a container, the concept of container is going away in gtk4, with the code:

<gtk:Box orientation="Horizontal">
   <Gtk:Entry Text="This is a label"/>
   <Gtk:Entry Text="This is a label"/>
   <Gtk:Entry Text="This is a label"/>
</gtk:Box>

First all, we need to define a class for gtk:Box:

class UiEntry : GXml.Element {
    [Description(nick="::orientation")]
    public Gtk.Orientation  orientation { get; set; }
    public UiChildren children { get; set; }
    construct {
         initialize_with_namespace ("gtk", "https://www.gtk.org/ui", "Box");
         child = new UiChildren ();
    }
}

The children property will keep track of your child widgets, so lets define UiChildren class, as a container where is possible to query a UiEntry derived class using its property name as the key:

class UiChildren : GXml.HashMap {
    construct {
        try { initialize_with_key (typeof (UiEntry),"Name"); }
        catch (GLib.Error e) {
           warning ("Initialization error for collection type: %s : %s".printf (get_type ().name(), e.message));
         }
    }
}

Now is possible to read the above XML document using:

var b = new UiBox ();
b.read_from_string("<gtk:Box orientation=\"Horizontal\">
   <Gtk:Entry Text=\"This is a label\"/>
   <Gtk:Entry Text=\"This is a label\"/>
   <Gtk:Entry Text=\"This is a label\"/>
</gtk:Box>");

Above code, will create the set of UiEntry objects and initialize their properties with the value in the declared attribute.

GXml.Element use the node’s name to detect a type to use at GLib.Object.new() to instantiate the object and GLib.Object.set_property() to find the property with a nickname marked with :: to map the attributes to object properties.

Now you have a GObject mapped XML file, which in turn can be serialized to XML using GXml.DomElement.write_string(), you can create a mapping to real Gtk.Widget objects, using Lib.Object.new() to instantiate the object and GLib.Object.set_property() to map from your object to the Gtk one.

The magic about how to create objects on the fly are at:

Parser: https://gitlab.gnome.org/GNOME/gxml/-/blob/master/gxml/XParser.vala

Object Utilities: https://gitlab.gnome.org/GNOME/gxml/-/blob/master/gxml/Object.vala

There’s only one thing I don’t understand. Where does the UiEntry or UIBox class come from if we don’t know in advance what is in the XML. I was wrong when I said about creating objects on the fly, I meant object types, classes.

I think this XML deserialization is necessary just for the implementation of the idea with packages, which I already mentioned. Although this seems very vague to me, it looks like I will have to declare a class copy of each GTK 4 widget class, this seems wrong to me.

Now I have made a kind of interface in which a grid of disconnected nodes can be created. The next thing I will do is find a way to iterate over XML(actually for graph visualization). Now it seems to me that I will just make BFS using child_nodes. Maybe there’s a better way.

Peek 2020-02-25 07-27

The example starts from a given XML structure, then map it to a GObject based API using GXml.

You should start to establish the structure of the XML you are going to use, then you can map it.

If you are going to use .ui files structure, this is the one may you want to map to an GObject based API and avoid to iterate over XML nodes, but on classes and properties.

There are specific rules for any XML structure, normally defined through a XSD or, for glade catalog, using a DTD for validation, but also to know the element’s name and attributes and children.

If you are going to use .ui you should know the XML structure, so you can map it to a GObject API using GXml and make easy to access to your widgets. For example an <object> has an id attribute you can use to create a Hashed table of all objects for quick access using a GXml.HashMap. The main class will be a class, maybe called UiInterface for the top <interface> and a class maybe called UiObject for the <object> node with the id property of type string and a class attribute of type string, where its value will be produced by GLib.Type.name() over a GtkWidget; and an another class maybe called UiChild.

Mapping .ui files using GXml is simple, if you need to help on that, may is time to open a new thread. I think is better to open new threads to keep focused on the topic and if we are going to talk about your application, then a new topic can be useful and better, I think.

Hi, have a small question. I’m just playing with GXML for now. Just made bfs:

            var g = new GXml.Document.from_file (f);
            var q = new Gee.LinkedList<DomNode>();
            var temp = new Gee.ArrayList<DomNode>();
            q.add(g);
            while (!q.is_empty) {
                while (!q.is_empty) {
                    prin("current level length: ", q.size);
                    prin("deleted node: ", q.peek().node_name, " : ",q.peek().text_content!=null?q.peek().text_content:"no content");
                    temp.add_all(q.poll().child_nodes);
                }
                q.add_all(temp);
                temp.clear();// можно наверно оптимизировать сделав temp стаком
            }

Testing on this example

<A>
    <B>
        <D>d_content</D>
        <E>e_content</E>
    </B>
    <C>
        c_content
    </C>
</A>

I get the following output:

current level length: 1
deleted node: #document : no content
current level length: 1
deleted node: A : 
    
    

current level length: 5
deleted node: #text : no content
current level length: 4
deleted node: B : 
        
        
        
    
current level length: 3
deleted node: #text : no content
current level length: 2
deleted node: C : 
        c_content
    
current level length: 1
deleted node: #text : no content
current level length: 6
deleted node: #text : no content
current level length: 5
deleted node: D : d_content
current level length: 4
deleted node: #text : no content
current level length: 3
deleted node: E : e_content
current level length: 2
deleted node: #text : no content
current level length: 1
deleted node: #text : no content
current level length: 2
deleted node: #text : no content
current level length: 1
deleted node: #text : no content

I didn’t expect to get these starting with # nodes. I roughly understand what it is(or not xd), but I did not think that they will come back from the child_nodes() method. Is there a way to avoid them, or might they be needed(I’m not an expert in XML). Now I assume that I will just do filtering on the first character when I redo everything on gpseq.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.