XUL:Templates Plan: Difference between revisions

From MozillaWiki
Jump to navigation Jump to search
No edit summary
 
(31 intermediate revisions by 7 users not shown)
Line 26: Line 26:
# Add a Query Processor that uses an XML datasource, probably using XPath queries.
# Add a Query Processor that uses an XML datasource, probably using XPath queries.
# Add some default handling syntax for other types of datasources where the queries aren't as complex.
# Add some default handling syntax for other types of datasources where the queries aren't as complex.
# Add more control over recursion
# Add any additonal syntax that anyone desires to the Template Builder.
# Add any additonal syntax that anyone desires to the Template Builder.


Line 43: Line 44:
The Datasource is specified using the datasources attribute on the container
The Datasource is specified using the datasources attribute on the container
element. Any form of Datasource may be used, in general it will be a URL of
element. Any form of Datasource may be used, in general it will be a URL of
something. I haven't determined exactly how the template system will know what
something. The datasource retrieval process is described in detail later.
kind of Datasource it is. However, I do think that only one datasource will be
supported at a time, except for RDF where multiple sources may be aggregated
into one using nsIRDFCompositeDataSource.
 
For XML this isn't an issue since one could always append several documents
or nodes together before using them in a template.


A Template Builder is responsible for generating content from the Datasource
A Template Builder is responsible for generating content from the Datasource
Line 174: Line 169:
complex values before a rule is found to match. The Template Builder parses
complex values before a rule is found to match. The Template Builder parses
the <bindings> block, but the Query Processor assigns the bindings.
the <bindings> block, but the Query Processor assigns the bindings.


===The Content Generation Step===
===The Content Generation Step===
Line 204: Line 201:
the nested content is generated and if a different set of rules should be
the nested content is generated and if a different set of rules should be
used.
used.
It isn't clear at this point whether recursion which generates the same result
again should be allowed or rejected. The current template builder doesn't
handle this gracefully for the non-lazily built case (generation of things
that aren't menus or trees) and the Tree Builder seems to drop the duplicated
items. Maybe someone could come up with a reason to have duplicated items, but
I don't think this needs to be supported.


===Content Updating===
===Content Updating===
Line 235: Line 225:
modify an existing one. In this case, in makes sense to disable updating to
modify an existing one. In this case, in makes sense to disable updating to
reduce overhead.
reduce overhead.


==General Syntax==
==General Syntax==
Line 296: Line 288:
for each <rule> and the <conditions> tag inside a <rule> is considered to be
for each <rule> and the <conditions> tag inside a <rule> is considered to be
the query.
the query.
==Simplified Syntax==
There is also a simplified syntax that may be used when there is no need
for multiple rules with conditions - that is all results should be displayed
unconditionally. The <query> and <rule> tags are not used and instead a query
attribute is used directly on the template. The action goes directly inside
the <template> with no additional tags.
  <vbox datasources="{datasource}" ref="{reference point}">
    <template query="{query}">
      {action}
    </template>
  </vbox>
This won't be available for all Query Processors. The intent is to allow a
simpler syntax for simpler cases.


==Backwards Compatible Forms==
==Backwards Compatible Forms==
Line 355: Line 330:
* template with a <query> and a <rule>
* template with a <query> and a <rule>
:    -> Full syntax; the rule should have a <conditions> tag and an <action> tag and may optionally have a <bindings> tag.
:    -> Full syntax; the rule should have a <conditions> tag and an <action> tag and may optionally have a <bindings> tag.
* template with no <query>, no <rule> and a <query> attribute
:    -> Simplified syntax


* template with no <query> and a <rule> with no <action>
* template with no <query> and a <rule> with no <action>
Line 370: Line 343:
  <vbox datasources="{datasource}"
  <vbox datasources="{datasource}"
       ref="{reference point}"
       ref="{reference point}"
       flags="dont-test-empty dont-build-content"
       flags="dont-test-empty dont-build-content dont-recurse"
       template="{template node}"
       template="{template node}"
      containment="{containment predicates}"
      infer="{infer source}"
       sortDirection="ascending"
       sortDirection="ascending"
       sortActive="true"
       sortActive="true"
       sort="?var">
       sort="?var">
   <template processor="{query-processor-name}"
   <template type="{query-processor-name}"
             container="?ref"
             container="?ref"
             member="?member"
             member="?member"
            containment="{containment predicates}"
             query="{query string}"/>
             query="{query string}"/>
     <queryset>
     <queryset>
Line 422: Line 396:
tag but uses a different set of attributes to be more clearer for non-RDF
tag but uses a different set of attributes to be more clearer for non-RDF
usage.
usage.
==Conditions==
Read about [[Template Conditions]]
==Recursion==
Recursion is controled in one of two ways. The 'dont-recurse' flag disables  it for all generated results. If the flag is omitted, the results are queried for a default value. This allows RDF to still be recursive while other types are not. There should also be some determination based on the element tag used.
The second method is to add an element or attribute (to be determined) inside the action, pointing to another <template> via its id. The causes that template to be parsed and used for child generation.
It isn't clear at this point whether recursion which generates a result with the
same id should be allowed or rejected. The current template builder doesn't
handle this gracefully for the non-lazily built case (generation of things
that aren't menus or trees) and the existing Tree Builder seems to drop the
duplicated items. Maybe someone could come up with a reason to have duplicated
items, but I don't think this needs to be supported.
==Datasources==
Datasources are specified using the datasources attribute (singular datasource may be better but might be confusing to switch to a similar name). It specifies the datasource to use in the template. The attribute is a URL or id reference.
A URL reference (<listbox datasources="<nowiki>http://www.example.com/data</nowiki>") will cause that URL to be retrieved and parsed into an XML document. This document is used as the datasource to the template builder. In some to-be-determined manner, the document might instead be parsed into an RDF datasource which would then be used by the template builder. For RDF, multiple URLs may be specified which will be combined into an nsIRDFCompositeDataSource. Non-RDF sources may only specify a single URL. For XML this isn't an issue since one could always append several documents or nodes together before using them in a template.
An id reference is a hash (#) followed by the id of a <datasource> element in the same document. The <datasource> element handles the datasource loading and supplies it to the template builder.
  <datasource id="thesource" type="xml" src="<nowiki>http://www.example.com/data</nowiki>"/>
The datasource element will look up a datasource loader of the given type (in the example "xml"), probably by appending the type to a contract id as rdf: datasources are currently done. This will retrieve the datasource from the src attribute and supply it to the template builder. Multiple templates may refer to the same <datasource> element; in this case the source is only loaded once and shared by all templates that refer to it. Datasources may load asynchronously and will callback to the template builder to start generation.
If there is no src attribute, the tag itself it used as the xml datasource.
An error event (onerror) may be used for error handling. Other attributes on the <datasource> tag may be used for datasource-specific purposes.


==Examples==
==Examples==


Here is an example for generating XML, assuming some simple query
Here is an example for generating XML, assuming some simple query
syntax.
syntax. (note: people should be a different namespace -- there will be issues here)


   <People id="data">
   <datasource id="data" type="xml">
     <Person name="Fred" gender="male"/>
     <people>
    <Person name="Sarah" gender="female"/>
      <person name="Fred" gender="male"/>
   </People>
      <person name="Sarah" gender="female"/>
    </people>
   </datasource>
   <listbox datasources="#data">
   <listbox datasources="#data">
     <template>
     <template>
       <query>
       <query>
         <results expr="Person"/>
         <results expr="person"/>
         <assign var="?name" expr="@name"/>
         <assign var="?name" expr="@name"/>
         <assign var="?gender" expr="@gender"/>
         <assign var="?gender" expr="@gender"/>
Line 472: Line 483:
     </template>
     </template>
   </vbox>
   </vbox>


==API==
==API==


===Query Processor===
See the [[XULTemplatesAPI | XUL Templates API]]
 
<pre>
interface nsIXULTemplateQueryProcessor : nsISupports
{
  /**
  * Initialize for query generation. This will called before the rules are
  * processed and whenever the rules are rebuilt.
  *
  * @param aDatasource datasource for the data
  * @param aBuilder the template builder
  * @param aTemplateNode the <template> node
  */
  void initializeForBuilding(in nsISupports aDatasource,
                            in nsIXULTemplateBuilder aBuilder,
                            in nsIDOMNode aRootNode);
 
  /**
  * Compile a query from a node. The result of this function will later be
  * passed to generateResults for result generation. If null is returned,
  * default query processing will be used instead.
  *
  * The reference variable may be used within the query as a placeholder for
  * the reference point, if it was set as the container attribute on the
  * template.
  *
  * The member variable is determined from the member attribute on the
  * template, or from the uri in the first action's rule if that attribute is
  * not present. A rule processor may use the member variable as a hint to
  * indicate what variable is expected to contain the results.
  *
  * If necessary, the query builder may use other means to determine these
  * variables.
  *
  * @param aBuilder the template builder
  * @param aQuery <query> node to compile
  * @param aRefVariable the reference variable
  * @param aMemberVariable the member variable
  *
  * @returns a compiled query object
  */
  nsISupports compileQuery(in nsIXULTemplateBuilder aBuilder,
                          in nsIDOMNode aQuery,
                          in nsIAtom aRefVariable,
                          in nsIAtom aMemberVariable);
 
  /**
  * Compile a query from a string. The result of this function will later be
  * passed to generateResults for result generation. This method is
  * equivalent in function to compileQuery method but takes the query as a
  * string rather than a node.
  *
  * A query processor is not required to support queries as strings and may
  * throw an NS_ERROR_NOT_IMPLEMENTED error if not.
  *
  * @param aBuilder the template builder
  * @param aQueryString query string to compile
  * @param aRefVariable the reference variable
  * @param aMemberVariable the member variable
  *
  * @returns a compiled query object
  */
  nsISupports compileQueryString(in nsIXULTemplateBuilder aBuilder,
                                in AString aQueryString,
                                in nsIAtom aRefVariable,
                                in nsIAtom aMemberVariable);
 
  /**
  * Generate the results of a query and return them in an enumeration. The
  * enumeration must contain nsIXULTemplateResult objects. If there are no
  * results, an empty enumerator must be returned. The arguments may be used
  * to establish the query and reference point.
  *
  * The context reference (aRef) is a reference point used when calculating
  * results. It is calculated from the ref attribute on the first pass or the
  * parent node when calculating inner children. It may be ignored if it is
  * not deemed relevant. The reference variable specified during query
  * compilation is expected to be a placeholder for the reference point.
  *
  * This method is only called when compileQuery returns a non-null value for
  * a particular query.
  *
  * @param aDatasource datasource for the data
  * @param aRef context reference used as a starting point
  * @param aQuery the compiled query returned from query compilation
  *
  * @returns an enumeration of nsIXULTemplateResult objects as the results
  */
  nsISimpleEnumerator generateResults(in nsISupports aDatasource,
                                      in nsIXULTemplateResult aRef,
                                      in nsISupports aQuery);
 
  /**
  * Add a binding for a particular rule. A binding calculates a result for
  * an expression after a rule has matched. The bindings are optional
  * variable assignments that should be applied after a rule is found to
  * match.
  *
  * @param aRuleNode rule to add binding to
  * @param aVar variable to assign result to
  * @param aRef variable that holds reference value
  * @param aExpr expression used to compute the value to assign
  */
  void addBinding(in nsIDOMNode aRuleNode,
                  in nsIAtom aVar,
                  in nsIAtom aRef,
                  in AString aExpr);
 
  /**
  * Translate a ref attribute string into a result. This is used in the first
  * pass. Other passes may use the result object directly.
  *
  * XXX perhaps remove this and use result id's as the ref
  *
  * @param aDatasource datasource for the data
  * @param aRefString the ref attribute string
  *
  * @return the translated ref
  */
  nsIXULTemplateResult translateRef(in nsISupports aDatasource,
                                    in AString aRefString);
 
  /**
  * Compare two results to determine their order, used when sorting results.
  * This method should return -1 when the left result is less than the right,
  * 0 if both are equivalent, and 1 if the left is greater than the right.
  * The sort should only consider the specified field.
  *
  * This method must only be called with results that were created by this
  * query processor.
  *
  * @param aLeft the left result to compare
  * @param aRight the right result to compare
  * @param aVar field to compare to
  *
  * @param returns -1 if less, 0 if equal, and 1 if greater
  */
  PRInt32 compareResults(in nsIXULTemplateResult aLeft,
                          in nsIXULTemplateResult aRight,
                          in nsIAtom aVar);
};
</pre>
 
===Result Objects===
 
<pre>
interface nsIXULTemplateResult : nsISupports
{
  /**
  * True if the result is a container
  */
  readonly attribute boolean isContainer;
 
  /**
  * True if the result is an empty container
  */
  readonly attribute boolean isEmpty;
 
  /**
  * True if the template builder may process the children and generate child
  * content. If false, child content is not processed. This may be overriden
  * by syntax used in the template rule.
  */
  readonly attribute boolean mayProcessChildren;
 
  /**
  * ID of the result. The content will be given this id. The id should be
  * unique for a query.
  */
  readonly attribute AString id;
 
  /**
  * Item object for the result, generally some datasource specific object
  */
  readonly attribute nsISupports item;
 
  /**
  * Get a binding for a variable such as ?name for this result
  *
  * @param aVar the variable to look up
  *
  * @return the value for the variable or a null if not bound
  */
  nsISupports getBindingFor(in nsIAtom aVar);
 
  /**
  * Get a binding for a variable as a string such as ?name for this result
  *
  * @param aVar the variable to look up
  *
  * @return the value for the variable or a null string if not bound
  */
  AString getBindingAsStringFor(in nsIAtom aVar);
 
  /**
  * Indicate that a particular rule has matched and that output will be
  * generated for it.
  *
  * @param aRuleNode the rule node that matched
  */
  void ruleMatched(in nsIDOMNode aRuleNode);
};
</pre>
 
===Rule Filter===
 
<pre>
interface nsIXULTemplateRuleFilter : nsISupports
{
  /**
  * Evaluate a result for a match. The match method should return true if the
  * rule matches and the result should be accepted, or false if the rule does
  * not match and the result should be dropped.
  *
  * A filter may be added to a rule by calling the builder's addRuleFilter
  * method.
  *
  * @param aDatasource datasource for the data
  * @param aRef the result to examine
  * @param aRule the rule node
  *
  * @return true if the rule matches
  */
  boolean match(in nsISupports aDatasource,
                in nsIXULTemplateResult aRef,
                in nsIDOMNode aRule);
};
</pre>
 
===Some additional methods for the Template Builder (nsIXULTemplateBuilder)===
 
<pre>
  /**
    * Inform the template builder about a result that has been added, changed
    * or removed. This method is expected to be called by a query processor
    * when the datasource has been changed.
    *
    * If both the old result (aOldResult) and the new result (aNewResult) are
    * set, then the result is one that has changed. The results are expected
    * to have the same id. The rules are reapplied to the result and the
    * output content is regenerated. If aIsBindingUpdate is true, then only
    * a rule's binding has changed. In this case, the rules are not reapplied
    * as it is expected that the same rule will still apply.
    *
    * If aOldResult is null, a new result aNewResult is available which
    * should be added to the set of results. The query node that the new
    * result applies to must be specified using the aQuery parameter. The new
    * result will be applied to the rules and the result's RuleMatched method
    * will be called for the matching rule.
    *
    * If aNewResult is null, an existing result aOldResult should be removed
    * as it is no longer valid.
    *
    * XXX old result might be better passed as an id or hash code instead
    *    also, query might be a string query
    *
    * @param aRef reference container id
    * @param aOldResult the old result
    * @param aNewResult the new result
    * @param aQueryNode the query that the result applies to
    * @param aIsBindingUpdate true if only the bindings have changed
    */
    void updateResult(in nsAString& aRef,
                      in nsIXULTemplateResult aOldResult,
                      in nsIXULTemplateResult aNewResult,
                      in nsIDOMNode aQueryNode,
                      in PRBool aIsBindingUpdate);
 
    /**
    * Return the result for a given id. Only one such result is returned and
    * is always the currently active match.
    *
    * @param aId the id to return the result of
    */
    nsIXULTemplateResult getResultForId(in AString aId);
 
    /**
    * Adds a rule filter for a given rule, which may used for specialized
    * rule filtering. Any existing filter on a rule is removed. The default
    * conditions specified inside the <rule> tag are applied before the
    * rule filter is applied.
    *
    * @param aRule the rule to apply the filter to
    * @param aFilter the filter to add
    */
    void addRuleFilter(in nsIDOMNode aRule, in nsIXULTemplateRuleFilter aFilter);
</pre>


==Issues==
==Sort Service==


* atoms are used as variable identifiers. This is faster for comparisons but is only accessible to privileged code.
See the [[XULSortService | XUL Sort Service]]
* the Template Builder should probably have some function which reapplies the rules without performing the query again. This would be useful for non updating cases or when the filters have changed.

Latest revision as of 00:18, 23 April 2006

XUL 2.0 Templates

The goal here is improve the XUL template builder to be more useful to developers. Several proposals have been made, some simple and others more complex. None that I know of have been specified to any level of detail that would make them possible to implement. In this proposal, I provide specifics of how work on the template builder should proceed. It is primarily based on the other proposals.

Some specific things that have been desired include:

  • support for other types of data besides RDF
  • better logging and debugging
  • conditions which are more complex (negations, substring matching, etc...)

Some proposals have been large and difficult to implement quickly. While backwards compatibility with existing templates isn't a long term goal, it must be in the short term, since changing all the existing templates used in Mozilla is a lot of work. Instead, changes should be made incrementally. I've spent some time over the last while beginning the first of the steps outlined below and am quite sure that this is possible.

  1. Break the Template Builder into two components, one to do rule parsing and content generation, and the other to do query and result generation. The former is not dependant on RDF, while the latter is.
  2. Change the datasource handling to be able to handle different types of datasources and allow different Query Processors to be used.
  3. Add a better means of logging and debugging templates.
  4. Add a Query Processor that uses an XML datasource, probably using XPath queries.
  5. Add some default handling syntax for other types of datasources where the queries aren't as complex.
  6. Add more control over recursion
  7. Add any additonal syntax that anyone desires to the Template Builder.

In this last step we can add other features, like externally referenced blocks for parts of the template body, specifying more complex recursion, modifying the DOM generation, or any of the extras others have suggested.

There's also the sort service stuff which should eventually be simplified.

Templates

A template is a container element with a <template> inside it. The <template> is hidden, but its contents specify how to generate content from a Datasource. The content is inserted as siblings after the <template>. (This might later be changed to be anonymous siblings in step 6 above.)

The Datasource is specified using the datasources attribute on the container element. Any form of Datasource may be used, in general it will be a URL of something. The datasource retrieval process is described in detail later.

A Template Builder is responsible for generating content from the Datasource and template. Two subtypes, the Content Builder for normal content, and the Tree Builder for non-content trees are used. The only difference is that while the Content Builder generates DOM nodes from the template, the Tree Builder stores the output internally as a tree of results.

A second object, called a Query Processor, is responsible for generating results to be displayed. However, the Template Builder does the work of generating content from the results.

The Query Processor that is used will probably be dependant on the datasource, but one may specified explicitly in the template, including one implemented by an application in native code or JavaScript. This allows different Query Processors to be used, even for the same kind of data, although only one may be used per template. For instance, one might implement a Query Processor which processes XQuery on an XML datasource.

The Query Processor generally is expected to act as a layer between the Template Builder and the Datasource. The Template Builder doesn't interact with the data in the Datasource directly.

Template building consists of several steps:

The Compilation Step

The Template Builder compiles the queries and rules into an internal form. For the query this is done by calling the Query Processor.

The Query Processor takes a query node, compiles into into whatever form is necessary and then later uses it to generate a set of results.

The syntax of the query node designated by a <query> tag is determined by the Query Processor and has no predefined semantics. However, some form of default syntax will probably be available for simple cases so that Query Processors may be implemented easier.

In the RDF case, the query will contain the <content>, <member> and <triple> tags and will be compiled by the RDF Query Processor into a rule network, as is the case with the current template system.

The Query Step

Given an initial reference point and a query, generate a set of results. The actual specifics of the query and generation are left to a separate object called a Query Processor. The Query Processor takes a query node, compiles into into whatever form is necessary and then uses it to generate a set of results. The reference point may be used as a context for generating results. At the top level of the template, this is specified using the ref attribute.

Each result generated by the Query Processor consists of the following:

  • an id, which is unique for a set of results
  • an indication of whether a result is a container or not
  • an indication of whether a result is empty or not
  • a set of (variable -> value) pairs

How this information is determined is dependant on the Query Processor. For example, for an SQL processor, the variables might be the column names and the values of the variables are the values in those columns for each result.

Example:

 <query>
   select name, address from Users
 </query>

In this example, ?name and ?address will be available in each result. Naturally, for SQL output, there will be no containers. The id of each record might be constructed from the primary key.

Usually, one variable is designated as the member variable. This is typically used in the query to define what part of the query is most relevant to the results. For SQL, for instance, this would have little significance. For other types of data, for example a path-like query like XPath, it would be used to indicate what part of the navigation holds the results to separate it from parts which are used only to gather additional data.

The results from the Query Processor are expected to be returned in order and they will normaly be used in that order by the Template Builder, unless the Template Builder has been specified to use sorting in which case they will later be sorted.

The Query Processor itself may be a separate component, so one that is, for example, specific to SQL or mozStorage or web services would be included as part of those components instead of the template builder. Other simple Query Processors could even be implemented in XBL, for instance, for generating data from an array or JavaScript structure.

The Filter and Match Step

In this step, the Template Builder iterates through a set of rules, once for each result. For each result, an attempt is made to match the result to a set of rules. Each rule consists of three components:

  • a set of match criteria (conditions)
  • an additional set of (variable -> value) pairs to define (bindings)
  • an action section containing the output to generate on a match (action)

The result is first examined using the match criteria. This is defined inside a <conditions> block using various forms. The actual conditions syntax and semantics are defined by the template builder, so a common simple language will be used. Most likely, this will be fairly simple, involving only a set of relational steps (such as ?x < ?y, ?y startswith "A", etc...)

The user may add additional conditions by suppling a Rule Filter which takes as an argument a result and returns true or false if there is a match. This Rule Filter may be written in native code or script for more complex situations. This allows greater flexibility when defining complex rules. However if a separate Rule Filter is not supplied, the default conditional processing will occur.

If a result is determined not to match a rule's conditions, the next rule is examined for that result. This continues until a rule is found which matches. If no rule is found, the result is dropped and does not get used in the output. Only one rule will match a result at a time. Once a rule is found where the conditions are satisfied, the other rules are not examined. Naturally, a rule with empty conditions will match by default.

If a result matches a rule, the bindings (or optional assignments) part adds additional (variable -> value) pairs. This is used to avoid calculating complex values before a rule is found to match. The Template Builder parses the <bindings> block, but the Query Processor assigns the bindings.


The Content Generation Step

The <action> section is used to specify the content that gets generated for the match. This works similarly to the existing XUL template mechanism. Each rule may have a different action, so different content may generated for each rule. One element is expected to have a uri attribute. The content above this element will be generated only once per query, while that element and it descendants will be generated once for each matching result. Naturally, since the content above the element will be generated only for the first match, the action for whichever rule first matches will apply. Later matches will not generate this additional content even if it is different in a different rule.

The element with the uri attribute is given an id attribute equivalent to the id of the corresponding result. The content and its descendants are examined for variable substitution. References to variables in attributes will be replaced by their corresponding values from the result.

The content generation is done entirely by the Template Builder. The Tree Builder is a subtype of the Template Builder which stores the data in a more optimal manner for large sets of data.

Once content generation is complete, the process may recurse to generate nested data. The value of the id of the result is used as the reference point (the ref) of the next iteration. The nested content will be inserted inside the element with the uri attribute. Note that this is how the existing XUL template mechanism works. However, there will need to be more direct control over how the recursion works. In particular, whether it recurses or not, where the nested content is generated and if a different set of rules should be used.

Content Updating

Some Query Processors may wish to support dynamic updating. That is, when the underlying datasource changes, the generated content should update to reflect this. The Query Processor is expected to determine when an update should occur and call the Template Builder indicating that a result needs to be updated. This would be the case in four situations:

  • a new result is available
  • a result has been removed
  • a result has changed such that it may match a different rule
  • a result has changed such that it will still match the same rule. This case applies when only a binding has changed, since the same rule would match.

While the Query Processor is responsible for determining when and which type of change has occured, the Template Builder is responsible for actually generating and removing the necessary content and checking which rule matches.

Note: a Query Processor is not required to support dynamic updating. In fact, there should be a means of turning it off even when it is supported. For instance, it is expected that most XML sources will be loaded from remote sources. It is likely that one would simply request a new document rather than modify an existing one. In this case, in makes sense to disable updating to reduce overhead.


General Syntax

The syntax is intended to be fairly similar to the existing syntax, at least at first. The goal is to implement something that is similar and backwards compatible first to avoid a lot of unnessesary additional work. Here is the general syntax, where items in curly braces are placeholders:

 <vbox datasources="{datasource}" ref="{reference point}">
   <template>
     <query>
       {query}
     </query>
     <rule>
       <conditions>
         {conditions}
       </conditions>
       <bindings>
         {bindings}
       </bindings>
       <action>
         {action}
       </action>
     </rule>
     ...
   </template>
 </vbox>

The <conditions> and <bindings> tags are optional, however the <action> is required. Naturally, without any conditions, the rule will match every result. As a shorthand in this case where there is only one rule, the <action> may be promoted to a location directly inside the template (as a sibling of the <query> tag); the syntax otherwise is the same.

The <queryset> tag which is used to group rules and queries together such that multiple queries may be done in one template, each with its own set of rules.

 <vbox datasources="{datasource}" ref="{reference point}">
   <template>
     <queryset>
       <query> ... </query>
       <rule> ... </rule>
     </queryset>
     <queryset>
       <query> ... </query>
       <rule> ... </rule>
     </queryset>
   </template>
 </vbox>

The Template Builder will process each queryset in sequence, generating results and content as it goes. However, if a later queryset has a query which causes a result to be generated that has the same id as one from an earlier queryset, the later result will be ignored. This is how the existing RDF template generation occurs.

The queryset syntax is intended to be a more formal way of specifying this behaviour, so that is more clear. For backwards compatibility with existing templates, if no <queryset> or <query> tags are present, a queryset is implied for each <rule> and the <conditions> tag inside a <rule> is considered to be the query.

Backwards Compatible Forms

There are several syntax forms uses only for backwards compatibility with existing XUL templates. They are used only when no <query> tag or query attribute is present. Things are determined as follows:

Each <rule> is considered to be wrapped inside a <queryset>. If there are no rules, consider there to be an implied rule containing the contents of the template.

If a <rule> contains a <conditions> element as a child, the <conditions> tag is considered to be the <query> and what would have been the conditions is empty. The Template Builder will invoke the Query Processor in the same way but using a different node. The <bindings> and <action> work as in normal usage.

If a <rule> does not contain a <conditions> element, a default query is used which generates one result for each child of the reference point and uses attributes specified on the <rule> to further specify constraints and variables. The content inside the <rule> in considered to be the action.

Here is an example. A <queryset> is added around the rule, and the <conditions> becomes the <query>.

 <vbox datasources="{datasource}" ref="{reference point}">
   <template>
     <rule>
       <conditions> {query} </conditions>
       <bindings> {bindings} </bindings>
       <action> {action} </action>
     </rule>
     ...
   </template>
 </vbox>

Only the RDF Query Processor needs to support these syntax forms.

Syntaxes Summary

  • template with a <query> and a <rule>
-> Full syntax; the rule should have a <conditions> tag and an <action> tag and may optionally have a <bindings> tag.
  • template with no <query> and a <rule> with no <action>
-> Simplified syntax (backwards compatibility only)
  • template with no <query> and a <rule> with an <action>
-> Full syntax (backwards compatibility only)


Full Syntax

Here is a list of all the tags and attributes:

<vbox datasources="{datasource}"
     ref="{reference point}"
     flags="dont-test-empty dont-build-content dont-recurse"
     template="{template node}"
     containment="{containment predicates}"
     infer="{infer source}"
     sortDirection="ascending"
     sortActive="true"
     sort="?var">
 <template type="{query-processor-name}"
           container="?ref"
           member="?member"
           query="{query string}"/>
   <queryset>
     <query>
       {query}
     </query>
     <rule>
       <conditions>
         <where var="?var"
                rel="contains"
                value="value"
                ignorecase="true"
                negate="true"/>
         <filter expression="{script}"/>
       </conditions>
       <bindings>
         <binding subject="?ref"
                  predicate="{expr}"
                  object="?var"/>
         <assign var="?var"
                 ref="?ref"
                 expr="{expr}"/>
       </bindings>
       <action>
         <label uri="?member"/>
       </action>
     </rule>
     ...
   </queryset>
   ...
 </template>
</vbox>

I'm using some declarative tags for the conditions here. Another possibility is to use a simple expression language in an attribute. The disadvantage of that is that to a developer, one might just as well use JavaScript as the expression language rather than inventing some other language. I'd imagine that using script would be less efficient for simple comparison checks, so a simple <where> tag is used instead. However, the <filter> tag or Rule Filters allow script processing for more complex situations.

The <assign> tag inside the <bindings> tag is equivalent to the <binding> tag but uses a different set of attributes to be more clearer for non-RDF usage.

Conditions

Read about Template Conditions

Recursion

Recursion is controled in one of two ways. The 'dont-recurse' flag disables it for all generated results. If the flag is omitted, the results are queried for a default value. This allows RDF to still be recursive while other types are not. There should also be some determination based on the element tag used.

The second method is to add an element or attribute (to be determined) inside the action, pointing to another <template> via its id. The causes that template to be parsed and used for child generation.

It isn't clear at this point whether recursion which generates a result with the same id should be allowed or rejected. The current template builder doesn't handle this gracefully for the non-lazily built case (generation of things that aren't menus or trees) and the existing Tree Builder seems to drop the duplicated items. Maybe someone could come up with a reason to have duplicated items, but I don't think this needs to be supported.

Datasources

Datasources are specified using the datasources attribute (singular datasource may be better but might be confusing to switch to a similar name). It specifies the datasource to use in the template. The attribute is a URL or id reference.

A URL reference (<listbox datasources="http://www.example.com/data") will cause that URL to be retrieved and parsed into an XML document. This document is used as the datasource to the template builder. In some to-be-determined manner, the document might instead be parsed into an RDF datasource which would then be used by the template builder. For RDF, multiple URLs may be specified which will be combined into an nsIRDFCompositeDataSource. Non-RDF sources may only specify a single URL. For XML this isn't an issue since one could always append several documents or nodes together before using them in a template.

An id reference is a hash (#) followed by the id of a <datasource> element in the same document. The <datasource> element handles the datasource loading and supplies it to the template builder.

 <datasource id="thesource" type="xml" src="http://www.example.com/data"/>

The datasource element will look up a datasource loader of the given type (in the example "xml"), probably by appending the type to a contract id as rdf: datasources are currently done. This will retrieve the datasource from the src attribute and supply it to the template builder. Multiple templates may refer to the same <datasource> element; in this case the source is only loaded once and shared by all templates that refer to it. Datasources may load asynchronously and will callback to the template builder to start generation.

If there is no src attribute, the tag itself it used as the xml datasource.

An error event (onerror) may be used for error handling. Other attributes on the <datasource> tag may be used for datasource-specific purposes.


Examples

Here is an example for generating XML, assuming some simple query syntax. (note: people should be a different namespace -- there will be issues here)

 <datasource id="data" type="xml">
   <people>
     <person name="Fred" gender="male"/>
     <person name="Sarah" gender="female"/>
   </people>
 </datasource>
 <listbox datasources="#data">
   <template>
     <query>
       <results expr="person"/>
       <assign var="?name" expr="@name"/>
       <assign var="?gender" expr="@gender"/>
     </query>
     <rule>
       <conditions>
         <where var="?gender" rel="equals" value="male"/>
       </conditions>
       <action>
         <listitem uri="?" class="isMale" label="?name"/>
       </action>
     </rule>
     <rule>
       <action>
         <listitem uri="?" label="?name"/>
       </action>
     </rule>
   </template>
 </vbox>


An SQL example:

 <vbox datasources="{sql-datasource-reference}">
   <template>
     <query>
       select name, artist, cost from Albums
     </query>
     <action>
       <hbox uri="?">
         <label value="?name"/>
         <label value="?artist"/>
         <label value="?cost"/>
       </action>
     </rule>
   </template>
 </vbox>

API

See the XUL Templates API

Sort Service

See the XUL Sort Service