Plasma GitLab Archive
Projects Blog Knowledge

3.3. The class type extension

class type [ 'node ] extension =
  object ('self)
    method clone : 'self
      (* "clone" should return an exact deep copy of the object. *)
    method node : 'node
      (* "node" returns the corresponding node of this extension. This method
       * intended to return exactly what previously has been set by "set_node".
       *)
    method set_node : 'node -> unit
      (* "set_node" is invoked once the extension is associated to a new
       * node object.
       *)
  end
This is the type of classes used for node extensions. For every node of the document tree, there is not only the node object, but also an extension object. The latter has minimal functionality; it has only the necessary methods to be attached to the node object containing the details of the node instance. The extension object is called extension because its purpose is extensibility.

For some reasons, it is impossible to derive the node classes (i.e. element_impl and data_impl) such that the subclasses can be extended by new new methods. But subclassing nodes is a great feature, because it allows the user to provide different classes for different types of nodes. The extension objects are a workaround that is as powerful as direct subclassing, the costs are some notation overhead.

The picture shows how the nodes and extensions are linked together. Every node has a reference to its extension, and every extension has a reference to its node. The methods extension and node follow these references; a typical phrase is

self # node # attribute "xy"
to get the value of an attribute from a method defined in the extension object; or
self # node # iter
  (fun n -> n # extension # my_method ...)
to iterate over the subnodes and to call my_method of the corresponding extension objects.

Note that extension objects do not have references to subnodes (or "subextensions") themselves; in order to get one of the children of an extension you must first go to the node object, then get the child node, and finally reach the extension that is logically the child of the extension you started with.

3.3.1. How to define an extension class

At minimum, you must define the methods clone, node, and set_node such that your class is compatible with the type extension. The method set_node is called during the initialization of the node, or after a node has been cloned; the node object invokes set_node on the extension object to tell it that this node is now the object the extension is linked to. The extension must return the node object passed as argument of set_node when the node method is called.

The clone method must return a copy of the extension object; at least the object itself must be duplicated, but if required, the copy should deeply duplicate all objects and values that are referred by the extension, too. Whether this is required, depends on the application; clone is invoked by the node object when one of its cloning methods is called.

A good starting point for an extension class:

class custom_extension =
  object (self)

    val mutable node = (None : custom_extension node option)

    method clone = {< >} 

    method node =
      match node with
          None ->
            assert false
        | Some n -> n

    method set_node n =
      node <- Some n

  end
This class is compatible with extension. The purpose of defining such a class is, of course, adding further methods; and you can do it without restriction.

Often, you want not only one extension class. In this case, it is the simplest way that all your classes (for one kind of document) have the same type (with respect to the interface; i.e. it does not matter if your classes differ in the defined private methods and instance variables, but public methods count). This approach avoids lots of coercions and problems with type incompatibilities. It is simple to implement:

class custom_extension =
  object (self)
    val mutable node = (None : custom_extension node option)

    method clone = ...      (* see above *)
    method node = ...       (* see above *)
    method set_node n = ... (* see above *)

    method virtual my_method1 : ...
    method virtual my_method2 : ...
    ... (* etc. *)
  end

class custom_extension_kind_A =
  object (self)
    inherit custom_extension

    method my_method1 = ...
    method my_method2 = ...
  end

class custom_extension_kind_B =
  object (self)
    inherit custom_extension

    method my_method1 = ...
    method my_method2 = ...
  end
If a class does not need a method (e.g. because it does not make sense, or it would violate some important condition), it is possible to define the method and to always raise an exception when the method is invoked (e.g. assert false).

The latter is a strong recommendation: do not try to further specialize the types of extension objects. It is difficult, sometimes even impossible, and almost never worth-while.

3.3.2. How to bind extension classes to element types

Once you have defined your extension classes, you can bind them to element types. The simplest case is that you have only one class and that this class is to be always used. The parsing functions in the module Pxp_yacc take a spec argument which can be customized. If your single class has the name c, this argument should be

let spec =
  make_spec_from_alist
    ~data_exemplar:            (new data_impl c)
    ~default_element_exemplar: (new element_impl c)
    ~element_alist:            []
    ()
This means that data nodes will be created from the exemplar passed by ~data_exemplar and that all element nodes will be made from the exemplar specified by ~default_element_exemplar. In ~element_alist, you can pass that different exemplars are to be used for different element types; but this is an optional feature. If you do not need it, pass the empty list.

Remember that an exemplar is a (node, extension) pair that serves as pattern when new nodes (and the corresponding extension objects) are added to the document tree. In this case, the exemplar contains c as extension, and when nodes are created, the exemplar is cloned, and cloning makes also a copy of c such that all nodes of the document tree will have a copy of c as extension.

The ~element_alist argument can bind specific element types to specific exemplars; as exemplars may be instances of different classes it is effectively possible to bind element types to classes. For example, if the element type "p" is implemented by class "c_p", and "q" is realized by "c_q", you can pass the following value:

let spec =
  make_spec_from_alist
    ~data_exemplar:            (new data_impl c)
    ~default_element_exemplar: (new element_impl c)
    ~element_alist:            
      [ "p", new element_impl c_p;
        "q", new element_impl c_q;
      ]
    ()
The extension object c is still used for all data nodes and for all other element types.
This web site is published by Informatikbüro Gerd Stolpmann
Powered by Caml