| <?xml version="1.0" encoding="UTF-8"?> |
| <!-- |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| --> |
| <!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V1.2//EN" "http://forrest.apache.org/dtd/document-v12.dtd" [ |
| <!ENTITY s '<code>site.xml</code>'> |
| <!ENTITY linkrewriter '<link href="#linkrewriting_impl">linkrewriter</link>'> |
| ]> |
| <document> |
| <header> |
| <title>Forrest Sitemap Reference</title> |
| </header> |
| <body> |
| <p> |
| Technically, Forrest can be thought of as a |
| <link href="ext:cocoon">Cocoon</link> distribution that has been stripped |
| down and optimized for people with simple site publishing needs. Central |
| to Cocoon, and hence Forrest, is the <strong>sitemap</strong>. The sitemap |
| defines the site's URI space (what pages are available), and how each page |
| is constructed. Understanding the sitemap is the key to understanding |
| Forrest. |
| </p> |
| <note> |
| We advise you to spend time to understand the Apache Cocoon sitemap. See |
| <link href="ext:cocoon/sitemap">Cocoon sitemap</link> and |
| <link href="ext:cocoon/concepts">Cocoon concepts</link> and related |
| component documentation. It is also necessary to understand the "**" and |
| "*" pattern matching and replacements. See the email thread: "Re: explain |
| sitemap matches and pass parameters to transformers" |
| <link href="http://issues.apache.org/jira/browse/FOR-874">FOR-874</link>. |
| </note> |
| <p> |
| This document provides an overview of the special sitemap which is used at |
| the core of Apache Forrest. |
| </p> |
| <warning> |
| The example sitemap fragments might be out-of-date because since this |
| document was written, the core sitemaps in main/webapp/ have changed and |
| some of the specialised processing has moved to plugins. View your source |
| sitemaps when reading this document. (See |
| <link href="https://issues.apache.org/jira/browse/FOR-922">FOR-922</link>.) |
| </warning> |
| <section id="getting_started"> |
| <title>Getting started</title> |
| <p> |
| Forrest's sitemap comprises the multiple |
| $FORREST_HOME/main/webapp/*.xmap files. The main one is |
| <strong>sitemap.xmap</strong> which delegates to others, including to |
| sitemaps in the various |
| <link href="site:plugins/infrastructure">plugins</link>. |
| </p> |
| <p> |
| You can add pre-processing sitemaps to your project |
| <code>src/documentation</code> directory (or wherever |
| <code>${project.sitemap-dir}</code> points to). Any match that is not |
| handled, passes through to be handled by the default Forrest sitemaps - |
| obviously extremely powerful. The capability is described in |
| "<link href="site:project-sitemap">Using project sitemaps</link>". |
| </p> |
| <p> |
| Another way to experiment with the sitemap is to do '<code>forrest |
| run</code>' on a Forrest-using site. Making changes to the core |
| <code>*.xmap</code> files will now be immediately effective at |
| <code>http://localhost:8888/</code> |
| </p> |
| </section> |
| <section id="overview"> |
| <title>Sitemap Overview</title> |
| <p> |
| Forrest's sitemap is divided both physically and logically. The most |
| obvious is the physical separation. There are a number of separate |
| *.xmap files, each defining pipelines for a functional area. Each *.xmap |
| file has its purpose documented in comments at the top. Here is a brief |
| overview of the files, in order of importance. |
| </p> |
| <table> |
| <tr> |
| <th><strong>sitemap.xmap</strong> |
| </th> |
| <td>Primary sitemap file, which delegates responsibility for serving |
| certain URIs to the others (technically called sub-sitemaps). More |
| about the structure of this file later.</td> |
| </tr> |
| <tr> |
| <th>forrest.xmap</th> |
| <td>Sitemap defining Source pipelines, which generate the body section |
| of Forrest pages. All pipelines here deliver XML in Forrest's |
| intermediate "document-v13" format, regardless of originating source |
| or format.</td> |
| </tr> |
| <tr> |
| <th>menu.xmap</th> |
| <td>Pipelines defining the XML that becomes the menu.</td> |
| </tr> |
| <tr> |
| <th>linkmap.xmap</th> |
| <td>Defines a mapping from abstract ("site:index") to physical |
| ("index.html") links for the current page. See |
| <link href="site:linking">Menus and Linking</link> for a conceptual |
| overview, and the <link href="#linkrewriting_impl">Link |
| rewriting</link> section for technical details.</td> |
| </tr> |
| <tr> |
| <th>resources.xmap</th> |
| <td>Serves "resource" files (images, CSS, Javascript).</td> |
| </tr> |
| <tr> |
| <th>raw.xmap</th> |
| <td>Serves files located in <code>src/documentation/content/xdocs</code> |
| that are not to be modified by Forrest.</td> |
| </tr> |
| <tr> |
| <th>plugins.xmap</th> |
| <td>Provides access to the plugins descriptor files.</td> |
| </tr> |
| <tr> |
| <th>aggregate.xmap</th> |
| <td>Generates a single page (HTML or PDF) containing all the content |
| for the site.</td> |
| </tr> |
| <tr> |
| <th>faq.xmap</th> |
| <td>Processes FAQ documents.</td> |
| </tr> |
| <tr> |
| <th>status.xmap</th> |
| <td>Generates <link href="site:changes">changes</link> and |
| <link href="site:todo">todo</link> pages from a single |
| <code>status.xml</code> in the project root. |
| </td> |
| </tr> |
| <tr> |
| <th>issues.xmap</th> |
| <td>Generates a page of content from an RSS feed. Used in Forrest to |
| generate a "current issues" list from JIRA.</td> |
| </tr> |
| <tr> |
| <th>revisions.xmap</th> |
| <td> |
| Support for HOWTO documents that want "revisions". Revisions are |
| XML snippets containing comments on the main XML file. The main |
| pipeline here automatically appends a page's revisions to the |
| bottom. |
| </td> |
| </tr> |
| <tr> |
| <th>dtd.xmap</th> |
| <td>A Source pipeline that generates XML from a DTD, using Andy |
| Clark's |
| <link href="http://www.apache.org/~andyc/neko/doc/dtd/index.html">DTD |
| Parser</link>. Useful for documenting DTD-based XML schemas, such |
| as <link href="site:dtd-docs">Forrest's own DTDs</link>. |
| </td> |
| </tr> |
| <tr> |
| <th>profiler.xmap</th> |
| <td>Defines the "profiler" pipeline. allowing pipelines to be benchmarked.</td> |
| </tr> |
| </table> |
| </section> |
| <!-- |
| <section> |
| <title>Logical structure</title> |
| <p>There are a few major groups of sitemap pipelines</p> |
| <dl> |
| <dt>Content pipelines</dt> |
| <dd>These define the body (without menu and header) for HTML pages, and all the content of PDFs.</dd> |
| <dt>Menu pileines. |
| </dl> |
| </section> |
| --> |
| <section id="source_pipelines"> |
| <title>Source pipelines (**.xml)</title> |
| <p> |
| Most *.xmap files (forrest, aggregate, faq, status, issues, revisions, |
| dtd) define Source pipelines. Source pipelines define the content (body) |
| XML for site pages. The input XML format can be any format |
| (document-v13, Docbook, RSS, FAQ, Howto) and from any source (local or |
| remote). The output format is always Forrest's intermediate |
| "document-v13" format. |
| </p> |
| <p> |
| Source pipelines always have a "<code>.xml</code>" extension. Thus, |
| <link href="index.xml">index.xml</link> gives you the XML source for the |
| index page. Likewise, <link href="faq.xml">faq.xml</link> gives you XML |
| for the FAQ (transformed from FAQ syntax), and |
| <link href="changes.xml">changes.xml</link> returns XML from the |
| status.xml file. Take any page, and replace its extension |
| (<code>.html</code> or <code>.pdf</code>) with <code>.xml</code> and |
| you'll have the Source XML. |
| </p> |
| <p> |
| This is quite powerful, because we now have an abstraction layer, or |
| "virtual filesystem", on which the rest of Forrest's sitemap can build. |
| Subsequent layers don't need to care whether the XML was obtained |
| locally or remotely, or from what format. Wikis, RSS, FAQs and Docbook |
| files are all processed identically from here on. |
| </p> |
| <source> |
| (subsequent Forrest pipelines) |
| | |
| --------+------------------------^------------------------------------------ |
| | STANDARD FORREST FORMAT (current document-v13) |
| +-----^-------^--------^------------^------^-----^-----^------^----- |
| SOURCE | | | | | | | | |
| FORMATS doc-v11 doc-v13 doc-v20 ... Docbook FAQ Howto Wiki RSS ?? |
| (*.xml) |
| (in forrest.xmap, faq.xmap, etc) |
| </source> |
| <section id="forrest_xmap"> |
| <title>forrest.xmap</title> |
| <p> |
| Most of the usual Source pipelines are defined in |
| <code>forrest.xmap</code> which is the default (fallback) handler for |
| <code>**.xml</code> pages. The forrest.xmap uses the |
| <link href="site:cap">SourceTypeAction</link> to determine the type of |
| XML it is processing, and converts it to document-v13 if necessary. |
| </p> |
| <p> |
| For instance, say we are rendering <link href="site:write-howto">a |
| Howto document</link> called "howto-howto.xml". It contains this |
| DOCTYPE declaration: |
| </p> |
| <source> |
| <!DOCTYPE howto PUBLIC "-//APACHE//DTD How-to V1.3//EN" |
| "http://forrest.apache.org/dtd/howto-v13.dtd"></source> |
| <p> |
| The SourceTypeAction sees this, and applies this transform to get it |
| to document-v13: |
| </p> |
| <source> |
| <![CDATA[ |
| <map:when test="howto-v13"> |
| <map:transform src="{forrest:forrest.stylesheets}/howto-to-document.xsl" /> |
| </map:when> |
| ]]> |
| </source> |
| <!-- |
| if we link to an intermediate .xml file, the CLI crawler tries |
| to fetch the @hrefs from it but they still have site: in them |
| which causes it to break |
| |
| <p> |
| The intermediate result is visible at the URL |
| <link href="../howto/howto-howto.xml">howtos/howto-howto.xml</link>. |
| </p> |
| --> |
| </section> |
| <section id="other_source"> |
| <title>Other source pipelines</title> |
| <p> |
| As mentioned above, all non-core Source pipelines are distributed in |
| independent <code>*.xmap</code> files. There is a block of |
| <code>sitemap.xmap</code> which simply delegates certain requests to |
| these subsitemaps: |
| </p> |
| <source> |
| <![CDATA[ |
| <!-- Body content --> |
| <map:match pattern="**.xml"> |
| <map:match pattern="locationmap.xml"> |
| <map:generate src="{forrest:forrest.locationmap}" /> |
| <map:serialize type="xml"/> |
| </map:match> |
| <map:match pattern="plugins.xml"> |
| <map:mount uri-prefix="" src="plugins.xmap" check-reload="yes" /> |
| </map:match> |
| <map:match pattern="pluginDocs/plugins_(.*)/index(|\.source).xml" type="regexp"> |
| <map:mount uri-prefix="" src="plugins.xmap" check-reload="yes" /> |
| </map:match> |
| <map:match pattern="linkmap.*"> |
| <map:mount uri-prefix="" src="linkmap.xmap" check-reload="yes" /> |
| </map:match> |
| <map:match pattern="**faq.xml"> |
| <map:mount uri-prefix="" src="faq.xmap" check-reload="yes" /> |
| </map:match> |
| <map:match pattern="community/**index.xml"> |
| <map:mount uri-prefix="" src="forrest.xmap" check-reload="yes" /> |
| </map:match> .... |
| ....]]> |
| </source> |
| <section id="late_binding_pipelines"> |
| <title>Late-binding pipelines</title> |
| <p> |
| One point of interest here is that the sub-sitemap is often not |
| specific about which URLs it handles, and relies on the caller (the |
| section listed above) to only pass relevant requests to it. We term |
| this "binding a URL" to a pipeline. |
| </p> |
| <p> |
| For instance, the main pipeline in <code>faq.xmap</code> matches |
| <code>**.xml</code>, but only <code>**faq.xml</code> requests are |
| sent to it. |
| </p> |
| <p> |
| This "late binding" is useful, because the whole URL space is |
| managed in <code>sitemap.xmap</code> and not spread over lots of |
| *.xmap files. For instance, say you wish all <code>*.xml</code> |
| inside a "<code>faq/</code>" directory to be processed as FAQs. Just |
| override <code>sitemap.xmap</code> and redefine the relevant source |
| matcher: |
| </p> |
| <source> |
| <![CDATA[ |
| <map:match pattern="**faq.xml"> |
| <map:mount uri-prefix="" src="faq.xmap" check-reload="yes" /> |
| </map:match>]]> |
| </source> |
| </section> |
| </section> |
| </section> |
| <section id="output_pipelines"> |
| <title>Output pipelines</title> |
| <p> |
| To recap, we now have a <code>*.xml</code> pipeline defined for each |
| page in the site, emitting standardized XML. These pipeline definitions |
| are located in various *.xmap files, notably forrest.xmap |
| </p> |
| <p> |
| We now wish to render the XML from these pipelines to output formats |
| like HTML and PDF. |
| </p> |
| <section id="pdf"> |
| <title>PDF output</title> |
| <note> |
| PDF is now generated via the org.apache.forrest.plugin.output.pdf |
| plugin. |
| </note> |
| <p> |
| Easiest case first; PDFs don't require menus or headers, so we can |
| simply transform our intermediate format into XSL:FO, and from there |
| to PDF. This is done by the following matches in |
| <code>output.xmap</code> from the pdf plugin ... |
| </p> |
| <source> |
| <![CDATA[ |
| <!-- Match requests for XSL:FO documents --> |
| <map:match type="regexp" pattern="^(.*?)([^/]*).fo$"> |
| <map:select type="exists"> |
| <map:when test="{lm:project.{1}{2}.fo}"> |
| <map:generate src="{lm:project.{1}{2}.fo}"/> |
| </map:when> |
| <map:otherwise> |
| <map:aggregate element="site"> |
| <map:part src="cocoon://module.properties.properties"/> |
| <map:part src="cocoon://skinconf.xml"/> |
| <map:part src="cocoon://{1}{2}.xml"/> |
| </map:aggregate> |
| <map:transform type="xinclude"/> |
| <map:transform type="]]>&linkrewriter;<![CDATA[" src="cocoon://{1}linkmap-{2}.fo"/> |
| <map:transform src="{lm:pdf.transform.document.fo}"> |
| <map:parameter name="imagesdir" value="{properties:resources.images}/"/> |
| <map:parameter name="xmlbasedir" value="{properties:content.xdocs}{1}"/> |
| <map:parameter name="path" value="{1}"/> |
| </map:transform> |
| </map:otherwise> |
| </map:select> |
| <map:serialize type="xml"/> |
| </map:match> |
| ]]> |
| </source> |
| <p> |
| This section of the pipeline matches requests for XSL:FO |
| documents by using a regular expression match to break the |
| request into directory (.*?) and filename ([^/]*) parts. If |
| the XSL:FO document exists in the project (the |
| <code>exists</code> selector), it is used; otherwise, the |
| XSL:FO is generated: |
| </p> |
| <ol> |
| <li> |
| The properties input module, skinconf and the <link |
| href="#source_pipelines">source document</link> are |
| combined into an aggregate |
| </li> |
| <li> |
| XInclude elements are processed |
| </li> |
| <li> |
| Links are rewritten |
| </li> |
| <li> |
| The source as generated from the preceding steps is |
| transformed by the stylesheet with the locationmap hint |
| <code>pdf.transform.document.fo</code> and serialized as |
| the final XSL:FO document |
| </li> |
| </ol> |
| <source> |
| <![CDATA[ |
| <!-- Match requests for PDF documents --> |
| <map:match type="regexp" pattern="^(.*?)([^/]*).pdf$"> |
| <map:select type="exists"> |
| <map:when test="{lm:project.{1}{2}.pdf}"> |
| <map:read src="{lm:project.{1}{2}.pdf}"/> |
| </map:when> |
| <map:when test="{lm:project.{1}{2}.fo}"> |
| <map:generate src="{lm:project.{1}{2}.fo}"/> |
| <map:transform type="i18n"> |
| <map:parameter name="locale" value="{../locale}"/> |
| </map:transform> |
| <map:serialize type="fo2pdf"/> |
| </map:when> |
| <map:otherwise> |
| <map:generate src="cocoon://{1}{2}.fo"/> |
| <map:transform type="i18n"> |
| <map:parameter name="locale" value="{../locale}"/> |
| </map:transform> |
| <map:serialize type="fo2pdf"/> |
| </map:otherwise> |
| </map:select> |
| </map:match> |
| ]]> |
| </source> |
| <p> |
| This next section of the pipeline matches requests for PDF |
| documents in a manner similar to the previous match for |
| XSL:FO documents. If the PDF document exists in the project, |
| it is passed directly to the client. If the XSL:FO document |
| exists for the requested PDF, the XSL:FO is serialized by |
| the fo2pdf serializer and passed to the client as PDF (after |
| i18n is handled by the i18n transformer). When neither PDF |
| nor XSL:FO exists, XSL:FO is generated by the match |
| described above, i18n elements are processed for the current |
| locale, and the result is serialized as PDF. |
| </p> |
| </section> |
| <section id="html"> |
| <title>HTML output</title> |
| <p> |
| Generating HTML pages is more complicated, because we have to merge |
| the page body with a menu and tabs, and then add a header and footer. |
| Here is the <code>*.html</code> matcher in <code>sitemap.xmap</code> |
| ... |
| </p> |
| <source> |
| <![CDATA[ |
| 1 <map:match pattern="*.html"> |
| 2 <map:aggregate element="site"> |
| 3 <map:part src="cocoon:/skinconf.xml"/> |
| 4 <map:part src="cocoon:/build-info"/> |
| 5 <map:part src="]]><link href="#tab_pipeline">cocoon:/tab-{0}</link><![CDATA["/> |
| 6 <map:part src="]]><link href="#menu_pipeline">cocoon:/menu-{0}</link><![CDATA["/> |
| 7 <map:part src="]]><link href="#body_pipeline">cocoon:/body-{0}</link><![CDATA["/> |
| 8 </map:aggregate> |
| 9 <map:call resource="skinit"> |
| 10 <map:parameter name="type" value="transform.site.xhtml"/> |
| 11 <map:parameter name="path" value="{0}"/> |
| 12 </map:call> |
| 13 </map:match> |
| ]]> |
| </source> |
| <p> |
| So <link href="index.html">index.html</link> is formed by |
| aggregating <code>skinconf.xml</code>, |
| <code>build-info</code>, <link |
| href="body-index.html">body-index.html</link> and <link |
| href="menu-index.html">menu-index.html</link> and <link |
| href="tab-index.html">tab-index.html</link> and then |
| applying the <code>site-to-xhtml.xsl</code> stylesheet to |
| the result. |
| </p> |
| <p> |
| The conversion from <code>transform.site.xhtml</code> to |
| <code>site-to-xhtml.xsl</code> (line 10 above) is handled by |
| the locationmap in this fragment from |
| <code>locationmap-transforms.xml</code>: |
| </p> |
| <source> |
| <![CDATA[ |
| <match pattern="transform.*.*"> |
| <select> |
| <location src="{properties:skins-dir}{forrest:forrest.skin}/xslt/html/{1}-to-{2}.xsl" /> |
| <location src="{forrest:forrest.context}/skins/{forrest:forrest.skin}/xslt/html/{1}-to-{2}.xsl"/> |
| <location src="{forrest:forrest.context}/skins/common/xslt/html/{1}-to-{2}.xsl"/> |
| <location src="{forrest:forrest.stylesheets}/{1}-to-{2}.xsl"/> |
| </select> |
| </match> |
| ]]> |
| </source> |
| <p> |
| There is a nearly identical matcher for HTML files in subdirectories: |
| </p> |
| <source> |
| <![CDATA[ |
| <map:match pattern="**/*.html"> |
| <map:aggregate element="site"> |
| <map:part src="cocoon:/skinconf.xml"/> |
| <map:part src="cocoon:/build-info"/> |
| <map:part src="]]><link href="#tab_pipeline">cocoon:/{1}/tab-{2}.html</link><![CDATA["/> |
| <map:part src="]]><link href="#menu_pipeline">cocoon:/{1}/menu-{2}.html</link><![CDATA["/> |
| <map:part src="]]><link href="#body_pipeline">cocoon:/{1}/body-{2}.html</link><![CDATA["/> |
| </map:aggregate> |
| <map:call resource="skinit"> |
| <map:parameter name="type" value="transform.site.xhtml"/> |
| <map:parameter name="path" value="{0}"/> |
| </map:call> |
| </map:match> |
| ]]> |
| </source> |
| </section> |
| </section> |
| <section id="intermediate_pipelines"> |
| <title>Intermediate pipelines</title> |
| <section id="body_pipeline"> |
| <title>Page body</title> |
| <p> |
| Here is the matcher which generates the page body: |
| </p> |
| <source> |
| <![CDATA[ |
| 1 <map:match pattern="**body-*.html"> |
| 2 <map:generate src="cocoon:/{1}{2}.xml"/> |
| 3 <map:transform type="idgen"/> |
| 4 <map:transform src="{lm:transform.xml.xml-xpointer-attributes}"/> |
| 5 <map:transform type="xinclude"/> |
| 6 <map:transform type="]]>&linkrewriter;<![CDATA[" src="cocoon:/{1}linkmap-{2}.html"/> |
| 7 <map:transform src="{lm:transform.html.broken-links}" /> |
| 8 <map:call resource="skinit"> |
| 9 <map:parameter name="type" value="transform.xdoc.html"/> |
| 10 <map:parameter name="path" value="{1}{2}.html"/> |
| 11 <map:parameter name="notoc" value="false"/> |
| 12 </map:call> |
| 13 </map:match> |
| ]]> |
| </source> |
| <ol> |
| <li>In our matcher pattern, {1} will be the directory (if any) and {2} |
| will be the filename.</li> |
| <li>First, we obtain XML content from a source pipeline</li> |
| <li><p> |
| We then apply a custom-written |
| <code>IdGeneratorTransformer</code>, which ensures that every |
| <section> has an "id" attribute if one is not supplied, by |
| generating one from the <title> if necessary. For example, |
| <idgen> will transform: |
| </p> |
| <source> |
| <section> |
| <title>How to boil eggs</title> |
| ... |
| </source> |
| <p> |
| into: |
| </p> |
| <source> |
| <section id="How+to+boil+eggs"> |
| <title>How to boil eggs</title> |
| ... |
| </source> |
| <p> |
| Later, the <code>document-to-html.xsl</code> stylesheet |
| will create an <a name> element for every section, |
| allowing this section to be referred to as |
| <code>index.html#How+to+boil+eggs</code>. <code>document-to-html.xsl</code> |
| is looked up by the key <code>transform.xdoc.html</code> |
| in the locationmap in line 9 above. See |
| <code>locationmap-transforms.xml</code> for this match. |
| </p> |
| </li> |
| <li>We then expand XInclude elements.</li> |
| <li>and <link href="#linkrewriting_impl">rewrite links</link>..</li> |
| <li>and then finally apply the stylesheet that generates a fragment of |
| HTML (minus the outer elements like |
| <html> and <body>) suitable for merging with the menu and tabs.</li> |
| </ol> |
| </section> |
| <section id="menu_pipeline"> |
| <title>Page menu</title> |
| <p> |
| In the <code>sitemap.xmap</code> file, the matcher generating HTML for |
| the menu is: |
| </p> |
| <source> |
| <![CDATA[ |
| 1 <map:match pattern="**menu-*.html"> |
| 2 <map:generate src="cocoon:/{1}book-{2}.html"/> |
| 3 <map:transform type="]]>&linkrewriter;<![CDATA[" src="cocoon:/{1}linkmap-{2}.html"/> |
| 4 <map:transform src="{lm:transform.html.broken-links}" /> |
| 5 <map:call resource="skinit"> |
| 6 <map:parameter name="type" value="transform.book.menu"/> |
| 7 <map:parameter name="path" value="{1}{2}.html"/> |
| 8 </map:call> |
| 9 <map:serialize type="xml"/> |
| 10 </map:match> |
| ]]> |
| </source> |
| <p> |
| We get XML from a "book" pipeline, |
| <link href="#linkrewriting_impl">rewrite links</link>, and apply the |
| <code>book-to-menu.xsl</code> stylesheet to generate HTML. |
| </p> |
| <p> |
| How the menu XML is actually generated (the *book-*.html pipeline) is |
| sufficiently complex to require a |
| <link href="#menu_generation_impl">section of its own</link>. |
| </p> |
| </section> |
| <section id="tab_pipeline"> |
| <title>Page tabs</title> |
| <source> |
| <![CDATA[ |
| <map:match pattern="**tab-*.html"> |
| <map:mount uri-prefix="" src="tabs.xmap" check-reload="yes" /> |
| </map:match> |
| ]]> |
| </source> |
| <p> |
| And the match from <code>tabs.xmap</code>: |
| </p> |
| <source> |
| <![CDATA[ |
| 1 <map:match pattern="**tab-*.html"> |
| 2 <map:generate src="{lm:project.tabs.xml}"/> |
| 3 <map:transform type="xinclude"/> |
| 4 <map:select type="config"> |
| 5 <map:parameter name="value" value="{properties:forrest.i18n}"/> |
| 6 <map:when test="true"> |
| 7 <map:act type="locale"> |
| 8 <map:transform src="{lm:transform.book.book-i18n}"/> |
| 9 <map:transform type="i18n"> |
| 10 <map:parameter name="locale" value="{locale}"/> |
| 11 </map:transform> |
| 12 </map:act> |
| 13 </map:when> |
| 14 </map:select> |
| 15 <map:transform type="]]>&linkrewriter;<![CDATA[" src="cocoon:/{1}linkmap-{2}.html"/> |
| 16 <map:call resource="skinit"> |
| 17 <map:parameter name="type" value="transform.tab.menu"/> |
| 18 <map:parameter name="path" value="{1}{2}.html"/> |
| 19 </map:call> |
| 20 </map:match> |
| ]]> |
| </source> |
| <p> |
| All the smarts are in the <code>tab-to-menu.xsl</code> |
| stylesheet (resolved by the locationmap in line 17), which |
| needs to choose the correct tab based on the current path. |
| Currently, a "longest matching path" algorithm is |
| implemented. See the <code>tab-to-menu.xsl</code> stylesheet |
| for details. |
| </p> |
| </section> |
| </section> |
| <section id="resolvingResources"> |
| <title>Resolving Resources</title> |
| <p> |
| Many resources are resolved by the locationmap. This allow us to provide |
| many alternative locations for a file without cluttering up the sitemap |
| with multiple processing paths. We use a strict naming convention to |
| help make the sitemaps more readable. This is described in the |
| <link href="site:locationmap/namingConvention">Locationmap</link> |
| documentation. |
| </p> |
| </section> |
| <section id="menu_generation_impl"> |
| <title>Menu XML generation</title> |
| <p> |
| The "book" pipeline is defined in <code>sitemap.xmap</code> as: |
| </p> |
| <source> |
| <![CDATA[ |
| <map:match pattern="**book-*.html"> |
| <map:mount uri-prefix="" src="menu.xmap" check-reload="yes" /> |
| </map:match> |
| ]]> |
| </source> |
| <p> |
| Meaning that it is defined in the <code>menu.xmap</code> file. In there |
| we find the real definition, which is quite complicated, because there |
| are three supported menu systems (see <link href="site:linking">menus |
| and linking</link>). We will not go through the sitemap itself |
| (menu.xmap), but will instead describe the logical steps involved: |
| </p> |
| <ol> |
| <li>Take site.xml and expand hrefs so that they are all |
| root-relative.</li> |
| <li><p> |
| Depending on the <code>forrest.menu-scheme</code> property, we now |
| apply one of the two algorithms for choosing a set of menu links |
| (described in <link href="site:menu_generation">menu |
| generation</link>): |
| </p> |
| <ul> |
| <li><p> |
| For "@tab" menu generation, we first ensure each site.xml node |
| has a tab attribute (inherited from a parent if necessary), and |
| then pass through nodes whose tab attribute matches that of the |
| "current" node. |
| </p> |
| <p> |
| For example, say our current page's path is |
| <code>community/howto/index.html</code>. In |
| <code>site.xml</code> we look for the node with this |
| "<code>href</code>" and discover its "<code>tab</code>" |
| attribute value is "<code>howtos</code>". We then prune the |
| <code>site.xml</code>-derived content to contain only nodes with |
| <code>tab="howtos"</code>. |
| </p> |
| <p> |
| All this is done with XSLT, so the sitemap snippet does not |
| reveal this complexity: |
| </p> |
| <source> |
| <![CDATA[ |
| <map:transform src="resources/stylesheets/site-to-site-normalizetabs.xsl" /> |
| <map:transform src="resources/stylesheets/site-to-site-selectnode.xsl"> |
| <map:parameter name="path" value="{1}{2}"/> |
| </map:transform> |
| ]]> |
| </source></li> |
| <li><p> |
| For "directory" menu generation, we simply use an |
| <code>XPathTransformer</code> to include only pages in the |
| current page's directory, or below: |
| </p> |
| <source> |
| <![CDATA[ |
| <map:transform type="xpath"> |
| <map:parameter name="include" value="//*[@href='{1}']" /> |
| </map:transform> |
| ]]> |
| </source> |
| <p> |
| Here, the "<code>{1}</code>" is the directory part of the |
| current page. So if our current page is |
| <code>community/howto/index.html</code> then "<code>{1}</code>" |
| will be <code>community/howto/</code> and the transformer will |
| include all nodes in that directory. |
| </p></li> |
| </ul> |
| <p> |
| We now have a <code>site.xml</code> subset relevant to our current |
| page. |
| </p></li> |
| <li>The "<code>href</code>" nodes in this are then made relative to the |
| current page.</li> |
| <li>The XML is then transformed into a legacy "<code>book.xml</code>" |
| format, for compatibility with existing stylesheets, and this XML |
| format is returned (hence the name of the matcher: |
| <code>**book-*.html</code>).</li> |
| </ol> |
| </section> |
| <section id="linkrewriting_impl"> |
| <title>Link rewriting</title> |
| <p> |
| In numerous places in <code>sitemap.xmap</code> you will see the |
| "linkrewriter" transformer in action. For example: |
| </p> |
| <source> |
| <![CDATA[<map:transform type="linkrewriter" src="cocoon:/{1}linkmap-{2}.html"/>]]> |
| </source> |
| <p> |
| This statement is Cocoon's linking system in action. A full description |
| is provided in <link href="site:linking">Menus and Linking</link>. Here |
| we describe the implementation of linking. |
| </p> |
| <section id="input_modules"> |
| <title>Cocoon foundations: Input Modules</title> |
| <p> |
| The implementation of <code>site:</code> linking is heavily based on |
| Cocoon <link href="ext:cocoon/input-modules">Input Modules</link>, a |
| little-known but quite powerful aspect of Cocoon. Input Modules are |
| generic Components which simply allow you to look up a value with a |
| key. The value is generally dynamically generated, or obtained by |
| querying an underlying data source. |
| </p> |
| <p> |
| In particular, Cocoon contains an <code>XMLFileModule</code>, which |
| lets one look up the value of an XML node, by interpreting the key as |
| an XPath expression. Cocoon also has a |
| <code>SimpleMappingMetaModule</code>, which allows the key to be |
| rewritten before it is used to look up a value. |
| </p> |
| <p> |
| The idea for putting these together to rewrite "<code>site:</code>" |
| links was described in <link href="ext:inputmoduletransformer">this |
| thread</link>. The idea is to write a Cocoon Transformer that triggers |
| on encountering <link href="<code>scheme:address</code>">, and |
| interprets the <code>scheme:address</code> internal URI as |
| <code>inputmodule:key</code>. The transformer then uses the named |
| InputModule to look up the key value. The <code>scheme:address</code> |
| URI is then rewritten with the found value. This transformer was |
| implemented as |
| <link href="ext:linkrewritertransformer">LinkRewriterTransformer</link>, |
| currently distributed as a "block" in Cocoon 2.1 |
| (see <link href="ext:cocoon/linkrewritertransformer">API docs</link>). |
| </p> |
| </section> |
| <section id="implement_rewriting"> |
| <title>Implementing "<code>site:</code>" rewriting</title> |
| <p> |
| Using the above components, "<code>site:</code>" URI rewriting is |
| accomplished as follows. |
| </p> |
| <section id="cocoon_xconf"> |
| <title>cocoon.xconf</title> |
| <p> |
| First, we declare all the input modules we will be needing: |
| </p> |
| <source> |
| <![CDATA[ |
| <!-- For the site: scheme --> |
| <component-instance |
| class="org.apache.cocoon.components.modules.input.XMLFileModule" |
| logger="core.modules.xml" name="linkmap"/> |
| |
| <!-- Links to URIs within the site --> |
| <component-instance |
| class="org.apache.cocoon.components.modules.input.SimpleMappingMetaModule" |
| logger="core.modules.mapper" name="site"/> |
| |
| <!-- Links to external URIs, as distinct from 'site' URIs --> |
| <component-instance |
| class="org.apache.cocoon.components.modules.input.SimpleMappingMetaModule" |
| logger="core.modules.mapper" name="ext"/> |
| ]]> |
| </source> |
| <ul> |
| <li><strong>linkmap</strong> will provide access to the contents of |
| &s;; for example, <code>linkmap:/site/about/index/@href</code> |
| would return the value "index.html".</li> |
| <li><strong>site</strong> provides a "mask" over |
| <strong>linkmap</strong> such that <code>site:index</code> expands |
| to <code>linkmap:/site//index/@href</code></li> |
| <li><strong>ext</strong> provides another "mask" over |
| <strong>linkmap</strong>, such that <code>ext:ant</code> would |
| expand to <code>linkmap:/site/external-refs//ant/@href</code></li> |
| </ul> |
| <p> |
| However at the moment, we have only declared the input modules. They |
| will be configured in <code>sitemap.xmap</code> as described in the |
| next section. |
| </p> |
| </section> |
| <section id="sitemap"> |
| <title>sitemap.xmap</title> |
| <p> |
| Now in the sitemap, we define the LinkRewriterTransformer, and |
| insert it into any pipelines which deal with user-editable XML |
| content: |
| </p> |
| <source> |
| <![CDATA[ |
| .... |
| <!-- Rewrites links, e.g. transforming |
| href="site:index" to href="../index.html" |
| --> |
| <map:transformer name="linkrewriter" |
| logger="sitemap.transformer.linkrewriter" |
| src="org.apache.cocoon.transformation.LinkRewriterTransformer"> |
| <link-attrs>href src</link-attrs> |
| <schemes>site ext</schemes> |
| |
| <input-module name="site"> |
| <input-module name="linkmap"> |
| <file src="{src}" reloadable="false" /> |
| </input-module> |
| <prefix>/site//</prefix> |
| <suffix>/@href</suffix> |
| </input-module> |
| <input-module name="ext"> |
| <input-module name="linkmap"> |
| <file src="{src}" reloadable="false" /> |
| </input-module> |
| <prefix>/site/external-refs//</prefix> |
| <suffix>/@href</suffix> |
| </input-module> |
| </map:transformer> |
| .... |
| .... |
| <map:match pattern="**body-*.html"> |
| <map:generate src="cocoon:/{1}{2}.xml"/> |
| <map:transform type="idgen"/> |
| <map:transform type="xinclude"/> |
| <map:transform type="linkrewriter" src="cocoon:/{1}linkmap-{2}.html"/> |
| ... |
| </map:match>]]> |
| </source> |
| <p> |
| As you can see, our three input modules are configured as part of |
| the LinkRewriterTransformer's configuration. |
| </p> |
| <ul> |
| <li><p> |
| Most deeply nested, we have: |
| </p> |
| <source> |
| <![CDATA[ |
| <input-module name="linkmap"> |
| <file src="{src}" reloadable="false" /> |
| </input-module>]]> |
| </source> |
| <p> |
| The "<code>{src}</code>" text is expanded to the value of the |
| "<code>src</code>" attribute in the "<code>linkrewriter</code>" |
| instance, namely "<code>cocoon:/{1}linkmap-{2}.html</code>" Thus |
| the <code>linkmap</code> module reads dynamically generated XML |
| specific to the current request. |
| </p></li> |
| <li><p> |
| One level out, we configure the "<code>site</code>" and |
| "<code>ext</code>" input modules, to map onto our dynamically |
| configured "<code>linkmap</code>" module. |
| </p></li> |
| <li><p> |
| Then at the outermost level, we configure the |
| "<code>linkrewriter</code>" transformer. First we tell it which |
| attributes to consider rewriting: |
| </p> |
| <source> |
| <![CDATA[ |
| <link-attrs>href src</link-attrs> |
| <schemes>site ext</schemes>]]> |
| </source> |
| <p> |
| So, "<code>href</code>" and "<code>src</code>" attributes |
| starting with "<code>site:</code>" or "<code>ext:</code>" are |
| rewritten. |
| </p> |
| <p> |
| By nesting the "<code>site</code>" and "<code>ext</code>" input |
| modules in the "<code>linkrewriter</code>" configuration, we |
| tell "<code>linkrewriter</code>" to use these two input modules |
| when rewriting links. |
| </p></li> |
| </ul> |
| <p> |
| The end result is that, for example, the source XML for the |
| <code>community/body-index.html</code> page has its links rewritten |
| by an XMLFileModule reading XML from |
| <code>cocoon:/community/linkmap-index.html</code> |
| </p> |
| </section> |
| <section id="dynamic_linkmap"> |
| <title>Dynamically generating a linkmap</title> |
| <p> |
| Why do we need this "linkmap" pipeline generating dynamic XML from |
| &s;, instead of just using &s; directly? The reasons are described |
| in <link href="ext:linkmaps">the linkmap RT</link>: we need to |
| concatenate @hrefs and add dot-dots to the paths, depending on which |
| directory the linkee is in. This is done with the following |
| pipelines in <code>linkmap.xmap</code> ... |
| </p> |
| <source> |
| <![CDATA[ |
| <!-- site.xml with @href's appended to be context-relative. --> |
| <map:match pattern="abs-linkmap"> |
| <map:generate src="content/xdocs/site.xml" /> |
| <map:transform src="resources/stylesheets/absolutize-linkmap.xsl" /> |
| <map:serialize type="xml" /> |
| </map:match> |
| |
| <!-- Linkmap for regular pages --> |
| <map:match pattern="**linkmap-*"> |
| <map:generate src="cocoon://abs-linkmap" /> |
| <map:transform src="resources/stylesheets/relativize-linkmap.xsl"> |
| <map:parameter name="path" value="{1}{2}" /> |
| <map:parameter name="site-root" value="{conf:project-url}" /> |
| </map:transform> |
| <map:serialize type="xml" /> |
| </map:match> |
| ]]> |
| </source> |
| <p> |
| You can try these URIs out directly on a live Forrest to see what is |
| going on (for example, Forrest's own |
| <link href="../abs-linkmap">abs-linkmap</link>). |
| </p> |
| </section> |
| </section> |
| </section> |
| </body> |
| </document> |