You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@cocoon.apache.org by Jonas Ekstedt <ek...@ibg.uu.se> on 2004/12/05 14:09:09 UTC

Implementation of a tag attribute language

Hello

The recent discussions about tag attribute languages has been very
interesting and enlightening. To make it more concrete here are a few
ideas on how to actually implement these concepts.

TALGenerator
------------
* Extends the generator in the template block.
* Does not support tag bundles (ie taglibs)
* Only treats elements containing a tal:do attribute in a special way.

A new TALToken is introduced (for an introduction to tokens see
http://www.mail-archive.com/dev@cocoon.apache.org/msg25040.html). Here's
an example of how a TAL template is compiled:

<table>
  <tr tal:do="forEach(listOfBeans)">
    <td>${it.beanProperty1}</td>
    <td>${it.beanProperty2}</td>
  </tr>
</table>

=> (ignoring whitespace nodes) =>

PlainElementToken  localName="table"
TALToken           localName="tr", do="forEach(listOfBeans)"
PlainElementToken  localName="td"
ExpressionToken    expression="it.beanProperty1"
PlainElementToken  localName="td"
ExpressionToken    expression="it.beanProperty2"

TALToken might look something like:

public class TALToken {
  ...

  public void invoke(ScriptContext context) {
    if (getDoCommand().equals("forEach")) {

      Iterator it = evaluateToIterator(getDoParameter(0));
      while (it.hasNext()) {
        context.getVariables().put("it", it.next());
        context.getConsumer().startElement(uri, localName, qname, attrs)
        invokeBody(context);
        context.getConsumer().endElement(uri, localName, qname);
      }

    } else if (getDoCommand().equals("if")) {

      boolean test = evaluateToBoolean(getDoParameter(0));
      if (test) {
        context.getConsumer().startElement(uri, localName, qname, attrs)
        invokeBody(context);
        context.getConsumer().endElement(uri, localName, qname);
      }

    } else if (...) {
      ...
    }
  }
}

I think a TALGenerator that is limited in scope (ie only supports a
few control structures like if, forEach etc) would be quite easy
to implement if it is built on top of the current template block.

Cheers Jonas

Re: Implementation of a tag attribute language

Posted by Daniel Fagerstrom <da...@nada.kth.se>.
Jonas Ekstedt wrote:
> On Sun, 5 Dec 2004, Daniel Fagerstrom wrote:
<snip/>
> Yeah, that's a better idea. We could even go a step further:
> 
> PlainElementToken  localName="table"
> ForEachTALToken    parameter[0]="listOfBeans"
> IfTALToken         parameter[0]="a < b"
> PlainElementToken  localName="tr"
> ...

Yes.

<snip/>
>>By having a standardized way to pass arguments to both attribute and tag
>>driven directives they can share implementation.
> 
> I'd say they're sufficiently different to warrant separate
> implementations.
We'll see no need to decide now.

> A TALToken parameter can be stored
> as a single Expression object whereas a TagToken attribute is either a
> CharactersToken or an ExpressionToken (or even a mix of both).
Yes, you're right. I don't know, but I would suggest that it is an 
mistake in the implementation of JXTG. Many of JXTGs tags are modelled 
after XSLT and XSLT let the "xsl:" tags "decide" how an attribute should 
be interpreted. I would prefer the same strategy for tags in Template. 
But as we strive for back compabillity I guess it is not possible.

> There are
> probably other differences as well. Also there won't be all that many
> TALTokens so the code duplication will be negligble.
Yes.

<snip/>
>>WDYT, am I missing something? Do you have anything against if I refactor
>>the code in the direction I described above?
> 
> What you propose is certainly a better solution. I always felt
> uneased by getStart(), getStartBody() and the likes. Won't be able to help
> unfortunately as exams are looming.

Good, then I take care of the refactoring.

/Daniel

Re: Implementation of a tag attribute language

Posted by Jonas Ekstedt <ek...@ibg.uu.se>.
On Sun, 5 Dec 2004, Daniel Fagerstrom wrote:

snip...
> > <table>
> >   <tr tal:do="forEach(listOfBeans)">
> >     <td>${it.beanProperty1}</td>
> >     <td>${it.beanProperty2}</td>
> >   </tr>
> > </table>
> >
> > => (ignoring whitespace nodes) =>
> >
> > PlainElementToken  localName="table"
> > TALToken           localName="tr", do="forEach(listOfBeans)"
> > PlainElementToken  localName="td"
> > ExpressionToken    expression="it.beanProperty1"
> > PlainElementToken  localName="td"
> > ExpressionToken    expression="it.beanProperty2"
>
> Something in that direction. I would prefer this:
>
> PlainElementToken  localName="table"
> TALToken           do="forEach(listOfBeans)"
> PlainElementToken  localName="tr"
> PlainElementToken  localName="td"
> ExpressionToken    expression="it.beanProperty1"
> PlainElementToken  localName="td"
> ExpressionToken    expression="it.beanProperty2"
>
> It gives the TALToken access to its enclosing element (with the tal:do
> attribute removed). It also generalizes to multiple directives in an
> natural way:
>
> <tr tal:do="forEach(listOfBeans);if(...)">
> ...
>
> =>
>
> PlainElementToken  localName="table"
> TALToken           do="forEach(listOfBeans)"
> TALToken           do="if(...)"
> PlainElementToken  localName="tr"
> ...
>
> Where the "forEach" TALToken invoke the "if" TALToken while executing
> invokeBody, which in turn can invoke the "tr" PlainElementToken. The
> "getAttributes" method in the TALToken class just calls the
> "getAttributes" method in its child.

Yeah, that's a better idea. We could even go a step further:

PlainElementToken  localName="table"
ForEachTALToken    parameter[0]="listOfBeans"
IfTALToken         parameter[0]="a < b"
PlainElementToken  localName="tr"
...

It shouldn't however, as you say, be possible to add custom TALTokens.

snip...

> By having a standardized way to pass arguments to both attribute and tag
> driven directives they can share implementation.

I'd say they're sufficiently different to warrant separate
implementations. A TALToken parameter can be stored
as a single Expression object whereas a TagToken attribute is either a
CharactersToken or an ExpressionToken (or even a mix of both). There are
probably other differences as well. Also there won't be all that many
TALTokens so the code duplication will be negligble.

snip...

> BTW: I think that your implementation of the script and token structure
> is quite cool in the way that we get the tree structure nearly for free
> and it also seem quite space efficient. But on the other hand I believe
> that the explicit handling data structure implementation details spreads
> to much to other parts of the code. Which will make the implementation
> unecesarilly complex.
>
> I would prefer to refactor the code so that element, attribute and
> character tokens are responsible for their children with methods like
> "addChild(...)", "addAttribute(...)", "getAttributeItterator()" etc.
> This can be implemented efficiently by e.g. using single linked lists of
>   tokens where each token has a reference to the next token.
>
> We can squeze out the last few bits of space efficency later if needed,
> but at this stage in the project I think we should strive for simplicity.
>
> WDYT, am I missing something? Do you have anything against if I refactor
> the code in the direction I described above?

What you propose is certainly a better solution. I always felt
uneased by getStart(), getStartBody() and the likes. Won't be able to help
unfortunately as exams are looming.

Cheers Jonas

Re: Implementation of a tag attribute language

Posted by Daniel Fagerstrom <da...@nada.kth.se>.
Jonas Ekstedt wrote:
> Hello
> 
> The recent discussions about tag attribute languages has been very
> interesting and enlightening.

Both by generating interesting ideas and illustraiting some interesting 
aspects of mail list social dynamics ;)

> To make it more concrete here are a few
> ideas on how to actually implement these concepts.
> 
> TALGenerator
> ------------
> * Extends the generator in the template block.
> * Does not support tag bundles (ie taglibs)
> * Only treats elements containing a tal:do attribute in a special way.
> 
> A new TALToken is introduced (for an introduction to tokens see
> http://www.mail-archive.com/dev@cocoon.apache.org/msg25040.html). Here's
> an example of how a TAL template is compiled:
> 
> <table>
>   <tr tal:do="forEach(listOfBeans)">
>     <td>${it.beanProperty1}</td>
>     <td>${it.beanProperty2}</td>
>   </tr>
> </table>
> 
> => (ignoring whitespace nodes) =>
> 
> PlainElementToken  localName="table"
> TALToken           localName="tr", do="forEach(listOfBeans)"
> PlainElementToken  localName="td"
> ExpressionToken    expression="it.beanProperty1"
> PlainElementToken  localName="td"
> ExpressionToken    expression="it.beanProperty2"

Something in that direction. I would prefer this:

PlainElementToken  localName="table"
TALToken           do="forEach(listOfBeans)"
PlainElementToken  localName="tr"
PlainElementToken  localName="td"
ExpressionToken    expression="it.beanProperty1"
PlainElementToken  localName="td"
ExpressionToken    expression="it.beanProperty2"

It gives the TALToken access to its enclosing element (with the tal:do 
attribute removed). It also generalizes to multiple directives in an 
natural way:

<tr tal:do="forEach(listOfBeans);if(...)">
...

=>

PlainElementToken  localName="table"
TALToken           do="forEach(listOfBeans)"
TALToken           do="if(...)"
PlainElementToken  localName="tr"
...

Where the "forEach" TALToken invoke the "if" TALToken while executing 
invokeBody, which in turn can invoke the "tr" PlainElementToken. The 
"getAttributes" method in the TALToken class just calls the 
"getAttributes" method in its child.

> 
> TALToken might look something like:
> 
> public class TALToken {
>   ...
> 
>   public void invoke(ScriptContext context) {
>     if (getDoCommand().equals("forEach")) {
> 
>       Iterator it = evaluateToIterator(getDoParameter(0));
>       while (it.hasNext()) {
>         context.getVariables().put("it", it.next());
>         context.getConsumer().startElement(uri, localName, qname, attrs)
>         invokeBody(context);
>         context.getConsumer().endElement(uri, localName, qname);
>       }
> 
>     } else if (getDoCommand().equals("if")) {
> 
>       boolean test = evaluateToBoolean(getDoParameter(0));
>       if (test) {
>         context.getConsumer().startElement(uri, localName, qname, attrs)
>         invokeBody(context);
>         context.getConsumer().endElement(uri, localName, qname);
>       }
> 
>     } else if (...) {
>       ...
>     }
>   }
> }

Yes, that will do it, but by separating the TAL handling and element 
handling we get rid of the calls to "startElement" and "endElement", 
that is done in the PlainElementToken handling.

By having a standardized way to pass arguments to both attribute and tag 
driven directives they can share implementation.

The directives should still IMO be implemented as separate classes. Then 
we can install them programatically in the TalTemplateGenerator and 
declare the class as final. In that way we protect the users when they 
become obsessed by self destructive thoughts and want to add their own 
directives ;)

> I think a TALGenerator that is limited in scope (ie only supports a
> few control structures like if, forEach etc) would be quite easy
> to implement if it is built on top of the current template block.

Certainly.

Do you have any suggestions about how to implement the directives parser?

BTW: I think that your implementation of the script and token structure 
is quite cool in the way that we get the tree structure nearly for free 
and it also seem quite space efficient. But on the other hand I believe 
that the explicit handling data structure implementation details spreads 
to much to other parts of the code. Which will make the implementation 
unecesarilly complex.

I would prefer to refactor the code so that element, attribute and 
character tokens are responsible for their children with methods like 
"addChild(...)", "addAttribute(...)", "getAttributeItterator()" etc. 
This can be implemented efficiently by e.g. using single linked lists of 
  tokens where each token has a reference to the next token.

We can squeze out the last few bits of space efficency later if needed, 
but at this stage in the project I think we should strive for simplicity.

WDYT, am I missing something? Do you have anything against if I refactor 
the code in the direction I described above?

/Daniel