Updating documents via XMPP

Description: This page explains how documents are updated by the server via XMPP in Gradient.

There are two methods by which a server may update documents loaded from it via Gradient:

  1. Send modifications to be made to the client DOM.
  2. Send data for processing by ECMAScript embedded within the client DOM.

The second of these is covered in more detail within the documentation for XMPP event trapping within the DOM environment.

Modifying the client document

Modifications to the client document are made at the element level. Each modification consists of a "document modification directive", or "directive" for short.

Directives are sent over XMPP within a "directive set" extension, an XMPP extension element that can be added as a child element to a presence or message stanza.

A directive may conduct a series of operations on the client document. Each operation is roughly analogous to one of the four main DOM modification methods:

An XPath expression is used to specify which element(s) within the document to operate on.

Note: if you want to see the code behind all this, the example used here are executed from the simpleExample() method of DirectivePacketExtension.

This is best explained by example. Consider the following XML document:

<example>

  <append-to/>

  <to-insert-before/>

  <to-be-replaced/>

  <to-delete/>

</example>

This document is going to be used in the following examples. Each child element of the root will have a different operation carried out on it, as per its name.

<gradient:directive-set xmlns:gradient="http://ex-337.net/xmlns/xmpp/gradient">

  <gradient:directive gradient:appendto="/example/append-to">
    <appended/>
  </gradient:directive>

  <gradient:directive gradient:insertbefore="/example/to-insert-before">
    <inserted/>
  </gradient:directive>

  <gradient:directive gradient:replace="/example/to-be-replaced">
    <replacing/>
  </gradient:directive>

  <gradient:directive gradient:remove="/example/to-delete"/>

</gradient:directive-set>

Each part of this XML is explained below in detail.

Directives explained

The directive set element contains the directives to be applied to the document(s) targeted by the enclosing stanza. How you target documents is explained in the next section.

Each child element of "directive-set" is a directive. All directives except the final one have child elements. Each directive has an XPath expression as one of the following attributes:

  1. appendto

    The element(s) specified by this XPath expression will have the child elements of this directive appended to them. In this case, the XPath expression /example/append-to selects a single element in our example document. This is what the element looks like:

    <append-to/>

    The directive provided above is as follows:

    <gradient:directive gradient:appendto="/example/append-to">
      <appended/>
    </gradient:directive>

    Once this directive has been executed against the elements specified by the XPath expression, the result is as follows:

    <append-to>
      <appended/>
    </append-to>

    If the XPath expression is modified to return more than one element, e.g. /example/*, then the child element(s) of the directive will be appended to all of these elements.

  2. insertbefore

    The elements specified by this XPath expression will have all child elements of this directive inserted before it. In this case, the XPath expression /example/to-insert-before selects a single element, <to-insert-before/>.

    Below is the example XML with two of the four directives applied. The change made by the insertbefore directive is highlighted.

    <example>

      <append-to>
        <appended/>
      </append-to>

      <inserted/>
      <to-insert-before/>

      <to-be-replaced/>

      <to-delete />

    </example>

    The result of the second directive is that the child element of the insertbefore directive has been inserted before the element specified by the XPath expression.

  3. replace

    The elements specified by this xpath expression will be replaced by the child element of this directive.

    Important: if a replace expression is specified in a directive, the replace operation will only occur if and only if there is one child element of the directive. To replace one element with several, see combining directive expressions below.

    Below is the example XML with three of the four directives applied.

    <example>

      <append-to>
        <appended/>
      </append-to>

      <inserted/>
      <to-insert-before/>

      <replacing/>

      <to-delete />

    </example>

    Note that <replacing/> has taken the place of <to-be-replaced/>.

  4. remove

    This operation ignores any child elements of the directive, and deletes all elements specified by the XPath expression. In this case we're deleting the element specified /example/to-delete,which gives us something like this:

    <example>

      <append-to>
        <appended/>
      </append-to>

      <inserted/>
      <to-insert-before/>

      <replacing/>

                    <--note absence of <to-delete /> element

    </example>

This is the basics of directives. Note that although the XPath expressions in the above directive are about as simple as you can get, you can specify practically anything using expressions. For example:

/book/chapter[3]/para[1]

This example from the w3schools XPath tutorial selects the first element named "paragraph" of the third element named "chapter" of a document with "book" as it's root element. Another example:

person[@birthdate <= '1951-06-18']/surname[substring(., 1, 1)='S']/../preceding-sibling::*

This example, taken from the MSXML 4.0 XPath Developer's Guide, selects all preceding siblings of the parent node of all "surname" elements that enclose a text node starting with S that are children of "person" elements having a "birthdate" attribute of less than 1951-06-18. (Note that to include this XPath expression as an attribute of an element, the < should be escaped to &lt;.)

Using directives on namespaced documents

At this stage most documents loaded via Gradient are likely to be SVG documents, in which case it is expected that they will be namespaced, i.e. start with <svg xmlns="http://www.w3.org/2000/svg">.

Specifying namespaces within XPath expressions is extremely ugly, so the solution used by most (if not all) XPath implementations is to execute the expression within a "namespace context" that maps prefixes to namespace URIs.

Gradient derives a namespace context by requiring that all prefixes and namespaces used in the XPath expression be declared using the appropriate xmlns attribute on the directive element.

Consider the following example, an empty SVG document:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 4000 2000"/>
</svg>

Note: the code that creates and then modifies the example document above is in the namespacedExample() method of DirectivePacketExtension.

The document is namespaced with the SVG namespace URI, and an XPath expression such as /svg will not select any elements.

The below directive appends a rectangle to the root element of the document:

<gradient:directive xmlns:svg="http://www.w3.org/2000/svg" gradient:appendto="/svg:svg" >
  <rect xmlns="http://www.w3.org/2000/svg"
   fill="red" height="500" stroke="blue"
   stroke-width="100" width="500" x="5" y="5"/>
</gradient:directive>

Note: The above example includes a fragment of SVG, but it is not a Conforming Included SVG Document Fragment. The design considerations behind this are explained here.

How does this work?

  1. The directive declares the SVG namespace for use in the XPath expression. Note that that the chosen prefix of "svg" is totally arbitrary and could just as easily be "scalablevectorgraphics", although that would make complex XPath expression rather cumbersome.

  2. The XPath expression selects a namespaced element.

  3. The child element is within the SVG namespace, and has no prefix. More about this below.

Once the above directive has been applied to the example document, it looks like so:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 4000 2000">
  <rect fill="red" height="500" stroke="blue" stroke-width="100" width="500" x="5" y="5"/>
</svg>

Prefixes added to the child XML of a directive will carry through to the target XML document. What this means is that you can append XML in any namepace to a document. There are several possible uses for this:

Important:

Child elements of a directive without a namespace declaration will by default be considered to have the Gradient namespace, which is not good.

Child elements of a directive having a prefix matching a namespace declared on that element will be considered as belonging to a different namespace from the target document, even if the declared namespace URI is the same as the document namespace URI. This is why the SVG fragment above declares an xmlns attribute and not an xmlns:svg attribute.

Combining directive operations

It is possible to use one directive to make several modifications to an XML document, using the same XML for each operation.

Operations take place in a set order for each directive, as follows:

  1. If the appendto attribute is present, and there is one or more child element in the directive, then the appendto operation is executed.

  2. Otherwise, if there is no appendto attribute and there is an insertbefore attribute, and there is one or more child element in the directive, then the insertbefore operation is executed.

    An insertbefore attribute will be ignored if an appendto attribute exists in the same directive.

  3. If there is replace attribute, and there is one and only one child element of the directive, then the replace operation is executed.

  4. If there is a remove attribute, then the remove operation is executed.

If an XPath expression for any operation returns zero elements, then that operation has no effect.

Why allow combined directives?

Two reasons:

Replacing one element with several elements

The replace operation, as with the replaceChild DOM operation, replaces one node (always an element in this case) with another. However, you may wish to replace one element with several.

Note: the example used here is executed from the combinedDirectivesExample() method of DirectivePacketExtension.

To do this, you can combine the insertbefore and remove directives:

<gradient:directive gradient:insertbefore="/example/to-insert-before" gradient:remove="/example/to-insert-before">
  <replace-one/>
  <replace-two/>
  <replace-three/>
</gradient:directive>

In this example, two operations are carried out in the same directive:

This is equivalent to a replace directive with multiple child elements.

Events

Modification of an XML document supporting the DOM Level 2 Event Model can fire mutation events that may cause ECMAScript to be executed. Potentially, an appendto or insertbefore operation may fire an ECMAScript event that then uses the new XML for operations on the document.

A remove could subsequently remove the XML that was just added, which (a) saves memory on the client, and (b) resets this part of the document to the state it was in before the directive arrived, modulo any changes made by the ECMAScript.

Theoretically, anyway. I haven't tried this yet, but it seemed like a good idea at the time.

This explains one way of updating documents. The next page explains how you choose what documents to update.

© 2006. Some rights reserved. Author: Ian Sollars.