forrest-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Tim Williams <william...@gmail.com>
Subject my trip through the view-related pipelines
Date Wed, 07 Sep 2005 21:14:40 GMT
Below is a short and long version of a trip through the view-related
pipelines.  I certainly don't claim to understand every piece of it,
but this exercise has been useful for me to get a good detailed idea
of how views really work beyond the conceptual.  My descriptions may
leave something to be desired but it's worked for me.  I realized last
night that the move to XHTML2  and Views are intimately linked.  Even
if the name and approach changes, I think picking now as a starting
point to "go deep" with them is critical.  To speed up your learning
process, I also pasted in the urls that address each relevant part of
the pipeline -- I think seeing the result along with the code and
description is really helpful.

Hopefully folks that understand better than I, can read and make sure
I'm not spreading errant information here.  I hope this doesn't end up
confusing you more than it helps;)

--tim

Both long and short version go through the generator side of things
first, then the transformer side.  Let me know if something doesn't
make sense, as these were really my notes in analyzing what's going on
and no attempt has been made to actually ensure it logically flows;(

************************************************
          Short Version:
************************************************

The "call chain" through the pipeline keyed on patterns*:

pattern="*.html"

-> pattern="*.page"
--> pattern=(abbreviated)"skinconf.xml"& build-info & tab-* & menu-* &
body-*
--> pattern="prepare.view-nugget.**"
---> pattern="prepare.view.**"

-> pattern="getStylesheet.*.**"
--> pattern="prepare.view.**"
--> pattern="prepare.properties.*.**"
---> pattern="prepare.view.**"

Now, in english/psuedo-code:
- call comes in for an *html page
- call to *page aggregates all source data that might ultimately be
needed/used in contracts to present data to the user.
- call to getstylesheet
-- creates a list of all contracts and their properties
-- uses the view template and this contract-property list as the basis
for an aliased stylesheet inserting contract calls as appropriate. 
Two key templates in the generated stylesheet are getHead and getBody.
 The contract-properties list comes with boolean head and body
attributes that are used to determine whether calls to those contracts
should be inserted at that location.
- apply this dynamically generated stylesheet to the aggregated source
document and send it out.



************************************************
          Long Version:
************************************************

***** The Request **** 
URL request is matched by the following match in viewHelper.xhtml
plugin.  This "kicks
off" views.  

    <map:match pattern="*.html">
      <map:generate src="cocoon://{1}.page"/>
      <map:transform src="cocoon://getStylesheet.xhtml.{1}">
        <map:parameter name="path" value="{0}"/>
      </map:transform>
      <map:transform src="resources/stylesheets/strip_namespaces.xsl"/>
      <map:serialize type="xhtml"/>
    </map:match>

******  The Generator Call Chain ********
The following is a look at the call chain from the generator in the 
original request match whose source is cocoon://{1}.page.  

1.  The generator pulls gets its source from the following in the
internal.view plugin:

    <map:match pattern="*.page">
        <map:aggregate element="site">
          <map:part src="cocoon://skinconf.xml"/>
          <map:part src="cocoon://build-info"/>
          <map:part src="cocoon://tab-{1}.html"/>
          <map:part src="cocoon://menu-{1}.html"/>
          <map:part src="cocoon://body-{1}.html"/>
          <map:part src="cocoon:/prepare.view-nugget.{1}"/>
        </map:aggregate>
        <map:serialize type="xml"/>
    </map:match>

As you might expect, this results in a really large page that just
concatenates the different piece-parts -- some obvious, some maybe
not.

skinconf.xml - colors, familiar stuff
build-info - "info" element with version, project-skin, etc.
tab - tabs html chunk
menu - menu html chunk
body - content wrapped in a div id="content"
prepare-view-nugget.{1} - see next step

Example:
http://localhost:8888/index.page
    
2.  This is just an aggregation of all the content that might be
useful.  Unique to views really  the last part,
"prepare.view-nugget.{1}".  This resolves to the following in the same
internal.view plugin.
 
    <map:match pattern="prepare.view-nugget.**">
       <map:generate src="cocoon:/prepare.view.{1}"/>
       <map:transform src="resources/stylesheets/prepare.view.xsl">
         <map:parameter name="view" value="{1}"/>
       </map:transform>
       <map:transform type="xinclude"/>
        <map:serialize type="xml"/>
    </map:match>

This step and the next are described here because they're so close. 
The short story is that this step results in a "forrest:views" root
element to be aggregated in the step above.  The interesting part is
where these come from.  Using the prepare.view.** match below, it
allows these to be defined in multiple places even with different
extensions. Either way, this is all ultimately to get to a resultant
xml fragment that looks like default.fv.  From a views-user
perspective it may be interesting to dive into where/how all these can
be defined
but for a dev trying to understand the view-pipeline, I think we can
consider the default result, which is default.fv in the internal.view
plugin.  I've taken a look at prepare.view.xsl in the above but am not
really sure what it does.  Either my XSLT skills are really bad, or
the transform is really subtle.
http://localhost:8888/prepare.view-nugget.index.html
    

3.  You'll notice that the generator source for this one is the
following match, again in the
same internal.view plugin. 
    
        <map:match pattern="prepare.view.**">
          <map:act type="fallbackResolverAction">
            <map:parameter value="{1}" name="request"/>
            <map:parameter value="{project:theme}" name="projectFallback"/>
            <map:parameter value="{project:theme-ext}" name="projectExtension"/>
            <map:parameter value="{project:content.xdocs}" name="projectDir"/>
            <map:parameter value="{defaults:view-themes}" name="defaultDir"/>
            <map:parameter value="{defaults:theme}" name="defaultFallback"/>
            <map:parameter value="{defaults:theme-ext}"
name="defaultExtension"/>
            
            <map:generate src="file:/{uri}"/>
            <map:transform
src="resources/stylesheets/prepare.include.templates.xsl"/>
            <map:transform type="xinclude"/>
            <map:serialize type="xml"/>
          </map:act>
    </map:match>
   
 It's probably worth noting that when "call-template" is used to call
an external template, the stylesheet above
prepare.include.templates.xsl is responsible for implementing this by 
wapping out "call-template" with "xi-include".
See for yourself with:
http://localhost:8888/prepare.view.index.html

   
Generator Summary:  So, this has essentially gone out and brought
together all of the source data that we might want to use in our
output to the user, making it accessible to the transform within a
single xml document.
   
   
*******  The Transformer Call Chain **********   
Now if you go back up to the original request pattern="*.html", let's
follow the transformer call chain to understand it.

1. The source of the transformer in the original request comes
directly to the following match in the internal.view plugin.

  <map:match pattern="getStylesheet.*.**">
      <map:aggregate element="forrest:filter">
        <map:part src="cocoon://prepare.view.{2}" />
        <map:part src="cocoon://prepare.properties.{1}.{2}" /> 
      </map:aggregate>
      <map:transform src="resources/stylesheets/prepare.{1}.xsl" >
        <map:parameter name="request" value="{2}"/>
        <map:parameter name="forrestContext" value="{forrest:context}"/>
      </map:transform>
      <map:transform type="xinclude"/> 
      <map:serialize type="xml"/>
    </map:match>

See for yourself with:    
http://localhost:8888/getStylesheet.xhtml.index.html 

We've already seen that the call to prepare.view.{2} will give us an
equivalent to default.fv, so we won't cover that again.  First, go
read about this prepare.properties{1}.{2} below, then come back and
read the rest.

.... 

So now we have a list of properties about all of the contracts that
will be used and a view template document roughly equivalent to
default.fv.  Now this, in my mind is where the magic happens.  I am
unable to describe details of *how* the prepare.xhtml.xsl stylesheet
does it's work but through some pretty clever xsl:namespace-alias, the
contracts are turned into an "aliased" stylesheet(see bottom).  This
is xsl at it's best I think.  Someone smarter than I will have to
explain the details but this is roughly what I gather in psuedo-codo:

for each contract in "prepare.properties"
  locate the contract in "prepare.view"
  create an aliased "call-template" appropriate to that contract
next

grossly oversimplified?  yep, but hopefully you get the idea.

now lets take an example:

the following contract shows up in our properties result set:
<forrest:property name="siteinfo-meta" head="true" body="false" format="xhtml"/>

Now in our resultant stylesheet there will be two template calls that
get most things kicked off, they are: getHead and getBody.  The
looping through the properties let us determine which contracts should
have an aliased call-template for them.  Using our example, the
"siteinfo-meta" contract would get identified as needing to get called
in the alias:getHead template.  So the following would get created for
it:

<alias:template name="getHead">
  <alias:call-template name="siteinfo-meta-head"/>
</alias:template>

If there were other contracts that have "head=true" then they would
also get printed there too based on the loops that reside in getHead. 
Aww shucks, let's go ahead and take a look at that too:

<alias:template name="getHead">
  <xsl:for-each 
    select="/*/forrest:properties/*[@head='true' and count(. |
key('head-template', @name)[1]) = 1]">
    <xsl:variable name="name" select="@name"/>
    <xsl:apply-templates mode="head"
      select="//forrest:contract[@name=$name]"/>
  </xsl:for-each>
</alias:template>

This ultimately creates the templae above.  You see that
siteinfo-meta-head is the only contract where @head='true' hense, the
call-template to it.
    
 2.  We can see that it is aggregating two view related parts to
become the forrest:filter
 
     <map:match pattern="prepare.view.**">
       <map:act type="fallbackResolverAction">
         <map:parameter value="{1}" name="request"/>
         <map:parameter value="{project:theme}" name="projectFallback"/>
         <map:parameter value="{project:theme-ext}" name="projectExtension"/>
         <map:parameter value="{project:content.xdocs}" name="projectDir"/>
         <map:parameter value="{defaults:view-themes}" name="defaultDir"/>
         <map:parameter value="{defaults:theme}" name="defaultFallback"/>
         <map:parameter value="{defaults:theme-ext}" name="defaultExtension"/>
        
         <map:generate src="file:/{uri}"/>
         <map:transform
src="resources/stylesheets/prepare.include.templates.xsl"/>
         <map:transform type="xinclude"/>
         <map:serialize type="xml"/>
       </map:act>
    </map:match>
    
    <map:match pattern="prepare.properties.*.**">
      <map:generate src="cocoon:/prepare.view.{2}"/>
        <map:transform src="resources/stylesheets/prepare.properties.xsl">
          <!--Which output format?-->
          <map:parameter name="format" value="{1}"/>
        </map:transform>
        <map:transform type="xinclude"/>
        <map:serialize type="xml"/>
    </map:match>
  
Interestingly, the second part actually makes a request back to the
first as it's generator  source.  It goes and gets the view
(default.fv equivalent) and includes the properties for each contract
using another the cocoon match="get.contract-property.*.xhtml".  You
can see the result of that here:
http://localhost:8888/get.contract-property.siteinfo-meta.xhtml
  
  So now we have what essentially amounts to a list of properties
about all of the contracts, now
  go back to where you left off reading above.
  
  See for yourself here:
  http://localhost:8888/prepare.properties.xhtml.index.html

Transformer Summary:  So the getStylesheet is all about dynamically
creating an xslt based on contracts and view template.  We create a
list of contracts with their properties (essentially because we need
to know where they reside in the resultant xslt: head or body).  We
then go through each contract and create a real xslt call-template to
it (in an alias namespace obviously).

The use of the alias is tricky at first to those of us who aren't xslt
experts.  Once you think about it it's obvious that if you're using an
xslt to transform something into an xslt, then the namespace of the
output xslt would have to be different so as not to confuse whether a
particular xsl: element belonged to the resultant set or the
stylesheet doing the transform.

Mime
View raw message