Element expressions

Element expressions can be used to access the ancestry of an element.

The operators that make up an element expression work in a relative fashion, starting from the innermost element of the markup state, and working their way out to the root. In this way, they function to move an invisible cursor up the element stack. Since the meaning of these operators is best understood by example, each of them will be discussed in turn, using the following simple well-formed XML instance as an input:

  <manual id="M01">
    <chapter id="C01">
      <title id="C01.T">First Chapter</title>
      <p>Introduction paragraph</p>
      <section id="S01.01">
        <title id="S01.01.T">First Section</title>
        <section id="S01.01.01">
          <title id="S01.01.01.T">Nested Section</title>
          <p>We are here.</p>
        </section>
      </section>
    </chapter>
  </manual>
        

Element

element is in a sense an identity operator when it appears in an element expression: in fact it leaves the cursor unmoved. So while processing the innermost p element, the expression name of element would evaluate to p, and the expression number of attributes of element would evaluate to 0.

Because it serves largely as an identity operator, element can typically be left out of most element expressions. However, there are times when it is necessary: a simple example is that of name of, given previously. A more subtle example occurs when processing an external data entity: inside an external-data-entity rule, attribute and attributes refer to the attributes of the entity, not those of the current element. So in the following external-data-entity rule,

  external-data-entity #implied
     repeat over attributes as a
        output key of attribute a || "=%"" || attribute a || "%"%n"
     again
  
     repeat over attributes of element as a 
        output key of attribute a || "=%"" || attribute a || "%"%n"
     again
          

the first loop iterates over the attributes of the external data entity, whereas the second loop qualifies attributes with of element to specify that it is the attributes of the current element which are to be iterated over, not those of the entity.

Parent

parent moves the cursor up one level in the element stack, toward the root.

In the example above, while processing the innermost p element, the expression name of parent would evaluate to section, the expression number of attributes of parent would evaluate to 1, and the expression attribute "id" of parent would evaluate to S01.01.01.

If there is no parent element, a run-time error is thrown. This would be the case, for instance, if the current element is the root of the document.

Open element

Unlike the element and parent operators, the open element operator must be followed by an element name or a parenthesized list of element names. It moves the cursor to the innermost currently-opened element whose name matches one of the provided names; this search includes the current element and its parent.

The expression attribute "id" of open element "section" would evaluate to S01.01.01. Since open element includes the current element, it could also be used to leave the cursor where it is, if the provided list of element names includes p, the name of the current element.

The search for a matching element always stops as soon as a match is found: so using the same example, the expression number of attributes of open element ("manual" | "chapter" | "section") would evaluate to 1, even though elements manual and chapter have no attributes and appear first in the list of provided element names.

If open element fails to find any element matching the provided names, a run-time error is thrown.

Ancestor

The ancestor operator is much like the open element operator, except it excludes the current element.

In the example above, if the cursor is currently sitting on the innermost section element, the expression attribute "id" of ancestor "section" would evaluate to S01.01.

If ancestor fails to find any element matching the provided names, a run-time error is thrown.

Preparent

The preparent operator is much like the open element and ancestor operators, except it excludes both the current element and its parent.

Using the example instance above, if the cursor is currently sitting on the innermost p element, the expression attribute "id" of preparent "section" would evaluate to S01.01.

If preparent fails to find any element matching the provided names, a run-time error is thrown.

Previous

previous is different from any of the other operators mentioned thus far: instead of going up the element stack to ancestor elements, it goes across to a previously-encountered peer element. previous is also more restricted than the other operators mentioned above: it can only be used once in an element expression, and when used it can only appear at the beginning of the expression.

In the example instance above, if the cursor is currently sitting on the innermost p element, the previous element would be the preceding title element, so the expression attribute "id" of previous would evaluate to S01.01.01.T.

If there is no previous element, a run-time error is thrown. This would happen, for instance, if the current element is the first child of its parent.

Doctype

doctype is different from any of the other operators mentioned thus far: it does not represent a relative motion of the cursor through the element stack, but rather an absolute repositioning of the cursor to the root the element stack. Because of this, doctype cannot be combined with any other operator: any operator preceding it would be attempting to reach beyond the root of the element stack, and any operator following it would be redundant.

Using the same instance as above, regardless of the current element, the expression attribute "id" of doctype would evaluate to M01.

If there is no root element, a run-time error is thrown. This would happen, for instance, if there is no active markup parse in scope.

Combining operators

The operators described above can be combined to form more complex queries, subject to the restrictions given (e.g., previous must appear first, doctype cannot be combined with other operators). When reading these more complex element expressions, it is usually best to start from the rightmost operator and work to the left. For example, while processing the innermost p element, the expression attribute "id" of parent of parent will evaluate to S01.01: the rightmost parent operator would move the cursor to the section element whose id attribute is S01.01.01, while the next parent operator to the left moves the cursor one more level, thereby yielding the section element with an id attribute of S01.01. For a more complex example, given the same context the expression attribute "id" of parent of preparent "section" would evaluate to C01: the rightmost preparent "section" moves to the cursor to the section element with an id attribute of S01.01, so the parent operator applied to this yields its parent, which would be the chapter element.

Element expressions with markup element events

An expression of type markup-element-event can be used in element expressions, subject to certain restrictions:

If the second restriction is violated, a run-time error is thrown. A valid expression of type markup-element-event could be obtained by capturing the value of #current-markup-event in an element rule, by creating the event directly using create-element-event, or by picking out values from the elements of shelf.

The effect of applying other operators to an expression of type markup-element-event yields the same results as if the operators were applied when the event was created. So the following brief program

  global markup-element-event event
  
  process
     do xml-parse scan "<a a.a=%"Hello, World!%"><b/></a>"
        output "%c"
     done
  
     output attribute "a.a" of parent of event || "%n"
  
  
  element "b"
     set event to #current-markup-event
  
     output "%c"
  
  
  element #implied
     output "%c"
          

will output Hello, World! at the end of the process rule: the event was created when element b was active, and so event represents the markup state at this point in time. The parent of event at that time was element a, and its attribute a.a had value Hello, World!. Therefore, the expression attribute "a.a" of parent of event evaluates to Hello, World!.

Element expressions as markup events

Just as an expression of type markup-element-event can be used in an element expression, so can a valid but otherwise arbitrary element expression be used as an expression of type markup-element-event. In the previous example, if we know in the element rule for element b that we will eventually only care about the event that fired the parent element rule, then the program can be rewritten

  global markup-element-event event
  
  process
     do xml-parse scan "<a a.a=%"Hello, World!%"><b/></a>"
        output "%c"
     done
  
     output attribute "a.a" of event || "%n"
  
  
  element "b"
     set event to parent
  
     output "%c"
  
  
  element #implied
     output "%c"
          

with the same net output.