slamb.org / projects / axamol / sax-pipeline  

Using AXP Pages

AXP : org.xml.sax.ContentHandler :: JSP : java.io.PrintWriter

In English, JSP (JavaServer Pages) are files with literal text and fragments of Java code. A .jsp file compiles to a .java file and therefore a Java class. This class prints the literal text to a PrintWriter and executes the Java code normally. People like JSPs because they make it easy to produce Java classes which mostly handle text output of HTML webpages.

AXPs use a similar idea. They are XML files with fragments of Java code. A .axp file similarly produces a Java class. The XML elements, attributes, and character data are output to the ContentHandler automatically.

Creating "Hello, World"

Here's an AXP that produces the typical "Hello, world" code:

<axp:root xmlns:axp="http://www.slamb.org/framework/axp" language="java">
  <myElement foo="bar">Hello, world.</myElement>
</axp:root>

This produces a Java class like this one:

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.xml.sax.SAXException;
import org.slamb.framework.Generator;
import org.xml.sax.ContentHandler;
...

public class helloworld_daxp implements Generator {
    ...

    public void generate(HttpServletRequest req, ContentHandler handler)
            throws ServletException, SAXException {
        AttributesImpl __axp_atts;
        char[] __axp_chars;
        {
            __axp_atts = new AttributesImpl();
            __axp_atts.addAttribute("", "foo", "foo", "CDATA", "bar");
            handler.startElement("", "myElement", "myElement", __axp_atts);
            __axp_chars = "Hello world.".toCharArray();
            handler.characters(__axp_chars, 0, __axp_chars.length);
            handler.endElement("", "myElement", "myElement");
        }
    }
}

Note several points about this example:

Adding logic

Okay, we've made an AXP page. But it doesn't do anything we couldn't do with a plain document. So let's put in some logic:

<axp:root xmlns:axp="http://www.slamb.org/framework/axp" language="java">
  <page>
    <axp:logic>
      for (int i = 0; i &lt; 10; i++)
          <iteration num="{i}">The number is {i}</iteration>
    </axp:logic>
  </page>
</axp:root>

Now we're starting to see the power of AXP. This one will produce a Java class with a generate method like this:

AttributesImpl __axp_atts;
char[] __axp_chars;
{
    __axp_atts = new AttributesImpl();
    handler.startElement("", "page", "page", __axp_atts);
    for (int i = 0; i < 10; i++) {
        __axp_atts = new AttributesImpl();
        __axp_atts.addAttribute("", "num", "num", "CDATA", String.numueOf(i));
        handler.startElement("", "iteration", "iteration", __axp_atts);
        __axp_chars = ("The number is " + String.valueOf(i)).toCharArray();
        handler.characters(__axp_chars, 0, __axp_chars.length);
        handler.endElement("", "iteration", "iteration");
    }
    handler.endElement("", "page", "page");
}

And when run, it will produce this:

<page>
  <iteration num="0">The number is 0.</iteration>
  <iteration num="1">The number is 1.</iteration>
  <iteration num="2">The number is 2.</iteration>
  <iteration num="3">The number is 3.</iteration>
  <iteration num="4">The number is 4.</iteration>
  <iteration num="5">The number is 5.</iteration>
  <iteration num="6">The number is 6.</iteration>
  <iteration num="7">The number is 7.</iteration>
  <iteration num="8">The number is 8.</iteration>
  <iteration num="9">The number is 9.</iteration>
</page>

What's new:

Since { and } are used as escapes in the AXP, we need escapes. We use the typical \. So \{ is a literal {. \\ is a literal \.

The technical details: AXP has two types of scopes: logic and data. In a logic scope, we put in Java code. In a data scope, we put in literal text and Java expressions (to be stringified; surrounded by {}s). Attributes are always data scopes. Any element except <axp:logic> starts a new data scope. <axp:data> does nothing but start a data scope. <axp:logic> does nothing but start a logic scope. Entering a data scope implies entering a new Java scope.

Adding dynamic elements and attributes

So what if we want to come up with element and attribute names dynamically? Or add attributes conditionally?

I borrowed a trick from XSLT here: <xsl:element> and <xsl:attribute>. They create elements and attributes with arbitrary names, so:

<axp:element name="{foo}" hardcodedAttribute="blah">
  <axp:logic>
    if (odd) <axp:attribute name="odd">yes</axp:attribute>
  </axp:logic>
</axp:element>

will produce an element with the name held in foo. It will have an "odd" attribute if the odd boolean is set.

If you want a literal attribute name to a dynamic element, you need to create it through <axp:attribute>. Otherwise, you have no way to distinguish it from the name of the element. <axp:attribute> must always happen before any child elements are output.

Adding seemingly-unbalanced elements

AXP normally doesn't allow you to start and end elements in different blocks of code — in fact, this is why it starts and ends blocks of Java code. This makes the compiler cause an error most times you try this. But if you know what you are doing, you can get around it by using the handler directly:

<table>
  <tr>
    <axp:logic>
      int i = 0;
      for (int i = 0; i &lt; numThumbnails; i++) {
          if (i != 0 && i % NUM_COLUMNS == 0) {
              handler.endElement("", "tr", "tr");
              handler.startElement("", "tr", "tr", new AttributesImpl());
          }
          <td><img src="thumb-{i}.jpg" /></td>
      }
    </axp:logic>
  </tr>
</table>

This gives a table of thumbnails that is NUM_COLUMNS columns across. (Much easier than in XSLT!)

Creating unbalanced logic blocks

If you were watching carefully when I presented the scope rules, you might have wondered what happens with a block like this:

<parent>
  <axp:logic>
    if (mybool) {
  </axp:logic>
</parent>
<axp:logic>
  }
</axp:logic>

In the current implementation, it will generate code like this:

{
    __axp_atts = new AttributesImpl();
    handler.startElement("", "parent", "parent", __axp_atts);
    if (mybool) {
        handler.endElement("", "parent", "parent");
    }
}

However this is not to be depended on. This is a limitation of the parser, not an intended behavior.

Importing classes

You can add import declarations at the root level:

<axp:root xmlns:axp="http://www.slamb.org/framework/axp" language="java">
  <axp:import class="java.util.Iterator"/>
  <axp:import class="java.util.Locale"/>
  ...

They must precede all other children.

Including other AXP pages

The <axp:include href="foo.axp"/> directive will include the results of foo.axp at the given point. This declaration must be static; <axp:element name="axp:include" href="..."/> will not have the intended effect. The href attribute's contents may be dynamic.

Using tag libraries

(The following is preliminary. It is significantly different from what is implemented now. It may never be implemented as given.)

AXP supports tag libraries similar to JSP's. These do nothing that is impossible with pure AXP, but can be much more terse.

A few taglibs are even provided:

Static calls are replaced with some magic. This example:

<axp:root xmlns:axp="http://www.slamb.org/framework/axp"
                   xmlns:html="axp-taglib:org.slamb.framework.taglibs.html">
  ...

  <html:form action="submit.do">
    <html:errors/>
    <html:text property="text" size="60"/>
    <html:submit property="submitType">Submit</html:submit>
  </html:form>

creates code like this:


// standard tag <html:form>
{
    __axp_tagstack.push(__axp_tag = new org.slamb.framework.taglibs.html.form(__axp_tagstack, handler));
    __axp_atts = new AttributesImpl();
    __axp_atts.addAttribute("", "action", "action", "CDATA", "submit.do");
    __axp_tag.startElement("axp-taglib:org.slamb.framework.taglibs.html",
                           "html:form", "form", __axp_atts);
    while (__axp_tag.shouldProcessBody()) {
        // standard tag <html:errors>
        {
          __axp_tagstack.push(new org.slamb.frameworks.taglibs.html.errors(handler);
          __axp_atts = new AttributesImpl();
          __axp_tag.startElement("axp-taglib:org.slamb.framework.taglibs.html",
                                 "html:errors", "errors", __axp_atts);
          while (__axp_tag.shouldProcessBody()) {
          }
          __axp_tag.endElement("axp-taglib:org.slamb.framework.taglibs.html",
                                 "html:errors", "errors");
          __axp_tagstack.pop(); __axp_tag = (Tag) __axp_tagstack.peek();
        }

        // standard tag <html:text>
        {
            ...
        }

        // body tag <html:submit>...</html:submit>
        {
            __axp_tagstack.push(new org.slamb.frameworks.taglibs.html.errors(handler);
            __axp_atts = new AttributesImpl();
            ((BodyTag) __axp_tag).doElement("axp-taglib:org.slamb.framework.taglibs.html",
                                   "html:errors", "errors", __axp_atts, "Submit");
            __axp_tagstack.pop(); __axp_tag = (Tag) __axp_tagstack.peek();
        }
    }

    __axp_tag.endElement("axp-taglib:org.slamb.framework.taglibs.html",
                           "html:form", "form");
    __axp_tagstack.pop(); __axp_tag = (Tag) __axp_tagstack.peek();
}

At runtime, a stack is maintained of the active tags. This is how interacting tags can communicate: child tags can examine and modify parent tags.

Some tags, like <html:errors> are body tags. These are a bit special: there must not be any elements generated within. (<axp:logic> generating text and attributes is fine.) Their contents are not stringified with String.valueOf(...); thus, you can pass a primitive or arbitrary Object argument to a body tag in this way.

Tags can create loops by returning from shouldProcessBody a variable number of times.

There probably should be some mechanism to create variables in that scope, too. Hmm. Working on this.

Creating AXPs that produce AXPs

Since AXP's namespace is special, is it possible for AXPs to produce AXPs?

Currently, no. There's been no need. But if this ever comes up, I'll borrow another trick from XSLT: <xsl:namespace-alias>.

(You could also do by working with handler yourself. But then there would be little point in using a AXP instead of plain Java.)