Titania Delivery Developer's Guide

Author:

Part I: Portal Theme Developer's Guide

Chapter 1: Portal Theme Basics

Portal Themes are a powerful and flexible tool to configure the look and feel of a Portal. Titania Delivery provides carte blanche access to modify every aspect of a Portal Theme, from changing a color scheme to starting from a blank page.

Portal Theme development is best done by Web developers. Specifically, the skills you should be at least familiar with include HTML, CSS, Javascript, and in some cases, XSLT.

1.1: What is a Portal Theme?

A Portal Theme is a set of HTML templates, CSS and JavaScript files, and XSLT that allow for complete control over the presentation of content within a Portal.

Titania Delivery comes with a default portal theme, meant to be used either as an out-of-the-box solution to delivering and presenting your content or as a starting point for customizing a different look and feel. Every aspect of a Portal Theme is customizable. A new portal theme could also be created from scratch by deleting every file in a default theme, adding brand new versions of the required pages, and extending the theme by adding new custom pages.

If you are familiar with the Model-View-Controller pattern, you'll recognize that Portal Themes represent the 'View' aspect, with Titania Delivery providing the models and controllers.

1.2: Portal Theme Directory Structure

A Portal Theme contains a collection of files that, together, make up the look, feel, and interactivity of any Portals that utilize that theme. These files are organized into a directory structure that is analagous to a computer's file system.

All Portal Themes must have the following three top-level folders:

  • /pages - The pages directory contains the HTML templates used to draw the application pages required for all Portals to function. Titania Delivery templates are written using the Freemarker template language, which will be discussed later.
  • /static - The static directory contains website assets, such as images and CSS stylesheets, that are referenced from your page templates.
  • /xsl - The xsl directory contains the XSLT transforms that can convert XML content into HTML for display in the portal.

Offline packagers must be defined in the following directory:

  • /packagers - Specific packagers will be defined in subdirectories named after the packager (e.g. "webhelp"). Other subdirectories may be included to contain common templates and resources for all packagers.

In addition, Portal Themes should include a config.xml file at the root level. This file will contain parameters that allow for easy customization of a portal's appearance without actually modifying the theme.

Chapter 2: Page Templates

The /pages directory contains the templates that generate the HTML of a Portal.

2.1: What Is Freemarker?

Titania Delivery utilizes a template engine called Freemarker to render a Portal's pages.

Freemarker is a text templating engine. It provides looping and branching constructs as well a library of powerful functions (built-ins), directives, and expressions that allow for creating reusable templates which generate HTML or any other textual content. Freemarker pages are defined by templates and data-models. Data-models are the data provided to templates. Titania Delivery provides each template with a data-model, and the template designer can use that model to render and display appropriate information. Freemarker files have a .ftl file extension.

2.2: Common Template Variables

Titania Delivery populates each Portal Theme page with variables containing several useful properties.

Within a Freemarker template, these properties can be accessed with ${propertyName}. Many properties have fields (and sometimes those fields have fields themselves). These can be accessed with dot syntax. For example, ${propertyName.fieldName}.

Common Properties

The following properties are present on every page:

portal
A complex object containing the properties of the portal itself. See PortalIdentity.
hasSecurity
A boolean property indicating whether the portal is configured for security.
isAuthenticated
A boolean property indicating whether the current user has logged in.
user
A complex object containing the properties of the authenticated user. See PortalUser.
currentUrl
The absolute URL of the current page, excluding any fragment identifier.
request
The HttpServletRequest object for the current request.
RequestParameters
A hash of the URL parameters passed to the request.
userData
A UserDataStorage object that can be used to store and retrieve persistent data for the current user or session.
siteData
A SiteDataStorage object that can be used to store and retrieve global data shared by all users.
buildString
A string containing Titania Delivery versioning information.
online and offline
Boolean variables indicating whether the script is executing in an 'online' (normal) mode or as part of an offline packager. These variables can be used to easily conditionalize modules that contain logic for both online and offline page generation. See Offline Packagers for details.
t
The namespace for the layout template directives.
Commenting-Related Properties

The following properties are also present on every page, but pertain specifically to commenting-related features.

cmScriptLocation
A string specifying the URL of the Titania Annotator Javascript library.
moderators
An array of the ids of the users configured as comment moderators.

2.3: Portal Theme Pages

The following pages are required for a basic Portal to function properly. All paths are relative to the Portal Theme root.

Most of the following pages are located in the root of the /pages directory, with the exception of the pages contained in the /errors directory. There are no requirements regarding the content of the following files; any one of them could be left blank without causing problems as long as they are not linked to. When rendered, these pages' data-models are supplemented with data structures specifically designed for the purpose connoted by the file's name.

Portal Theme Page URLs
Template File URL Pattern(s) Description
/pages/portal-home.ftl /portalPath/ Portal landing page.
/pages/searchResults.ftl /portalPath/search Search results page.
/pages/viewer.ftl /portalPath/viewer/*, /portalPath/content/* Viewer page for XML content in the context of other documents. For example, a DITA topic in the context of a DITA map, or a chunked section of a non-DITA XML document.
/pages/mapViewer.ftl (deprecated) /portalPath/viewer/*, /portalPath/content/* Viewer page for top-level embeddable content, such as XML, Markdown, and HTML fragments (no root <html> element.
Note: This page is deprecated, and should simply <#include> viwer.ftl. This template may be removed in a future release.
/pages/login.ftl /portalPath/login Login page for LDAP-based authentication.
/pages/assemblyViewer.ftl /portalPath/assembly/assemblyKey/ ToC view page of an assembly.
/pages/assemblyTopicViewer.ftl /portalPath/assembly/assemblyKey/topicId Viewer page for an entry in an assembly.
/pages/assemblyPreview.ftl N/A Template used to render the preview view of a topic in the assembly editor.
/pages/custom/*.ftl /portalPath/pages/* Custom pages.
/pages/error.ftl N/A Page used to present HTTP errors (404, 500, etc.).

2.3.1: Portal Landing Page

This page is defined by the /pages/portal-home.ftl template in the portal theme. The base URL of any portal is the URL path /portalUrlPath/. When navigating to a Portal URL, this page will be rendered.
Page Variables

This page has access to all of the common variables.

2.3.2: Search Results Page

This page is defined by the /pages/searchResults.ftl template in the portal theme. The search results page is meant to show the results of a query, and is bound to the URL path /portalUrlPath/search.

This page is populated with search results from the following query parameters:

escape
Either true or false. Specifies whether special query characters in the term should be escaped when executing the query. The default is false. See Search Syntax for details of reserved search characters.
facet
A metadata field to use as a search facet. If not specified, the search facets configured for the portal are used. See the Titania Delivery Administrative Guide for details on configuring a portal's default search facets. This parameter may be specified multiple times to specify multiple metadata fields to use as facets.
filter
A secondary search query to use to filter the available documents that should be included in the results. The same results could be achieved by adding additional criteria to the term, but there are performance benefits to using a separate filter parameter, especially when the same filter may be reused for multiple searches.
groupBy
The property for grouping results. Default is itemKey.
groupSize
The maximum number of contextualized DITA topics to include in result groupings. The default is 5.
highlightSize
The maximum number of characters to include in text snippets containing matching text. The default is 300.
maxFacetValues
The maximum number of facet values to include in the search results. The default is 10.
n
The number of search results to return per page. If not specified, the default is 10.
page
The zero-based index of the page to view. The default is 0.
portalContent
Portals can generate files using the siteData interface. This parameter specifies if and how content in that project should be included in the search. The valid values are:
exclude
Portal-generated content is excluded. This is the default.
include
Portal-generated content is included, along with other content available through the portal.
only
Only portal-generated content will be included in the results.
sortBy
The property name or names by which to sort the results. (All metadata values are indexed as keywords, and must be named with '_md' suffix: for example, sortBy=title_md.) Multiple names may be specified, separated by spaces or commas (URL-encoded as necessary). Multiple sortBy parameters may be included, but since the order of parameter processing is not guaranteed to be consistent, it is advisable to use only one sortBy parameter if corresponding sortDirections are specified.
Note: Sort keys given in the sortBy parameter will be used before the default "relevance" (as determined by the search engine) is applied. So the top items in the result list might not be the "most relevant" based on the search term.
sortDirection
Specify the sort directions corresponding to each sortBy field. If the list of sort directions is empty or shorter than the list of sortBy fields, the default order is ASC. Values may be either ASC or DESC (case-insensitive) to specify the sort direction as ascending or descending. Multiple sortDirection parameters may be included, but since the order of parameter processing is not guaranteed to be consistent, it is advisable to use only one sortDirection parameter (with multiple values, if necessary).
startAfter
For programmatic use only, this should be an object value from the SearchResultsPage startNextAfter property.
term
The search query.

Additionally, any number of metadata filters can be supplied, each in the format: f.[metadataName]_md. All parameters are optional but if a term parameter is not supplied the search results will be empty.

For example, assume the search page is requested with the following URL query string: ?term=Titania&n=5&page=1&f.format_md=dita. The page would be populated with data containing up to five search results of the second page (remember that the page param is zero-indexed) of results that match the search term Titania that have format metadata with a value of dita.

  • Search results for the word "Titania"
  • Up to five results
  • Starting with the sixth result, because we requested the second page (because the page parameter is zero-indexed).
Data Model

In addition to the common properties, this page has access to the following:

filterParams
A string containing the URL parameters for the metadata filters being applied to the search. This is a convenience property that could be constructed manually from the filters data structure. This property can be used to easily construct URLs in paginated search result lists.
filters

A hash. The keys of the hash are the metadata names being used to filter results. Each keyed entry is an array of the values for that metadata name specified by the user for filtering.

For example, if a user filters on a metadata named "foo" using values "abc" and "xyz", the ${filters} hash would look like this:

{
  foo: ['abc', 'xyz']
}
result
A SearchResultsPage containing the set of results to display, grouped by context, represented by SearchResultGroup objects.
sortBy
Sequence or null. If sortBy names were given, this will be a sequence of the given sortBy field names.
sortDirection
Sequence or null. If sortDirection parameter was given, this will be the sequence of values specified.
term
The search term entered by the user.

2.3.3: Embeddable Content Viewer Pages

This page is defined by the /pages/viewer.ftl and /pages/mapViewer.ftl (deprecated) templates in the portal theme. Content that can be embedded in a portal's overall look, such as XML content, Markdown, and some HTML files, are rendered through this template. Other file types are served directly, without a template.

If the document is a contextualized DITA topic - that is, a topic that is part of a map - it will be rendered using viewer.ftl. All other file types will be rendered using mapViewer.ftl.

Important: mapViewer.ftl is deprecated. It is strongly recommended that all viewer page logic be placed in viewer.ftl, and that mapViewer.ftl simply be an inclusion of viewer.ftl. The Freemarker code can determine whether the document is a contextualized topic using the hasContext variable. The mapViewer.ftl template may be removed in a future release.
Note: When providing links to the viewer page, it is strongly advised to use the <@td.searchResultUrl> tag, specifying a object for the tag's @searchResult attribute.

Tip: In addition to the URL format returned by the viewerUrl tag, you can use metadata to construct the web URL to the viewer pages using query parameters. By accessing /portalUrlPath/viewer?<md_name>=<md_value>. You can specify as many metadata parameters as you want. If more than one file matches the metadata query, the system will pick one at random.

So, for example, if you wanted to access a DITA topic with <resourceId id="12345"/>, you could use /myportal/viewer?resourceid=12345.

Variables

In addition to the common properties, this page has access to the following:

itemUrl
The ContentLocator for the file being displayed. In the case of a DITA topic under a map, this will be the locator for the topic. In the case of a synthetic topic generated from a heading-only node in a DITA map, this will be the locator of the map. In the case of a chunked division in a non-DITA document, this will be the locator for the top-level document.
contextRefId
Only present when viewing a piece of content in the context of another, such as a DITA topic or XML division chunk. In the case of a DITA topic within a map, this will be the ID of the topicref element referencing the topic. In the case of a chunked division, this will be the ID of the element defining the chunk.
virtual
A boolean attribute describing whether the content being viewed is a virtual chunk, meaning that the content was generated as part of content processing, and was not based on a file in a project. This will be false for DITA maps and topics, and for top-level non-DITA documents. It will be true for chunked divisions in non-DITA documents and for topics generated from title-only <topicref> elements in DITA maps.
title
The title of the document being shown. In the case of contextualized content such as a DITA topic in a map or a chunked division, this will be the title of the topic or chunk. In the case of monolithic XML documents or root-level DITA maps, this will be the title of the document itself. In the case of chunked divisions, this is the only way to get the title of the chunk, as all other document properties listed here will be associated with the top-level document.
itemData
An ItemDataAccess data structure with access to the properties of the document identified by itemUrl.
properties
A hash containing the file's properties. A convenience synonym for itemData.propertyMap .
metadata
A hash of all of the properties for the file being viewed, including project and context metadata, if any.
Note: This is not a synonym for itemData.properties, which only contains only the metadata for the item itself.
contexts
An array of ContentRelationship data structures that represent the various contexts in which this file is present. Only those contexts visible to the portal (as determined by metadata rules) will be included.
hasContext
A boolean value denoting whether the object being viewed is contextualized under a DITA map.
contextHarpUrl
The ContentLocator of the context (usually a DITA map) for the content being viewed. Optional, and will never be present in mapViewer.ftl.
contextMetadata
The hash of properties for the context (generally a DITA map) of the file being viewed. Optional, and will never be present in mapViewer.ftl.
debug
The boolean value represented by the optional debug URL parameter. Can be used to trigger special debugging behavior. Always present, defaults to false.
ignoreCache
The boolean value represented by the optional ignoreCache URL parameter, and can be used in conjunction with the ignoreCache attribute on the <@td.content> tag to bypass server-side caching mechanisms. Always present, defaults to false.
UUID
A generated UUID that can be used to identify individual page views for analytics recording purposes.

Note: In most cases, the document being viewed is a file in a project. However, in some cases, these pages will be used to view content for which there is no corresponding document in a project. These include

  • Synthetic DITA topics defined by a title-only <topicref> element.
  • A chunked division from a non-DITA document.

In such cases, the metadata properties will be describing the top-level document/DITA map instead of the chunk being viewed. The one exception to this is title, which will always be the title of the chunk being viewed, regardless of whether or not it is virtual.

2.3.4: Login Page

This page is defined by the /pages/login.ftl template in the portal theme. This page is used when a portal uses an LDAP authentication scheme, to gather the username and password for the user.

This page is only required for portals secured using LDAP. If a user is not logged in or their session has recently expired and they attempt to visit any page within the Portal, they will be forwarded to this page if the Portal does not support anonymous browsing. Attempting to access the login page in a Portal without security will redirect to portal-home.ftl. In order to log in, a client must send a successful POST containing valid username and password parameters to the URL below. If their information matches the credentials stored in the server connected to by the LDAP configuration set up on the admin application then they will be forwarded to the portal. If the request was unsuccessful for any reason, the user will be redirected to login.ftl and there will be an error message added to the model.

The most basic login.ftl should contain the following contents:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="utf-8">
</head>

<body>

 <#if errorMessage??>
  ${errorMessage?html}
 </#if>

 <form action="login" method="post">
  <label for="username">User Name</label>
  <input type="text" name="username" placeholder="User Name">
  <label for="password">Password</label>
  <input type="password" name="password" placeholder="Password">
  <button type="submit">Submit</button>
 </form>

</body>

</html>

Page Variables

In addition to the common variables, this page has access to the following:

errorMessage
A string with the error message produced by the previous login attempt.

2.3.5: Assembly Viewer Pages

These pages is defined by the /pages/assemblyViewer.ftl and /pages/assemblyTopicViewer.ftl templates in the portal theme and are used to display assemblies and their contents.

These pages can only be accessed in Portals that have security and that have the Assembly feature.

Note: When providing links to an assembly page, it is strongly advised to use the <@td.assemblyViewerUrl> tag as the value to the link's href attribute.
Page Variables

In addition to the common variables, these pages have access to the following:

assembly
The Assembly object.
assemblyTopicRef
The ID for the document in the assembly being viewed. Applies only for assemblyTopicViewer.ftl.
ignoreCache
The boolean value represented by the optional ignoreCache URL parameter, and can be used in conjunction with the ignoreCache attribute on the <@td.content> tag to bypass server-side caching mechanisms. Always present, defaults to false.
UUID
A generated UUID that can be used to identify individual page views for analytics recording purposes.

2.3.6: Assembly Preview Template

This page is defined by the /pages/assemblyPreview.ftl template in the portal theme. It is used to display topics in the Assembly Editor.

It is not intended to be used anywhere else. This page can only be accessed in a Portal that has security and that has the Assembly feature. To test it, log in to a secured portal and create a new assembly. Do a search for any content and click it to view a preview. The window to the right shows the result of assemblyPreview.ftl.

2.3.7: Error Pages

When the Portal application serves up an HTTP error page - such as when it sends an HTTP 404 response when a piece of content does not exist - the application will dynamically determine which template to use for the error.

When an HTTP response in the 400 or 500 range is sent, the application will use /pages/errors/{code}.ftl, if it exists, and /pages/error.ftl if it does not. For example, when sending a 404 response, /pages/errors/404.ftl will be used if it exists, and the fallback /pages/error.ftl. (The default portal theme's 404 and 500 error pages simply <#include ../error.ftl/>.)

Page Variables

Unlike most pages, error pages do not include the common variables. Error pages are limited to the following data:

portal
A complex object containing the properties of the portal itself. See PortalIdentity.
request
The HttpServletRequest object for the current request.
errorCode
An integer with the HTTP error code.
stackTrace
If applicable, the Java exception stack trace that caused the error page.

2.3.8: Custom Pages

The /pages/custom folder contains full-page templates that can be used to extend the functionality of a Portal by adding new pages.

Custom pages can be linked to via <a href=<@harp.pageUrl page="path/to/custom/page.ftl" />>Link Text</a>. The path must be relative to the /pages/custom/ directory.

Page Variables

In addition to the common variables, all custom pages have access to the following:

multiValueParams
A hash of the URL request parameters, with all values for each parameter in a string array.
params
A hash of the URL request parameters, holding a single value for each parameter. (Do not use for multi-value parameters.)
requestBody
An HttpEntityContent object representing the HTTP request body (if any).
UUID
A generated UUID that can be used to identify individual page views for analytics recording purposes.
Specifying Content Type via File Extension

By default, the Content-Type header for custom pages is text/html. However, if the base name of the file has an extension with a known file type, the system will automatically select the appropriate value for the Content-Type header. For example, the custom page template /pages/custom/example.css.ftl would be served with a Content-Type header of text/css.

Here are some common file extensions and the resulting content type.

Note: If a file extension is not recognized, the Content-Type header will be text/html.
Extension Content Type
*.css.ftl text/css
*.js.ftl application/javascript
*.xml.ftl application/xml
*.dita.ftl and *.ditamap.ftl application/dita+xml
*.txt.ftl text/plain
*.csv.ftl text/csv
HTTP Headers for Custom Pages

By default, Titania Delivery serves custom pages with a minimum of HTTP headers, and a Content-Type header determined as described above. You can customize the HTTP headers sent for a given custom page by specifying those headers in metadata with keys beginning with _td.header.. For example, specifying _td.header.Content-Type=application/xml on a page's metadata will cause it to be served as an XML document.

Example Usage

A new custom page can be created with the following steps:

  1. Create a file named test.ftl somewhere on your computer. Paste in the following code:

    <!DOCTYPE html>
    <html lang="en-US">
     <head>
      <meta charset="utf-8" />
      <title>Hello, Titania!</title>
     </head>
     <body>
      <h1>Hello, Titania!</h1>
      <p>Welcome to the ${portal.name?html} portal</p>
     </body>
    </html>
  2. In your Portal Theme, select the pages/custom/ directory
  3. Click the Upload tab then click "Select a file..." to upload your file or drag your file to the location specified. You should now be able to see, and edit, test.ftl from within the browser.
  4. In portal-home.ftl, add the following in the first line after <div class="container">

    <a href=<@harp.pageUrl page="test.ftl" />>Test Page</a>
  5. Navigate to the Portal homepage and click the link to be taken to the new custom page.

One common use of custom pages is to render non-DITA content such as Microsoft Office, PDF, or graphic files. Using the above steps, create a new custom template called pngViewer.ftl. Paste in the following code:

<#assign td=JspTaglibs['http://www.titaniasoftware.com/harp/taglib']>
<!DOCTYPE html>
<html lang="en-US">
<head>
  <meta charset="utf-8" />
  <title>${portal.displayName?html}</title>
</head>
<body>
  
  <#if params['projectKey']?? && params['itemKey']??>
    <@td.fileProperties 
          var="itemData" 
          projectKey="${params['projectKey']}" 
          itemKey="${params['itemKey']}"/>
  </#if>
  
  <#if itemData??>
    <h1>${itemData.properties.specifiedDetails.title?html}</h1>
    <img src="<@td.viewerUrl item=itemData.item/>"/>
  <#else>
    <h1>No such file.</h1>
    <a href="javascript:history.back()">Go back.</a>
  </#if>
  
</body>
</html>

Then, set the contents of searchResults.ftl to:

<#assign td=JspTaglibs['http://www.titaniasoftware.com/harp/taglib']>
<!DOCTYPE html>
<html lang="en-US">
<head>
  <meta charset="utf-8" />
  <title>${portal.displayName?html}</title>
</head>
<body>
  
  <h1>
    Search Results:
    ${term?html}
  </h1>
  
  <#if result.content?size == 0>
    <h2>No Results</h2>
  
  <#else>
    <ul>
      <#list result.content as group>
        
        <#assign filename>
          ${group.firstResult.filename?lower_case?trim}
        </#assign>
        
        <#assign extension>
          ${filename?substring(filename?last_index_of('.') + 1)}
        </#assign>
        
        <#assign url>
          <#if extension?trim == "png">
            <@td.pageUrl page="graphic.ftl">
              <@td.urlParam name="projectKey" value="${group.firstResult.projectKey}" />
              <@td.urlParam name="itemKey" value="${group.firstResult.itemKey}" />
            </@td.pageUrl>
          <#else>
            <@td.viewerUrl searchResult=group.firstResult />
          </#if>
        </#assign>
        
        <li><a href="${url}">${group.firstResult.title?html}</a></li>
        
      </#list>
    </ul>
  </#if>
  
  </body>
</html>

Ensure that there is at least one file available to the Portal that has a .png extension. Then, navigate to the URL path /portalUrlPath/search?term=<filename> where filename is the name of one of the png files in your project. That file should come up as a search result and clicking on the link should redirect to the page generated by pngViewer.ftl. Any files returned by the search that do not have a .png extension will link to the regular viewer pages.

2.4: Storing Data

The userData and siteData variables provide a mechanism for storing and retrieving persistent data on the server.

The userData object manages data associated with the currently authenticated portal user. If no user is currently logged in, stored data is associated with, and expires with, the current user session. The siteData object manages data associated with the portal itself, and is not associated with any particular user.

These objects are available on every page, and provide simple methods for storing and retrieving virtually any data structure in a persistent way. For example, you can store a value using put(key, value) on one page, and on another page, retrieve the value with get(key).

The siteData object provides additional methods for storing and searching arbitrary data structures in user-defined search indexes. It also provides methods to add, retrieve, and delete files in a project that is associated with the portal.

For a full description of how userData and siteData work, see UserDataStorage and SiteDataStorage. This functionality can also be leveraged using client-side Javascript; see UserData and SiteData in Javascript.

Here is a more complex example:

<!-- The view will be of the 'current' state; next view will show new state. -->

<#assign counter = userData.get('counter')!0/>
${userData.put('counter', counter + 1)}
<p>Counter value: ${counter}</p>

<!-- Store the 5 most recent counters -->
<#assign counterList = userData.getList('counterList')![]/>
<p>Counter list: ${counterList?join(', ')?html}</p>
${userData.push('counterList', counter, 5)}

<!-- You can also store complex objects and arrays -->
${userData.put('object', {"a": 5, "b": "foo"})}
${userData.put('array', [1, 2, 3, 4, 5])}
${userData.push('complexData', {"I": "am", "a": {"nested": "structure"}});

The built-in theme's Recently Viewed Documents, Recent Searches, and Favorites features are implemented using userData.

2.5: Modifiable Lists and Maps

Native Freemarker hashes and arrays are not designed to be modified. Titania provides a number of tools for working with modifiable lists and maps instead.

There are cases in a portal theme page or offline packager where it can be beneficial to build up temporary caches of information to be reused or aggregated elsewhere in the page/package. For instance, a package script may want to cache the URLs of various pages as they are built. Unfortunately, Freemarker hashes and sequences are ill-suited to this sort of task.

Sequences and hashes in Freemarker can be created programmatically.

<#assign arr = []/>
<#assign hash = {}/>

They can also be updated.

<#assign arr = arr + [newElement]/>
<#assign hash = hash + {key: value}/>

However, the Freemarker documentation discourages open-ended concatenation, stating:

Note that hash concatenation is not to be used for many repeated concatenations, like for adding items to a hash inside a loop. While adding together hashes is fast and is constant time (independent of the size of the hashes added), the resulting hash is a bit slower to read than the hashes added together. Thus after tens or hundreds of additions the result can be impractically slow to read.

and

Note that sequence concatenation is not to be used for many repeated concatenations, like for appending items to a sequence inside a loop. It's just for things like <#list users + admins as person>. Although concatenating sequences is fast and is constant time (it's speed is independently of the size of the concatenated sequences), the resulting sequence will be always a little bit slower to read than the original two sequences were. Thus, after tens or hundreds of repeated concatenations the result can be impractically slow to reader (sic.).

In fact, in some unusual circumstances with huge collections, exceptions can be thrown when attempting to read from a concatenated hash or sequence.

Titania Delivery provides a set of Freemarker directives or functions that enable the creation and modification of mutable collections that do not suffer from the limitations of sequences and hashes.

newSet and newList

The newSet and newList directives and functions can be used to instantiate mutable lists of values. The only difference between the two collection types are:

  • Values in sets are unique; attempts to add the same value multiple times will not modify the list.
  • Values in lists can be removed by their index via the removeIndex directive, which is not available on sets.

Instantiation

Instances of sets and lists are instantiated using the newSet or newList objects. These objects can be invoked as either directives or functions.

<#-- As a directive -->
<@newSet var="mySet"/>
<@newList var="myList"/>

<#-- As a function -->
<#assign mySet = newSet()/>
<#assign myList = newList()/>

Once instantiated, the variable can be used as a prefix for the various directives and properties of the collection.

Parameters
var or assign
The global variable name to assign the new object. This parameter cannot be passed to the functional form.
<@newSet var="mySet"/>
<@newSet assign="mySet"/>
<#-- Identical to: -->
<#assign mySet = newSet()/>
local
The local variable name to assign the new object. The current processing context must be within a <#function> or <#macro>. This parameter cannot be passed to the functional form.
<@newList local="myList"/>
<#-- Identical to: -->
<#local myList = newList()/>
from
Optional. A Freemarker sequence that will be the starting values for the object. Passed as the first argument to the functional form.
<@newSet var="mySet" from=['a', 'b', 'c']/>
<#assign mySet = newSet(['a', 'b', 'c'])/>
Directives

Sets provide sub-directives that can be used to modify the contents of the set.

add
Used to add values to the set using the value parameter.
<@mySet.add value="someValue"/>
clear
Removes all values from the collection.
<@myList.clear/>
removeValue
Removes a value from the collection using the value parameter.
<@mySet.removeValue value="someValue"/>
removeIndex (lists only)
Removes a value from a list using the index parameter.
Important: This directive is not present on sets, and attempts to invoke it on sets will cause errors.
<@myList.removeIndex index=4/>
Properties

Sets and lists have the following properties.

size
The number of elements in the collection.
${mySet.size}
asSequence
The collection as a native Freemarker sequence. This is the primary mechanism by which the data in the collection should be read.
Note: There is no meaningful processing or memory overhead when using this property. It is a reference to the underlying storage for the object, it is not a copy.
${myList.asSequence[0]}
${mySet.asSequence?join('<br>')}
<#if myList.asSequence?seq_contains('someValue')>
  <#-- Conditional code here -->
</#if>
json
The set as a JSON string.
<pre>${mySet.json?html}</pre>

newMap

The newMap directive and function can be used to instantiate mutable hash maps. Keys in mutable hashes must be strings.

Instantiation
<#-- As a directive -->
<@newMap var="myMap"/>

<#-- As a function -->
<#assign myMap = newMap()/>
Parameters
var or assign
The global variable name to assign the new object. This parameter cannot be passed to the functional form.
<@newMap var="myMap"/>
<@newMap assign="myMap"/>
local
The local variable name to assign the new object. The current processing context must be within a <#function> or <#macro>. This parameter cannot be passed to the functional form.
<@newMap local="myMap"/>
from
Optional. A Freemarker hash that will be the starting entries for the map. Passed as the first argument to the functional form.
<@newMap var="myMap" from={"index": 5, "title": "Some Title"}/>
<#assign myMap = newMap({"index": 5, "title": "Some Title"})/>
Directives

Maps provide sub-directives that can be used to modify the contents of the map.

put
Used to add entries to the map using the key and value attributes. The key must be a string. The value can be anything.
<@myMap.put key="someKey" value="someValue"/>
clear
Removes all entries from the map.
<@myMap.clear/>
remove
Removes an entry from the set using the key parameter.
<@myMap.remove key="someKey"/>
Properties

Maps have the following properties.

size
The number of elements in the map.
${myMap.size}
keys
The keys in the map as a Freemarker sequence.
<#if myMap.keys?seq_contains('foo')>
  <#-- Conditional code here -->
</#if>
asHash
The map as a native Freemarker hash. This is the primary mechanism by which the data in the map should be read.
Note: There is no significant processing or memory overhead when using this property. It is a reference to the underlying storage for the object, it is not a copy.
${myMap.asHash['someKey']!'No entry for someKey'}
<#if myMap.asHash?keys?seq_contains('someKey')>
  <#-- Conditional code here -->
</#if>
json
The map as a JSON string.
<pre>${myMap.json?html}</pre>

2.6: Portal Theme JavaScript Utility Library

The utility library contains helpful tools for enhancing portal functionality using Javascript in your portal theme pages.

harp-sdk.js adds the HARPPortal object to the global scope. This object has the following interface:

interface HARPPortal {

    // Returns the Titania Delivery Version             
    String getVersion();
    
    // Returns the Titania Delivery build identifier    
    String getBuildId();
    
    // Returns the url to the given path
    String getPortalUrl(String path);
    
    /**
     * Returns the url to the Titania Delivery endpoint 
     * that handles data transfer of assemblies, 
     * comments, and helpful votes. Valid values are 
     * 'assemblies', 'comments', and 'feedback'.
     */
    String getPortalRpcUrl(String path);

    // AJAX interface to userData.
    UserDataStorage userData;

    // AJAX interface to siteData
    SiteDataStorage siteData;
    
    /** 
     * Deletes the assembly indicated by assemblyKey
     * redirectTo is an optional parameter that takes a 
     * path relative to the Portal root that the user will
     * be redirected to. 
     */
    void deleteAssembly(assemblyKey, redirectTo);
}

These functions can be called anywhere JavaScript is legal on a page. For instance:

<script>alert(HARPPortal.getPortalUrl("search"));</script>

Add this library to any page by putting the following code snippet into the <head> of the page:

<#assign c=JspTaglibs['http://java.sun.com/jsp/jstl/core']/>
<script type="text/javascript" 
        src="<@c.url value="/resources/scripts/harp-sdk.js"/>"></script>
Note: HARP is an internal legacy name for Titania Delivery. As such, some of the APIs referenced in this documentation still use the name HARP in place of Titania Delivery.
UserData and SiteData in Javascript

The HARPPortal object has userData and siteData properties that can be used to manage persistent data without reloading the whole page. The interfaces to these objects is the same as the DataStorage and SiteData Freemarker objects, with the exception that all methods return jQuery Promises instead of values.

HARPPortal.userData.put('foo', 'bar').then(function() {
    console.log('Stored foo');
    return HARPPortal.userData.get('foo');

  }).then(function(s) {
    console.log('Got foo: ' + s);
    return HARPPortal.userData.delete('foo');

  }).then(function() {
    console.log('Deleted foo');
    return HARPPortal.userData.push('testList', 1);

  }).then(function() {
    console.log('Pushed 1');
    return HARPPortal.userData.push('testList', {foo: 'bar'});

  }).then(function() {
    console.log('Pushed object.');
    return HARPPortal.userData.getList('testList');

  }).then(function(arr) {
    console.log('Got test list.');
    console.log(arr);
    return HARPPortal.userData.delete('testList');

  }).then(function() {
    console.log('testList deleted');

  }).fail(function(e) {
    alert('Failed');
    console.log(e);
  });

2.7: Portal Features

Portals have a number of features available to them. These features can be enabled and disabled in the admin application. Within a Portal Theme, the presence or absence of these features can be checked for, and the corresponding functionality exposed or hidden.
Important: The Portal Features functionality has been superseded by the Portal Parameters framework, but remains for backwards-compatibility.

The following features are available to Portals:

  • Anonymous Browsing (anonymousAccess)
  • Document-Level Comments (docLevelComments)
  • Element-Level Comments (elLevelComments)
  • Custom Assemblies (customAssemblies)
  • "Was This Helpful" Widgets (helpfulVote)

The status of these features can be checked for using the PortalIdentity object's enabledFeatures array or hasFeature method. Before exposing functionality to a Portal, the Portal Theme should always first check to ensure that it is enabled. For instance:

<#if portal.hasFeature('helpfulVote')>
  <#-- markup exposing functionality here -->
</#if>

2.8: Translation Support

Managing site translation in Titania Delivery involves using FreeMarker definition files containing the translations, and a mechanism for finding the strings in the current language.

The Titania Default portal theme contains a pages/i18n folder. This folder contains utility templates to be imported into other pages for purposes of translating the User Interface into different languages. Specifically, the pages/i18n/strings.ftl template exposes a getString() function that takes an English string as a parameter, and returns a translated string in the user's current language if one is available. In addition, the User Interface exposes the ability for the user to change the current interface language from among the available languages.

To use a translated string in a page, simply load it through the getString() function, e.g.

${getString('English String')?html}
Adding Strings

The language files in the pages/i18n folder are named for specific locale codes. These files contain simple Freemarker hashes of the English string mapped to the translation of that string.

<#assign strings = {
  "Learn about Titania": "En savoir plus sur Titania",
  "Learn about Titania Delivery": "En savoir plus sur Titania Delivery",
  "Powered by Titania Delivery": "Optimisé par Titania Delivery",
  "Links": "Liens"
}/> <#-- lots more, but this is an example -->

To add strings for a particular language, add them to this file.

To add new languages

  1. Create the new translation file, named for the locale code of the language, and load it into the pages/i18n folder.
  2. Update strings.ftl to import the new file, and add it to its stringlib collection.
Parameterized Strings

The getString() function supports parameterized strings using $1. $2, etc. for the parameters. For example, from the French translation file:

"Page $1 of $2": "Page $1 sur $2"

When requesting this string, you pass an array of parameter values to the getString() function, e.g.

${getString('Page $1 of $2', ['1', '10'])?html}
<#-- Translates to "Page 1 sur 10" -->

Assuming the translation includes the parameter tokens, they will be replaced with the specified values. You can escape dollar signs with a backslash (\), e.g. getString("\$2 dollars").

Determining the User's Language

The strings.ftl template uses the following algorithm to determine the appropriate language for the user.

  1. If there is a td.lang browser cookie, its value is used. The default portal theme includes a menu in the header allowing the user to manipulate this cookie.
  2. Otherwise, if the request specified a Content-Language header, that value is used. Most browsers send this header specifying the default language of the user's system.
  3. Otherwise the language is assumed to be English ("en").
Debugging Translations

The getString() function will look for a URL parameter specifying debugLang=true. If it is present, then the strings it returns will be marked.

  • If a string for the given input was found, the result will be wrapped in {s} and {/s}.
  • If a string was not found, it will be wrapped in {MISSING_STRING} and {/MISSING_STRING}.

This will allow you to easily identify strings that are correctly translated, strings that are missing from translation files, and strings that are not being rendered through getString() (because they will not be marked at all).

Translating Generated Text in XSLT Transforms

All of the default theme's XSLT stylesheets take a defaultLang parameter used to control the rendering language, and have a function similar to the Freemarker getString() function for loading translations. See Localizing Generated Text for full details.

2.9: Freemarker Extensions

Titania Delivery provides a number of custom Freemarker directives and functions in addition to the main Tag Library.

2.9.1: Layout Templates

The Layout Template directives allow Freemarker template authors to manage common layouts referenced by multiple pages.

A layout template is a Freemarker template that includes <@t.region> declarations for portions of the template that can be populated by client templates. A region may comprise any portion of the document, from a word or phrase to a large, complex document fragment. Regions are uniquely identified by name. If the same region is mentioned more than once in a layout template, the same content will be rendered everywhere the region is included.

A region declaration may include default content. This is a shorthand for adding content to the region in a client template using <@t.content> directives. Alternatively, the default content may also be used simply for documentation or instructions for users. When the default content is never meant to be rendered, the attribute, @renderDefault should be set to false.

Note these features of the following layout template:

  1. The region named "title" is included twice, once in the <head> and once in the <body> of the document.
  2. The regions named "pageHeader", "pageFooter", and "body" have default content, but only the content of "pageFooter" will be rendered, due to the attribute setting for @renderDefault.
  3. As with all Freemarker templates, the html markup will be emitted as-is. The <@t.region> elements will be replaced with the content supplied by client templates.
  4. Although this simple example does not use any other Freemarker directives or variables, in practice a layout template may include any valid Freemarker constructs.

page_layout.ftl
<html>
  <head>
    <title><@t.region name="title"/></title>
    <@t.region name="head"/>
  </head>
  <body>
    <@t.region name="pageHeader" renderDefault=false>
      PAGE HEADER GOES HERE
    </@t.region>

    <hr>

    <h1><@t.region name="title"/></h1>

    <@t.region name="body" renderDefault=false>
      BODY CONTENT GOES HERE
    </@t.region>

    <hr>

    <@t.region name="pageFooter" renderDefault=true>
      Powered by layout templates!
    </@t.region>
  </body>
</html>

Layout templates are invoked using the <@t.page> custom directive or the <@t.section> directive. Typically, a Freemarker template containing only a <@t.page> directive will be the root template for producing a portal page, as in the following example. Freemarker directives and other content may occur inside or outside the <@t.page> directive; however, within <@t.page>, any content not within a <@t.content>, <@t.prepend>, or <t.append> directive will be discarded.

A simplified description of <@t.page> processing is:

  1. The contents of the <@t.page> directive are processed.
    • Freemarker directives and bare content are processed per normal Freemarker processing. However, any rendered output is discarded.
    • <@t.content> and related directives are captured and stored for later rendering.
  2. The layout template is processed as a Freemarker template, replacing <@t.region> elements with the content specified by the client template.
    Note: If the layout template is (or contains) <@t.page> or <@t.section> directives, the process is recursive. This is explained in more detail below under "Layering Layout Templates."

Directives

Layout templates are implemented using the following directives:

<@t.region>
This directive declares a region to be populated in a layout template by client templates. Attributes:
@name
Required. The name of the region.
@renderDefault
Optional; default is true. Specifies whether the contents of the directive should be rendered if not replaced by a <@t.content> directive in the referencing page.
<@t.region.*>
As a shortcut, regions can be declared with their names as part of the directive name, e.g. <@t.region.body/> instead of <@t.region name="body"/>.
<@t.page>
Surrounds content whose overall structure is defined by a layout template. Attributes:
@layout
Required. A relative path to the layout template to use for the contents defined within this page. (The @master attribute may also be used, though this is deprecated.)
<@t.section>
Surrounds content whose overall structure is defined by a layout template. Attributes:
@layout
Required. A relative path to the layout template to use for the contents defined within this fragment. (The @master attribute may also be used, though this is deprecated.)
<@t.content>
This directive is used within <@t.page> and <@t.section> to define content to populate the regions declared by the referenced layout template. If used outside the context of <@t.page> or <@t.section>, causes an error. Attributes:
@region
Required. The name of the region to be populated.
@action
Optional. The allowable values are "replace", "prepend", or "append". The default is "replace". If there are multiple <@t.content> directives for the same region with an action of "replace", only the first will be executed. Multiple "append" actions will be inserted in the order in which they are declared; multiple "prepend" actions will be applied in reverse order.
defer
Optional boolean attribute. If true, render this content when the matching <@t.region> directive is processed. If false, render the contents immediately when the <@t.content> directive is evaluated. This could affect what variables and macros are in scope when the contents of the directive are evaluated.
<@t.prepend> and <@t.append>
These directives are synonyms for <@t.content>, but with the @region attribute set automatically. For example:
<@t.append region="header">
  More header!
</@t.append>
The above is the same as:
<@t.content region="header" action="append">
  More header!
</@t.content>
<@t.content.*>, <@t.prepend.*>, and <@t.append.*>
These directives are synonyms for their base versions, with the @region attribute included in the directive name instead of as an attribute. For example:
<@t.append.header>
  More header!
</@t.append.header>
<@t.content.body>
  This is body content
</@t.content.body>
This is synonymous with:
<@t.content action="append" region="header">
  More header!
</@t.content>
<@t.content region="body">
  This is body content
</@t.content>
Example Page with a Layout Template
page.ftl

Considering the following page, which contains some content to be rendered using the overall layout specified in another file:

<@t.page layout="relative/path/to/page_layout.ftl">
  <@t.content region="title">Page Title</@t.content>

  <@t.content region="head">
    <link rel="stylesheet" href="style.css">
  </@t.content>

  <@t.content region="pageHeader">
    Overriding page header.
  </@t.content>

  <#-- Uses the simplified, collapse model -->
  <@t.content.body>
    <h2>Page Contents</h2>
    <p>This is where the body goes.</p>
  </@t.content.body>

  <@t.content region="pageFooter" action="prepend">
    Extra, PREPENDED footer for this page.
  </@t.content>

  <#-- Uses the simplified, collapse model -->
  <@t.append.pageFooter>
    Extra, APPENDED footer for this page.
  </@t.append.pageFooter>
</@t.page>
Layouts for Page Sections

Layout templates may also be used for document fragments. Syntactically these fragment layout templates are the same as a page layout template, but will have a top-level element representing some type of HTML division element. Like page layout templates, they will contain <@t.region> directives to place and name regions of replaceable content.

section_layout.ftl
<div>
  <h1><@t.region name="section-title"/></h1>
  <@t.region name="section-contents"/>
</div>
page_with_sections.ftl
<html>
  <head>
    <title>Sections with Common Layout</title>
  </head>
  <body>
    <@t.section layout="relative/path/to/section_layout.ftl">
      <@t.content region="section-title">
        <#-- generate first section title -->
      </@t.content>
      <@t.content region="section-contents">
        <#-- generate first section contents -->
      </@t.content/>
    </@t.section>
    <@t.section layout="relative/path/to/section_layout.ftl">
      <@t.content region="section-title">
        <#-- generate 2nd section title -->
      </@t.content>
      <@t.content region="section-contents">
        <#-- generate 2nd section contents -->
      </@t.content/>
    </@t.section>
  </body>
</html>
Layering Layout Templates

A <@t.page> can reference a @layout which, itself, specifies a <@t.page> directive. Intermediate <@t.page> templates may declare their own <@t.region> directives. Such pages can reference any of the regions at any level of the referenced pages, including the regions in the ultimate layout template.

If a given region is replaced at multiple levels, the first replacing <@t.content> for that region from the outermost page will be used. Prepending and appending content will be applied from all levels, with the outermost page contents coming first for prepending and last for appending.

outerPage.ftl
<@t.page layout="innerPage.ftl">
  <@t.content region="innerPageContent">
    Content from outerPage.
  </@t.content>

  <@t.content region="layoutFooter">
    Footer from outerPage.
  </@t.content>
</@t.page>
innerPage.ftl
<@t.page layout="layout.ftl">
  <@t.content region="layoutContent">
    Content from innerPage.
    <@t.region name="innerPageContent"/>
  </@t.content>

  <@t.content region="layoutFooter">
    This will be overwritten by outerPage.ftl
  </@t.content>
</@t.page>
layout.ftl
<@t.region name="layoutContent"/>
<@t.region name="layoutFooter"/>
Output
Content from innerPage.
Content from outerPage.
Footer from outerPage.

The layout template directives are available in version 4.1 and later.

2.9.2: Utility Functions

Titania Delivery provides a number of utility functions for use by Freemarker templates.
toJSON(object)
Takes the given object and attempts to serialize it as a JSON string. Useful when building JSON API endpoints as part of your portal theme.
urldecode(str)
Unescapes URL-encoded characters in the given string. Useful when loading data from browser cookies that were stored with URL encoding.

2.9.3: Server-Side Javascript in Freemarker Templates

Titania Delivery provides a number of directives for evaluating Javascript on the server as part of rendering.

Freemarker is robust enough to almost function as a fully-fledged programming language. However, it does have some limitations in this regard, and there are times when it would be useful for certain logic-intensive or computational processing.

The following directives will invoke Javascript while processing a template. These directives will be available to both online and packager templates.

<@js>

This directive will allow users to execute javascript at the current location in the template. The javascript code can use page.var(name) to get the value of a Freemarker variable from the page, and use page.assign(name, value) and/or page.local(name, value) to mimic the behavior of the Freemarker <#assign> and <#local> directives. Calling the print() function will write the given value to the current location in the template. In addition, the td.load() function can be used to source an external script from somewhere in the theme.

Functions and variables declared in one <@js> directive will be available to all <@js> directives and <@jsFunction> definitions declared later in the package. In Javascript terms, they share the same global scope.

For example, the following would calculate the base name of a document's filename, assign it to the Freemarker variable 'baseName', and write it to the output.

<@js>
  var doc = page.var('doc');
  var name = doc.label;
  var ndx = name.lastIndexOf('.');
  if (ndx !== -1) {
    name = name.substring(0, index);
  }
  page.assign('baseName', name);
  print(name);
</@js>

The js and jsFunction directives are implemented using the Nashorn script engine, which includes some javascript extensions for integration with Java. See the Nashorn Java Scripting Programmer's Guide. Our implementation does not permit instantiation of arbitrary java classes.

One particular problem for script authors will be handling Freemarker sequences in javascript. The directive implementation handles conversion of top-level sequence variables into javascript as true javascript arrays. It also will convert javascript arrays to Freemarker sequences when assigned using page.assign() or page.local() functions. However, Freemarker sequences in subvariables (hash values) appear in javascript as generic list objects. As such, they do not provide all the usual javascript array functions, such as sort(), join(), etc. In order to call these functions on a sequence obtained from a hash value, use the Nashorn extension function, Java.from() to cast the sequence to a javascript array. The following example shows the different ways Freemarker sequences can be used in javascript.

<@js>
  // 'docs' will be a true javascript array, automatically converted from 
  // a Freemarker sequence by the page.var() function.
  var docs = page.var('documents'); 
  
  // Sort the docs by title. doc.metadata.title is a sequence (List in javascript),
  // which can use the subscript operator, like an array. No need to cast to array.
  docs.sort(function(a,b) {
    return a.metadata.title[0] < b.metadata.title[0] ?
        -1 : a.metadata.title[0] > b.metadata.title[0] ?
        1 : 0;
      });
  for (var i in docs) {
    var doc = docs[i];
    // doc.keywords is a sequence that comes into javascript as a List.
    // We can get the length of the sequence.
    if (doc.keywords.length > 0) {
      // But must cast to javascript array to get full array behavior.
      // WARNING: doc.keywords.join(' ') will cause script exception.
      var kwords = Java.from(doc.keywords).join(' ');
      // Now 'kwords' is a string containing space-separated values of doc.keywords
    }
  }
</@js>
<@jsFunction>

This is similar to the built-in Freemarker directive <#function>, except that the body of the function is implemented in Javascript instead of Freemarker. It has two attributes, @name (the name of the function) and, optionally, @parameters, the comma-separated list of parameter names.

Here is a function that computes the base name of a document passed in as a parameter.

<@jsFunction name="getBaseName" parameters="doc">
  var name = doc.label;
  var ndx = name.lastIndexOf('.');
  return ndx === -1 ? name : name.substring(0, ndx);
</@jsFunction>
<#-- Called using normal FreeMarker function call semantics. -->
${getBaseName(doc)?html}

For additional details, see the discussion of script engine implementation under <@js>, above.

2.9.4: Debugging Freemarker

The Freemarker language provides a number of useful debugging tools. Titania Delivery adds to these with a number of mechanisms for analyzing and debugging the behavior of your templates.
Use <#attempt>/<#recover>

Wrapping a section of your template in <#attempt> allows you to handle errors that occur yourself, instead of having the page fail to render entirely. The error will be encapsulated in the built-in .error variable.

<#attempt>
<#-- Risky code here -->
<#recover>
<#-- Error handling here; the error is in the .error variable -->
<pre>${.error?html}</pre>
</#attempt>
Set the ?ftldebug URL parameter

When you add the ?ftldebug parameter to a Titania Delivery portal URL, HTML comments will be inserted around all template <#include> directives. In addition, boundaries around content laid out using the special Layout Templates directives will also be marked. This can significanly help in identifying where the Feemarker code that generated a part of the page is managed.

<!-- START TEMPLATE "/pages/viewer.ftl" -->
<!-- START TEMPLATE "/pages/masters/mainLaout.ftl" -->
<!DOCTYPE html>
<html>
<head>
  <!-- START REGION "htmlHead" from "/pages/viewer.ftl" -->
  <title>Topic Title</title>
  <!-- END REGION "htmlHead" from "/pages/viewer.ftl" -->
</head>
<body>
  <!-- START REGION "body" from "/pages/viewer.ftl" -->
    <!-- START TEMPLATE "/pages/masters/modules/layout.ftl" -->
      Navbar Content Here
    <!-- END TEMPLATE "/pages/masters/modules/layout.ftl" -->
    Contents of the page here.
  <!-- END REGION "body" from "/pages/viewer.ftl" -->
</body>
</html>
<!-- END TEMPLATE "/pages/masters/mainLayout.ftl" -->
<!-- END TEMPLATE "/pages/viewer.ftl" -->

2.10: Template Variable Types

The page templates in a Portal Theme are provided with various objects representing the content and metadata in the system.
Key Content Access Concepts

Files in Titania Delivery are referred to as Project Items, since they exist within projects. In order to identify content for display, you need the following identifying information:

  • The Project Key.
  • Either the Item Key (database identifier of the file) or the folder path within the project.
  • For DITA topics in the context of a map, the item key of the referencing map, called the Context Key.
  • If a DITA topic is referenced multiple times from the same map, you will also need the resolved ID of the reference to the topic, called the Reference Identifier or refId.

For everything other than DITA topics, the Project Key and Item Key are sufficient to identify the content, and can be encapsulated in an ItemIdentifier . The more robust form for identifying content is the ContentLocator object, which contains the project key, item key, and context identification.

Project Items have properties and metadata associated with them. This information is often accessed using ItemDataAccess objects provided to the page. You can also get one from any page using <@td.fileProperties> .

Key Search Concepts

Rather than return Project Items, the search engine returns special SearchResultDocument objects, containing the identifying properties of the project item, as well as some details and metadata. Most searches return these objects grouped using SearchResultGroup objects, which group contextualized topics by their Context Key (uncontextualized DITA topics and non-topic content will also be contained in groups, but those groups will have only one member). Finally, the search engine provides pagination of groups using SearchResultGroup objects.

Key Assembly Concepts

A custom Assembly object is its own data structure, identified by its key. Items within an assembly are identified by the assembly key and the generated reference ID for that entry.

2.10.1: Assembly

Represents an assembly. Assemblies come in two flavors:

  • Created in the admin application, belonging to a project.
  • Created by a Portal application, belonging to the portal.
Assemblies created in the former case are presented to the Portal identically to all other Project documents. This data type does not apply to those as far as Portal Themes are concerned. In the latter case, an array of these objects can be retrieved by the <@td.assemblies> tag.
Properties
String key
The unique ID of the Assembly
Date createDate
The date the Assembly was created
Date lastModified
The last-modified date of the Assembly
String name
The title of the Assembly
String refId
The unique identifier for this node within the assembly. Used to identify which document to display when rendering the node in a portal.

2.10.2: ContentLocator

An object that encapsulates all of the information that, together, uniquely identifies a piece of content within the Titania Delivery repository.
Properties
String projectKey
The project key of the item.
String itemPath
The path to the item within the Project specified by projectKey .
String itemKey
The key of this item, unique to its containing Project.
String contextKey
The key of this document's context document, if applicable.
ItemIdentifier contextItemId
The encapsulated Project and item keys of this document's context document, if applicable.
Map queryParams
A map of additional parameters. The most common of these is "refId", which identifies the specific reference within the context identified by contextItemId .

2.10.3: ContentRelationship

Represents a relationship, such as a link or graphic reference, between two files.
Properties
ItemIdentifier source
The identifier for the source of the relationship.
ItemIdentifier target
The identifier for the target of the relationship. May be null if the referenced file does not exist, in which case the location of the missing object is identified by targetPathInProject .
RelationshipType type
The relationship type. The possible values are PARENT_CHILD , LINK , or GRAPHIC .
String targetPathInProject
The project path of the object being referenced.
String refTagInfo
Retrieves the tag name or DITA class of the reference.
boolean resolved
Whether the reference is resolved, that is, the target object exists.

2.10.4: Contextualization

Properties
ItemIdentifier childKey
The identifier for the uncontextualized object.
String description
A description of the context. Usually the title of the referencing DITA map.
boolean virtualTopic
Whether the topic is a virtual topic without a single source representation, such as a virtual topic generated from a title-only topicref, or a chunked section of a monolithic source.
String contextualizedTitle
Retrieves the title of the contextualized topic.
MetadataEntry[] metadataEntries
The metadata entries set on this document. It does not contain the metadata inherited from parent maps or the containing project.

2.10.5: FacetCount

Properties
String name
The facet field value.
long count
The number of documents with this value.

2.10.6: FacetField

Represents a piece of metadata used for faceting and its collection of values.
Properties
String name
The name of the facet field.
FacetCount[] values
The list of values for the facet, and their counts.

2.10.7: FragmentMetadata

An object holding the metadata values on a document fragment. The values array is an array of Strings.
Properties
String id
String label
Map metadata

2.10.8: HttpEntityContent

Represents an HTTP request or response entity.

The contents of the entity can be accessed using the body property. If expecting JSON-encoded content, HTML, or XML content, a parsed representation of the content can be accessed using the json , html , or xml properties. If there are errors parsing the content, they are available in the parseError property.

Introduced in version 4.2.

Properties
byte [] raw
The uninterpreted bytes of the entity.
String defaultCharset
The default charset for this entity, if any.
String defaultContentType
The default content type for this entity, if any.
String body
The entity body as a string.
String text
The entity body as a string.
Document xml
The body as a parsed XML DOM structure. If parsing fails, this property will be null and any exceptions can be read from the parseError property. The DOM structure can be interrogated using normal Freemarker XML handling; see the Freemarker documentation ( https://freemarker.apache.org/docs/xgui.html for details.
Object json
The body as a parsed JSON object. This can also be done manually in Freemarker using response.body?eval ; this property is provided as a convenience. If the parsing of the JSON fails, this property will be null and any exceptions can be read from the parseError property.
Document html
The body as a DOM structure generated by the Jsoup Java library ( https://jsoup.org/ ). If the parsing of the body fails, this property will be null and any exceptions can be read from the parseError property.
String base64
The raw entity encoded as base64 string. This can be used to handle binary data, for example to store a file that includes binary data.
String dataUrl
The bytes encoded as a data URL using the appropriate default MIME type.
Exception parseError
The Java exception caused by an attempt to parse the body content when accessed via xml , html , or json .

2.10.9: HTTPResponse

Represents the data returned by the <td.httpRequest> tag.

The contents of the response body can be accessed using the body property. If expecting JSON-encoded content, HTML, or XML content, a parsed representation of the content can be accessed using the json , html , or xml properties. If there are errors parsing the content, they are available in the parseError property. For example:

 <@td.httpRequest url="https://example.com/data.json" var="response"/>
 <#if response.status == 200>
   <#if response.contentType?contains('/json')>
     <#assign responseStruct = response.json!''/>
   <#elseif response.contentType?contains('/html')>
     <#assign responseStruct = response.html!''/>
   <#elseif response.contentType?contains('/xml')>
     <#assign responseStruct = response.xml!''/>
   </#if>
   <#if response.parseError??>
     <b>ERROR: ${response.parseError.message?html}</b>
     <pre>${response.body?html}</pre>
   </#if>
 </#if>
 

All headers are available in the headers property. The keys for the header names are represented in all lower-case, as well as in whatever case was presented in the actual response.

Introduced in version 4.2.

Properties
Map headers
Accesses a multi-valued map containing all of the headers present in the response.
long duration
The amount of time, in milliseconds, the request took to execute.
String responseUrl
Shortcut for getHeader("location") .
String contentType
Shortcut for getHeader("content-type")
int status
The HTTP status code of the response.
String statusText
The status message of the response.
byte [] raw
The uninterpreted bytes of the entity.
String defaultCharset
The default charset for this entity, if any.
String defaultContentType
The default content type for this entity, if any.
String body
The entity body as a string.
String text
The entity body as a string.
Document xml
The body as a parsed XML DOM structure. If parsing fails, this property will be null and any exceptions can be read from the parseError property. The DOM structure can be interrogated using normal Freemarker XML handling; see the Freemarker documentation ( https://freemarker.apache.org/docs/xgui.html for details.
Object json
The body as a parsed JSON object. This can also be done manually in Freemarker using response.body?eval ; this property is provided as a convenience. If the parsing of the JSON fails, this property will be null and any exceptions can be read from the parseError property.
Document html
The body as a DOM structure generated by the Jsoup Java library ( https://jsoup.org/ ). If the parsing of the body fails, this property will be null and any exceptions can be read from the parseError property.
String base64
The raw entity encoded as base64 string. This can be used to handle binary data, for example to store a file that includes binary data.
String dataUrl
The bytes encoded as a data URL using the appropriate default MIME type.
Exception parseError
The Java exception caused by an attempt to parse the body content when accessed via xml , html , or json .
Methods
String[] getHeaderValues( String name)
Retrieves all values for the header with the given name.
String getHeader( String name)
Utility method for retrieving the first value of a header.

2.10.10: ItemDataAccess

Encapsulates access to the various entities related to an item.
Properties
ItemIdentifier itemIdentifier
The ItemIdentifier for the item.
ItemProperties properties
Properties and metadata for the item.
ProjectInfo projectInfo
Information about the project containing the item.
Map propertyMap
Shortcut to properties.propertyMap
Map metadataMap
The MetadataEntries as a Map.
ContentRelationship[] outgoingRelationships
The outgoing relationships, such as links and graphics, from this object.
ContentRelationship[] incomingRelationships
The incoming relationships, such as links and graphics, to this object.
ProjectMetadata projectMetadata
The metadata collection on the project containing the item.

2.10.11: ItemIdentifier

Encapsulates the project and item keys for a given project item.
Properties
String projectKey
The key of the Project that contains the file.
String itemKey
The key of the file, unique to the Project.

2.10.12: ItemProperties

A collection of data on an item in a Titania Delivery Project.
Properties
ItemIdentifier item
The encapsulated Project key and Item key for this item.
SpecifiedItemDetails specifiedDetails
The details that can be on this item in the admin application, such as name and description. Only available if this is a non-parsable binary file.
Map embeddedMetadata
The embedded metadata from the file. This will be present for PDF, Microsoft Office, several graphics formats, and a number of other file types.
String effectiveUrlPath
The URL path by which this document can be directly addressed as a standalone document.
Map propertyMap

A hash of properties set on this document. Keys common to all files are:

  • key - The key of the document
  • name - The name of the document
  • projectKey - The key of the Project containing this document
  • size - The size of the document, in bytes
  • contentType - The MIME Type of the document
  • path - The path to this document within the Project
  • createDate - The date the document was created in Titania Delivery
  • lastModified - The date the document was last modified in Titania Delivery

Keys set on image files only are:

  • img_width - The width of the image, in pixels
  • img_height - The height of the image, in pixels

Keys set on XML files only are:

  • xml_isWellFormed
  • xml_hasDTD
  • xml_hasSchema
  • xml_hasDoctype
  • xml_doctypeExists
  • xml_isValid
  • xml_elementCount
  • xml_elementIds
  • xml_primarySchema
  • xml_publicId
  • xml_systemId
  • xml_schemas
  • xml_rootElement
  • xml_rootNamespace
  • xml_doctypeFile
  • xml_doctypeProject
  • xml_doctypeName
  • xml_title
  • dita_navtitle
  • xml_validationError
  • xml_isDita
  • dita_isTopic
  • dita_isMap
  • dita_isDitabase
  • dita_domains
Date createDate
The date that this item was created in Titania Delivery.
MetadataEntry[] metadataEntries
The metadata entries set on this document. It does not contain the metadata inherited from parent maps or the containing project.
FragmentMetadata[] allFragmentMetadata
Get all fragment metadata as a list.

2.10.13: ItemRepresentationType

These are the possible representation types for processed renditions of content items.
Enumeration constants
ORIGINAL
The original content item.
PROCESSED
The content item processed according to its content type.
FLATTENED
For DITA topics, the processed topic with external references resolved.
MONOLITH
For DITA maps, expanded map with all referenced content resolved.
PREVIEW
A browser-friendly representation of the content item.

2.10.14: MetadataEntry

An object representing a single metadata entry on a document. The values array is an array of Strings.
Properties
String name
The name of this metadata.
Object[] value
An array of Strings that are the values to this metadata

2.10.15: MetadataFilter

This object represents the metadata filters set on a Portal through the Administrative application. It is the data structure in the metadataFilters array in PortalIdentity .
Properties
MatchMode matchMode
Possible values are ANY and ALL . ANY will match documents that contain any of the values on the filter. ALL will match documents that contain all of the values on the filter.
boolean matchAbsent
If true , this metadata filter will match documents missing the given filter.
String name
The name of this metadata.
Object[] value
An array of Strings that are the values to this metadata

2.10.16: OfflinePackage

Represents the status and metadata of an offline package.

Introduced in version 4.1.

Properties
String key
The unique key.
String portalKey
The key of the portal where the package was generated.
String packager
The name of the packager that created the package.
String createdBy
The ID of the authenticated user who built the package.
OfflinePackageDocumentInfo[] documents
The documents that were included in the package.
Map params
The parameters passed to the packager.
Status status
The current status of the package. Values are QUEUED, WORKING, FINISHED, and FAILED.
String scriptOutput
The output log of the packager script.
String filename
The filename used for the package.
String mimeType
The MIME type of the package.
boolean signContents
Whether to generate SHA-512/RSA signatures for each file in the package, as well as the package itself. The default is false . Introduced in version 4.2.
String signaturePathPrefix
If signing package contents, this specifies the path prefix to apply for signature files. this can be used to place signature files in their own folder in the archive. The default is no prefix. Introduced in version 4.2.
String signaturePathSuffix
If signing package contents, this specifies the suffix to add to signature files. The default is ".signature". Introduced in version 4.2.
String signatureAlgorithm
The signing algorithm to use for both the package itself and individual files within the package, if signContents is enabled. The value must match those available in Java; see https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Signature . The default is "SHA512withRSA" . Introduced in version 4.2.
Date createDate
The timestamp when the package was requested.
String statusMessage
The current status message for the package. The package script can update this message as it builds the package in real-time, so this message should reflect the current state of the packaging process.
String hash
The hexadecimal-encoded SHA-512 hash of the package archive. Introduced in version 4.2.
String signature
The hexadecimal-encoded SHA-512/RSA checksum for the zip itself. Introduced in version 4.2.
boolean makeXmlSignatures
Whether to make XML signatures.

2.10.17: OfflinePackageDocumentInfo

Represents a document included in an offline package.

Introduced in version 4.1.

Properties
String projectKey
The project key.
String itemKey
The item key.
String contextKey
The context key.
String refId
The reference ID within the context, for example, the topicref ID in a DITA map.
boolean rootDoc
Whether or not this document was explicitly requested for inclusion in the package, and not included by virtue of being part of another explicit document. For example, when packaging a specific DITA map, the map itself will be marked as explicitly requested, while its topics will not (assuming the package requested the inclusion of contextualized children).
boolean virtualTopic
If true, this is a 'virtual' topic generated through content processing. There are two main cases for virtual content: DITA topics generated for <topicref> elements that specify a title but no content, and chunked sections of non-DITA XML documents.

2.10.18: PortalIdentity

This object is available to every Portal Theme page via $ portal and includes data about the Portal itself.
Properties
String name
The internal name of the portal, for admin purposes.
String urlPath
The URL path of the portal.
Date createDate
The date this Portal was created.
String displayName
The name to use when displaying the Portal title through the Portal Theme.
String description
The Description of the Portal.
MetadataFilter[] metadataFilters
The Metadata Filters set on this Portal.
String ownerName
The name of this Portal's Owner.
PortalSearchFacet[] searchFacets
The search facets set on this Portal.
String commentManagerSiteKey
The unique identifier that associates comments with this Portal.
String themeName
The name of the Portal Theme associated with this Portal.
String[] enabledFeatures
The features enabled for this portal. Possible values are:
  • anonymousAccess
  • docLevelComments
  • elLevelComments
  • customAssemblies
  • helpfulVote
Map parameters
Parameters for this portal as specified in the config.xml for the portal theme
String userFilterFunction
Javascript function used for user filters
PortalRobotsTxtBehavior robotsTxtBehavior
The behavior of this portal in robots.txt. One of NONE, DISALLOW, or ALLOW.
String portalProject
The key of the project for portal-generated content. Introduced in version 4.2.
Methods
boolean hasFeature( String id)
Utility method for determining whether the portal has the specified feature. Equivalent to portal.features?seq_contains(id) .
String facetDisplayName( String facetMdName)
Utility method for determining the display name of the given search facet.

2.10.19: PortalSearchFacet

This object stores the search facets of this Portal, set in the Admin application. It is the data structure of the searchFacets array in PortalIdentity , which is available on every page in the Portal.
Properties
String metadataName
The name of the metadata item.
String displayName
The display name of the metadata item, as set in the Search Facets tab in the admin application.

2.10.20: PortalUser

This object is only available to the Portal if the Portal has security ( hasSecurity ) enabled and the current user is logged in ( isAuthenticated ). If those two conditions fail, this object will be null and any attempt to reference it will result in a Freemarker error. This can be using the following code:

 <#if hasSecurity && isAuthenticated>
   <#-- Can now safely reference ${user} -->
 </#if>
 

or:

 <#if user??>
   <#-- Can now safely reference ${user} -->
 </#if>
 
Properties
String id
A persistent, obfuscated character string based on login name, which identifies the current user.
String userName
The username of the user, as specified by the server pointed to in this Portal's security configuration.
Map properties
The user properties pulled from the authentication service. For SAML-secured and OpenID-secured portals, this will contain whatever attributes were included in the authentication response message sent by the Identity Provider. For LDAP-secured portals, this will contain the properties listed in the LDAP connection configuration. NOTE: This collection allows for multi-valued properties, so values are arrays, not single values. Properties with single values will be lists of size 1.
Methods
boolean hasProperty( String name, String value)
Tests whether there exists a property with the given name containing the given value. If the value is not specified, this method tests whether the property exists.

2.10.21: ProjectInfo

Represents details of a project.
Properties
String key
The project key.
String name
The project name.
String urlSlug
The project URL slug.
String fileUrlFormula
The template for generating file URLs in this project.
Date createDate
The date and time that a project was created.
String contentPortal
The key of the portal for which this project holds content. Introduced in version 4.2.

2.10.22: ProjectItem

This object describes the main properties of a file stored in a Titania Delivery project.
Properties
String parentKey
The parent key for the item. Will be null for root-level items.
String key
The object's unique key that can be used to retrieve it directly from the project.
String label
The label of this object.
String contentType
The content type of the object. Will be null for folders.
String projectPath
The path within the project to this object. The path will begin with a forward slash (/) and be delimited by forward slashes. The path will end in a forward slash if this is a folder.
boolean folder
Whether the object is a folder.
long size
The size, in bytes, of the object.
Date lastModified
The last-modified date of the object.
Date createDate
The creation date of the object.

2.10.23: ProjectMetadata

Properties
String projectKey
The project key.
MetadataEntry[] metadataEntries
The metadata entries set on project.

2.10.24: SearchResultDocument

This represents a single document returned from a search. It is the data type in the results array of SearchResultGroup and of the array returned by the <@td.search> tag.
Properties
String projectKey
The key of this document's containing Project.
String itemKey
This document's item Key, unique to the project. In some cases, may be of the form key:elementId for chunked content.
String path
The path to this document within its project.
String filename
This document's filename.
String urlPath
The URL path for this file.
String title
This document's title, or its filename if the title does not exist.
String searchTitle
This document's search title, or its title if the searchTitle does not exist.
String[] keywords
The keywords of the document.
String textContents
The raw text contents of the document.
String contextKey
The key of this document's context document, or xxnullxx if it is a stand-alone document.
String contextName
The name of this document's context document.
String contextRefId
If this document is present in its parent context more than once, the contextRefId identifies which version the current document represents.
Long created
The date that this document was created in Titania Delivery.
Long lastModified
The date that this document was last modified in Titania Delivery.
boolean contextualized
true if this document is present in a parent context document. Equal to contextKey != xxnullxx .
String highlight
If this document was returned as part of a search query, this field will contain the text contents surrounding the most-relevant "hit" in the document.
Map metadata
A hash representing the metadata on this document. The keys are metadata names as assigned in the TD admin application or present in the source document. The value is an array of strings representing the values of that metadata. If a key is present it is guaranteed that there will be at least one element in the values array.
Map metadataNormalized
Similar to metadata , but with _md stripped from all the keys.
boolean virtual
Describes whether this search result represents a physical file in a Titania Delivery project, or a virtual, generated document. Examples of virtual documents include topics generated for DITA <topicref> elements that specify a title but no @href or @keyref, or a chunked section of a non-DITA document.

2.10.25: SearchResultGroup

This object is the data type of the content array of groups. It represents a "group" of search results.

Search results are generally grouped by their DITA map context. That is, a DITA topic used in multiple DITA map contexts will have its matching search results grouped together. No other documents are grouped in this way.

nContexts will always be >= 1 and equal to results?size . firstResult will be the same SearchResultDocument as results[0] .

Properties
SearchResultDocument firstResult
convenience variable for results[0] .
String groupingValue
The value of the grouping field specified by the query for all of the documents in this group. For example, grouped search results are grouped by the "itemKey" property by default, so this value will be the itemKey of all of the results in this group. When grouping by another field, this will be the value for that field on all of the documents in this group. Introduced in version 4.2.1.
int nContexts
The size of the results array. The value will always be >= 1.
SearchResultDocument[] results
The search results grouped by topic. If the topic was returned by a search result, then each context in which it exists will be present in the array. If the document is stand-alone, (that is, it is not referenced by any parent document) then there will be only one entry in the array. results?size will always be equal to nContexts .

2.10.26: SearchResultsPage

Represents a list of results from the search engine. When retrieved without grouping, e.g. the <@td.search> tag, the paged items will be SearchResultDocument objects. When grouped, e.g. on the searchResults.ftl or from the <@td.groupSearch> tag, the contents will be SearchResultGroup objects.
Properties
FacetField[] facetFields
The list of facet counts for the search.
int number
The zero-based index of the current page.
int size
The maximum number of results this page could contain.
int totalPages
The total number of pages available for the current search.
int numberOfElements
The number of elements on the current page. Will be less than or equal to size .
long totalElements
The total number of elements (groups or documents) returnable by the current search.
long totalHits
The total number of documents returnable by the current search.
boolean previousPage
true if there is a previous page.
boolean previous
true if there is a previous page.
boolean firstPage
true if the current page is the first page.
boolean nextPage
true if there is a next page.
boolean next
true if there is a next page.
boolean lastPage
true if the current page is the last page.
boolean last
true if the current page is the last page.
String groupedBy
The property by which the results are grouped when applicable, such as the default search page or the results of the td.groupSearch directive. The default grouping field is "itemKey", meaning that DITA topics with multiple DITA map contexts will be grouped together. This value will be null for ungrouped searches. Introduced in version 4.2.1.
Object[] startNextAfter
Markers that can be used in a subsequent request to get the next page of results. Avoids performance and/or hard limits on the full size of all pages in the search engine.
T[] content
The contents of the current page. This will either be SearchResultGroup or SearchResultDocument objects, depending on the way the query was performed.
  • When the search is performed using the standard portal search page, and rendered with searchResults.ftl , this will be a list of SearchResultGroup .
  • When performed using the <@td.groupSearch> tag, the results will also be of type SearchResultGroup .
  • When performed using <@td.search> , the results will be lists of SearchResultDocument .

2.10.27: SiteDataStorage

An extension of UserDataStorage that is shared by all users of a portal, including anonymous users. This object exposes a number of special methods for storing data and documents in a searchable way.

Portals have an associated content project that can store documents and metadata generated by the portal. Also, each portal can have one or more searchable indexes containing arbitrary data. (Additional portal data storage capabilities for nonindexed values, counters, and lists are described under "DataStorage".)

When a portal is created, a project is automatically created and associated with the portal as the portal content project. This project may contain portal-generated content, and functions like any other content project. The siteData object provides methods for accessing portal content items.

Indexed data can be stored and searched under user-defined index names. Each index should hold similarly structured records. Methods for storing and retrieving indexed data are provided in Freemarker and javascript using the siteData object. The administration interface also provides access to view and download portal indexed data. IMPORTANT: The total number of user-defined indices on the platform cannot exceed 100 at any time (across all portals). Theme developers and platform administrators should ensure that no more than 100 indices exist at any time.

Introduced in version 4.2.

Methods
Iterable searchIndexed( String index, String query, int [] paging)
Searches the given index using the given search. Since each index can have its own record type, all indexed searches are ordered by the built-in Elasticsearch field, "_doc".
int deleteIndexed( String index, String query)
Deletes records from the given index that match the given query.
int deleteIndexed( String index, String query, boolean waitRefresh)
Deletes records from the given index that match the given query. Passing false for the waitRefresh method will cause the method to return before the deletion has been fully processed. The default is true
void deleteIndex( String index)
Deletes the index with the given name, and all data in it.
void putIndexed( String index, Object value)
Stores a data structure in such a way that its properties can be queried using normal search syntax. The index functions as a namespace for the stored data. As a best practice, radically different data structures should be stored in different indexes.
boolean putIndexed( String index, Object value, boolean waitRefresh)
Stores a data structure in such a way that its properties can be queried using normal search syntax. The index functions as a namespace for the stored data. As a best practice, radically different data structures should be stored in different indexes. Passing false for the waitRefresh method will cause the method to return before the insertion has been fully processed. The default is true
void putFile( String location, String mimeType, CharSequence contents, Map metadata)
Creates or updates a file in the portal's project. If the file already exists, its contents will be overwritten and its metadata replaced with the given hash.
boolean fileExists( String location)
Determines whether a file exists at the given path.
String getFile( String location, String representation)
Retrieve the contents of the file at the given path as a string.
String getFileLocator( String location)
Retrieves the File Locator for the file at the given path.
String getFileDownloadURL( String location, String representation)
Gets an HTTP URL at which the given file can be downloaded directly, optional with a processed representation.
String getFileUploadURL( String location, boolean folderMode, String itemName)
Get a URL to add or replace a file via POST. Any existing file at the location will be overwritten.
boolean deleteFile( String location)
Deletes the given file or folder.
boolean setFileMetadata( String location, Map metadata)
Sets the metadata on the given file. The existing non-intrinsic metadata entries for the file will be replaced with those supplied. Use updateFileMetadata(String,Map) for adding additional metadata to a file.
boolean updateFileMetadata( String location, Map metadata)
Updates the metadata on the given file. The existing non-intrinsic metadata entries for the file will be updated and merged with those supplied.
String getProcessingPhase( String location)
Gets the current processing phase of the item at the specified location.
ProjectItem[] getProjectRoots()
List the portal project root contents.
ProjectItem[] getFolderContents( String location)
List the specified folder contents.
boolean exists( String key)
Determines if data with the given key exists.
void put( String dataKey, Object data)
Stores data with a key. If the data is a string, it is stored as-is. Otherwise, it is converted to JSON and then stored.
Object get( String dataKey)
Retrieves the data with the given key. Objects are encoded as JSON for storage, so the resulting object will be whatever type was stored, unless it's an object, in which case it will be represented as a map hierarchy.
void push( String listKey, Object value, int cap)
Pushes a new value onto the list with the given key.
void pushUnique( String listKey, Object value, int cap)
Pushes a new value onto the list with the given key. If it already exists in the list, it is removed and moved to the top.
Object[] getList( String listKey)
Retrieves the list with the given key.
boolean existsInList( String listKey, Object value)
Determines whether the given value exists in the list with the given key.
void removeFromList( String listKey, Object value)
Removes the given value from the list with the given key.
long getCounter( String key)
Retrieves the counter with the given key.
void setCounter( String key, long counter)
Assigns a counter value to the given key.
void incrementCounter( String key, long by)
Increments the counter with the given key by the given amount (which may be negative). If no such counter exists, it will be created and set to the given increment value.
void delete( String key)
Deletes the data and/or list with the given key.
void deleteCategory( String category)
Deletes all entries in the given category.
Map getCategory( String category)
Retrieves all entries in a given category.

2.10.28: SpecifiedItemDetails

Contains details for non-parsable formats, such as graphic files and other binary file types, that can be specified via the UI in the Administrative application. Member of ItemProperties .
Properties
String title
The title of the document, as specified in the administrative application.
String description
The description of the document, as specified in the administrative application.
String[] keywords
The keywords of the document, as specified in the administrative application.

2.10.29: UserDataStorage

Portals can store data on the server for later retrieval. If the user is authenticated, the stored data is persisted between sessions. Otherwise, it expires when the client session expires.

There are three types of nonindexed persistent data: values, counters, and lists. Each is associated with a key. A key may have all three types of values.

Values

Values are stored via put(String, Object) and retrieved via get(String). Values can be anything that can be serialized as JSON, including strings, numbers, booleans, lists, and data structures. Data values are serialized as JSON when stored in the database, and de-serialized when retrieved. If a value is a complex non-map object when it is stored, it will be retrieved as a map.

Lists

Lists are essentially managed arrays that can be added to or removed from without needing to retrieve the full data structure. You create/add to lists via push(String, Object, int) or pushUnique(String, Object, int). The full list can be retrieved via getList(String). Elements can be removed via removeFromList(String, Object). You can check whether a value exists in a list via existsInList(key). Like values, list entries can be anything that can be serialized as JSON.

Counters

Counters are whole number values that can be easily incremented and decremented with the atomic operation incrementCounter(String, long). They can also be retrieved and stored via getCounter() and setCounter().

Important: The three values are treated independently. If you set a value via setCounter(), it must be retrieved by getCounter(); it will not be retrieved by get(). Similarly, lists stored with put() cannot be modified via push(). However, if a given key has more than one type of value - a list and a counter, for instance - calling delete() on that key will delete all the associated data.

Categories

A category is implicitly defined by keys beginning with a certain prefix and a period. For example, deleteCategory('foo') would delete any entry with a key beginning with 'foo.' ('foo.a', 'foo.b', etc.). It would not delete 'foo'. There are a number of methods that allow the interaction with whole groups of entries by their category. This allows the development of features that involve an interaction of multiple keys such that they can be deleted.

Introduced in version 4.0.

Methods
boolean exists( String key)
Determines if data with the given key exists.
void put( String dataKey, Object data)
Stores data with a key. If the data is a string, it is stored as-is. Otherwise, it is converted to JSON and then stored.
Object get( String dataKey)
Retrieves the data with the given key. Objects are encoded as JSON for storage, so the resulting object will be whatever type was stored, unless it's an object, in which case it will be represented as a map hierarchy.
void push( String listKey, Object value, int cap)
Pushes a new value onto the list with the given key.
void pushUnique( String listKey, Object value, int cap)
Pushes a new value onto the list with the given key. If it already exists in the list, it is removed and moved to the top.
Object[] getList( String listKey)
Retrieves the list with the given key.
boolean existsInList( String listKey, Object value)
Determines whether the given value exists in the list with the given key.
void removeFromList( String listKey, Object value)
Removes the given value from the list with the given key.
long getCounter( String key)
Retrieves the counter with the given key.
void setCounter( String key, long counter)
Assigns a counter value to the given key.
void incrementCounter( String key, long by)
Increments the counter with the given key by the given amount (which may be negative). If no such counter exists, it will be created and set to the given increment value.
void delete( String key)
Deletes the data and/or list with the given key.
void deleteCategory( String category)
Deletes all entries in the given category.
Map getCategory( String category)
Retrieves all entries in a given category.

2.11: The Titania Tag Library

Titania Delivery provides a custom tag library that allows for accessing powerful features of Titania Delivery and easier customization of Portal Themes.

The tag library can be included on any page via <#assign td=JspTaglibs['http://www.titaniasoftware.com/harp/taglib']/>. The td identifier can be replaced with any valid name. This directive must be included on every top-level template file that uses the tag library, and is not supplied to pages by the infrastructure. Once included on the page, custom tags can be accessed with <@td.tagName atts... />.

2.11.1: <@td.assemblies>

Retrieves a user's Assembly collection and stores them in the @var

This tag contains the following attributes:

limit
The maximum number of results per page. Must be a positive integer n where 0 < n <= 2147483647. This does not limit the total number of search results, which may be limited by other settings.
  • Required: false
  • Type: int
  • Default: 10
scope
The scope of the @var attribute. Valid values are "page" and "request".
  • Required: false
  • Type: String
  • Default: "request"
sortBy
The property or properties to sort by, separated by whitespace or comma.
  • Required: false
  • Type: String
  • Default: none
sortDirection
A list of sort directions ('ASC' or 'DESC'), corresponding in order to the list of 'sortBy' fields. If the list is empty or shorter than the 'sortBy' list, the default of 'ASC' will be used for the missing directions.
  • Required: false
  • Type: String
  • Default: ASC
startAfter
Alternative pagination mode. The startNextAfter token from a previous page.
  • Required: false
  • Type: Object
  • Default: null
startPage
The page number to return. Must be an integer 0 <= n <= 10000. Note that ithe preferred pagination method is to use the startAfter attribute.
  • Required: false
  • Type: int
  • Default: 0
var
The name of the variable used to access the result.
  • Required: true
  • Type: String
  • Default: none
Example Usage:

It is advised to check for the presence of the Assemblies Feature and ensure that a PortalUser is logged in before using this tag. Failing to do so will result in errors.

<#if portal.enabledFeatures?seq_contains['customAssemblies'] && user??>
  <@td.assemblies var="assemblies" />
  .
  .
  .
  <#if assemblies?size &gt; 0>
    <#list assemblies as assembly>
      <a href=<@td.assemblyViewerUrl assembly=assembly/>${assembly.name?html}</a>
    </#list>
  </#if>
</#if>

2.11.2: <@td.assemblyEditorUrl>

The assemblyEditorUrl tag generates the URL to the Assembly editor for the current Portal. If @var is specified, this tag stores the URL in the given variable, otherwise it places it onto the page.

This tag contains the following attributes:

assembly
The Assembly object to open in the editor. If not specified, the editor will be opened with a new empty assembly.
  • Required: false
  • Type: Assembly
  • Default: none
scope
The scope of the @var attribute. Valid values are "page" and "request".
  • Required: false
  • Type: String
  • Default: "request"
urlQuery
The query string. This value will NOT be URL-encoded, so it should be encoded already. You can also use nested <@td.urlParam> tags to specify individual URL parameters in a URL-escaped way.
  • Required: false
  • Type: String
  • Default: none
var
The name of the variable used to access the result.
  • Required: false
  • Type: String
  • Default: none
Example Usage:

It is advised to check for the presence of the Assemblies Feature and ensure that a PortalUser is logged in before using this tag. Failing to do so will result in errors.

<#if portal.enabledFeatures?seq_contains['customAssemblies'] && user??>
  <@td.assemblies var="assemblies" />
   .
   .
   .
  <#if assemblies?size &gt; 0>
    <#list assemblies as assembly>
      <a href=<@td.assemblyEditorUrl 
                   assembly=assembly 
                   target="_blank"/>Edit ${assembly.name?html}</a>
    </#list>
  </#if>
</#if>

2.11.3: <@td.assemblyViewerUrl>

The assemblyViewerUrl tag provides a url to the viewer page of a given Assembly

This tag contains the following attributes:

assembly
The Assembly object.
  • Required: true
  • Type: Assembly
  • Default: none
refId
The ID of the node in the Assembly to display. If omitted, the URL of the Assembly itself is returned.
  • Required: false
  • Type: String
  • Default: The root node of the Assembly
scope
The scope of the @var attribute. Valid values are "page" and "request".
  • Required: false
  • Type: String
  • Default: "request"
urlQuery
The query string. This value will NOT be URL-encoded, so it should be encoded already. You can also use nested <@td.urlParam> tags to specify individual URL parameters in a URL-escaped way.
  • Required: false
  • Type: String
  • Default: none
var
The name of the variable used to access the result.
  • Required: false
  • Type: String
  • Default: none
Example Usage:

It is advised to check for the presence of the Assemblies Feature and ensure that a PortalUser is logged in before using this tag. Failing to do so will result in errors.

<#if portal.enabledFeatures?seq_contains['customAssemblies'] && user??>
  <@td.assemblies var="assemblies" />
  .
  .
  .
  <#if assemblies?size &gt; 0>
    <#list assemblies as assembly>
      <a href=<@td.assemblyViewerUrl assembly=assembly/>${assembly.name?html}</a>
    </#list>
  </#if>
</#if>

2.11.4: <@td.content>

The content tag inserts the requested content from the TD repository into the given page. If there is an error rendering the content to the page, the body of the tag will be displayed instead. If the content is empty, a boilerplate HTML snippet indicating an error is inserted.

This tag contains the following attributes:

addPageviewImage
Whether to automatically insert an HTML <img> tag after the rendered content that will request a 1x1-pixel, invisible image causing the server to record a view of this content. This will force a pageview to be recorded, even if the rendered page is served from a browser or other cache.
  • Required: false
  • Type: Boolean
  • Default: false
  • Since: 4.2
assembly
An Assembly whose content to render. One of this, 'url', or 'searchTerm' must be specified, and they are evaluated in that order.
  • Required: false
  • Type: Assembly
  • Default: None
assemblyTopicRef
If an assembly is specified, this is the ID of the topic within that assembly to render. If an assembly is specified without this attribute, the assembly map itself is rendered. this is specified without an assembly, it is ignored.
  • Required: false
  • Type: String
  • Default: None
contextKey
The context key. Overridden by contextUrl and/or contextUrlString and/or the context key specified in url/urlString, in that order.
  • Required: false
  • Type: String
  • Default: none
contextUrl
The ContentLocator of the context from which to load the data. Used to configure hyperlinks and image references in the resulting content.
contextUrlString
The ContentLocator of the context from which to load the data. Used to configure hyperlinks and image references in the resulting content.
  • Required: false
  • Type: String
  • Default: none
expandReferences
Whether to expand references to child documents. This is used to render all topics in a DITA Map-based publication on a single page.
  • Required: false
  • Type: boolean
  • Default: false
ignoreCache
Whether to go to the database, ignoring caches. Note that this will flush the requested data from the cache and re-cache the newly loaded version. Default is false.
  • Required: false
  • Type: boolean
  • Default: none
itemKey
The item key. Overridden by url and/or urlString.
  • Required: false
  • Type: String
  • Default: none
linkContext
This attribute is deprecated and can be ignored.
  • Required: false
  • Type: Boolean
  • Default: none
pipeline
Post-transform content processing can be accomplished with built-in content processing pipelines. Currently the only supported pipeline is "filterchain:/harp/portal/markFilteredTopics.xml", which can mark or remove links to topics not available in the portal, and requires pipelineParameters "portal" (PortalIdentity), "contextKey" (ItemIdentifier), and "removeFilteredEntries", a boolean controlling whether to remove or simply mark links to filtered content.
  • Required: false
  • Type: String
  • Default: None
projectKey
The Project key. Overridden by url and/or urlString.
  • Required: false
  • Type: String
  • Default: none
recordPageview
Whether to record a pageview for the given content in the reporting system. This should only be true when viewing content in a custom portal page. The built-in content viewing pages all record the pageview before the response is rendered. But when rendering content on a custom portal page, this should be specified as true to ensure that the content view is recorded.
  • Required: false
  • Type: boolean
  • Default: false
searchTerm
A search term whose first result will be the SearchResultDocument rendered by this tag. Overrides url and contextUrl attributes. Required if url attribute not provided. Can be combined with metadata specified in metadataFilterTags.
  • Required: false
  • Type: String
  • Default: none
timeout
The amount of time, in ms, after which the transform may be cancelled. The default value is generally 60000 (60 seconds), though this can vary depending on the application's configuration. Set to 0 to disable timeouts, but do this with care; infinitely-running transforms can severely degrade application performance site-wide.
  • Required: false
  • Type: long
  • Default: 60000
  • Since: 4.2
url
The ContentLocator object describing the content to retrieve. Required if searchTerm attribute is not provided.
urlString
The ContentLocator object describing the content to retrieve. Required if searchTerm attribute is not provided.
  • Required: false
  • Type: String
  • Default: none
xsl
Relative path to an XSLT file, relative to the WEB-INF directory of the web application, to apply to the content. Parameters to the XSLT can be passed using nested xslParam tags.
  • Required: false
  • Type: String
  • Default: none
Rendering a DITA Map as One Page

The expandReferences attribute can be used when referencing DITA maps to insert an XML document containing all of the topics in the publication, instead of the flattened DITA map markup. The XML structure for this document is similar, but not quite the same, as the structure used by the DITA Open Toolkit when rendering PDF output.

  • The root element is the map's root element.
  • The first child of the root element is <opentopic:map xmlns:opentopic="http://www.idiominc.com/opentopic">.
    • This structure contains the original, unexpanded map structure, including the map title element.
    • On each topicref, the ID of the corresponding topic element is in the puckdita:cxt-topicId attribute (xmlns:puckdita="http://www.titaniasoftware.com/namespace/puck-saxfilter-dita").
    • The metadata, including navtitle, for each topicref will have been resolved.
  • After the <opentopic:map> element, all of the topics referenced by the map flow, nested as specified by the map. Navtitle-only topicrefs are rendered as topics with the appropriate title. The id attribute maps to the puckdita:cxt-topicId attribute for its topicref in the <opentopic:map> section.
  • The @href attribute on all scope="local" format="dita" links and cross-references will have already been converted into IDREF-style links (e.g. href="#targetId") that will resolve in the flattened document.
  • Related links, including those generated from reltables and the map structure, will be included in the topics. To avoid displaying them they must be excluded by the stylesheet. You can differentiate authored links from generated links using the puckdita:linkSource attribute. If the attribute is not present, then the link was manually authored. Otherwise:
    "structure"
    The link is derived from the map structure.
    "reltable"
    The link was generated from a reltable.
Viewing an XML Document as Transformed HTML
<@td.content url=itemUrl
       xsl="/topic/topic.xsl">
   <@td.xslParam name="GENERATE-TASK-LABELS" value=true/>
   <@td.xslParam name="TASK-LABEL-TAG" value="h4"/>
</@td.content>

This tag will render the content identified by the ContentLocator object itemUrl using the XSLT transformation in the portal theme at /xsl/topic/topic.xsl. It will pass two parameters to the XSLT, GENERATE-TASK-LABELS=true and TASK-LABEL-TAG=h4.

Viewing a Contextualized Topic
<@td.content url=itemUrl
    contextUrl=contextUrl
    xsl="/topic/topic.xsl"/>

This loads the topic identified by the ContentLocator itemUrl, as referenced by the DITA map identified by contextUrl.

Rendering Content Based on Search
<@td.content searchTerm="serialno:12345"
    xsl="/topic/topic.xsl"/>

This will render the first document matching the given search.

Rendering an Assembly
<@td.content assembly=assemblyObj
    xsl="/map/map.xsl"/>

This will render the Assembly object give by assemblyObj.

Rendering an Assembly Entry
<@td.content assembly=assemblyObj
    assemblyTopicRef=refId
    xsl="/topic/topic.xsl"/>

This will render a topic within Assembly object give by assemblyObj and refId, the identifier of the topic within the assembly.

Rendering All Topics in a DITA Map
<@td.content url="itemUrl" xsl="/map/monolith.xsl"
  expandReferences=true/>

This will use a version of the specified DITA map (or assembly) that is similar to, but not the same as, the monolithic XML structure used by the DITA Open Toolkit for PDFs.

Monolithic DITA Map XML Structure

Here is an example of what a DITA map rendered with expandReferences=true might look like.

<map xmlns:puckdita="http://www.titaniasoftware.com/namespace/puck-saxfilter-dita"
    xmlns:opentopic="http://www.idiominc.com/opentopic">
  <opentopic:map>
    <title>Map Title</title>
    <topicref puckdita:cxt-topicId="topic1">
      <topicmeta>
        <navtitle>Topic One</navtitle>
      </topicmeta>
      <topicref puckdita:cxt-topicId="topic2">
        <topicmeta>
          <navtitle>Topic Two</navtitle>
        </topicmeta>
      </topicref>
    </topicref>
    <topicref puckdita:cxt-topicId="topic3">
      <topicmeta>
        <navtitle>Topic Three</navtitle>
      </topicmeta>
    </topicref>
  </opentopic:map>
  <topic id="topic1">
    <title>Topic One</title>
    <!-- ... -->
    <topic id="topic2">
      <title>Topic Two</title>
      <body>
        <p><xref href="#topic3">Topic 3</xref></p>
      </body>
    </topic>
  </topic>
  <topic id="topic3">
    <title>Topic Three</title>
    <!-- ... -->
  </topic>
</map>

2.11.5: <@td.fileProperties>

The fileProperties tag stores an ItemDataAccess object in @var that can be used to interrogate the properties of a file. If the file does not exist or is not accessible from the portal, no value is stored in the variable.

This tag contains the following attributes:

harpUrl
The ContentLocator. If specified, projectKey and itemKey are ignored.
harpUrlString
The ContentLocator as a string. If specified, projectKey and itemKey are ignored.
  • Required: false
  • Type: String
  • Default: none
itemKey
The item key
  • Required: false
  • Type: String
  • Default: none
projectKey
The Project key
  • Required: false
  • Type: String
  • Default: none
scope
The scope of the @var attribute. Valid values are "page" and "request".
  • Required: false
  • Type: String
  • Default: "request"
searchResult
The SearchResultDocument object. If specified, projectKey, portalKey, and harpUrl are ignored.
var
The name of the variable used to access the result.
  • Required: true
  • Type: String
  • Default: none
Example Usage:
<#if params['projectKey']?? && params['itemKey']??>
  <@td.fileProperties 
       var="itemData" 
       projectKey="${params['projectKey']}" 
       itemKey="${params['itemKey']}"/>
</#if>

<#if itemData??>
  <h1>${itemData.properties.specifiedDetails.title?html}</h1>
<#else>
  <h1>No such file.</h1>
</#if>

2.11.6: <@td.groupSearch>

The groupSearch tag executes a search over the contextualized content in the portal (i.e. DITA topics used by maps) as well as top-level publications, grouping the results by their context and storing the results in a variable. The result placed into @var is a SearchResultsPage. Note that the prevailing metadata search values (from the search request URL parameters) will be appended to the search query specified in the query attribute. This may lead to unexpected results if the tag is invoked in various search results contexts.

This tag contains the following attributes:

cacheKey
Unique identifier for this tag. If specified and cacheSecs is positive, this will serve as the cache key. Otherwise the relevant attributes will be used and all tags with the same attribute values will share the cache.
  • Required: false
  • Type: String
  • Default: none
cacheSecs
The number of seconds to cache the results. All calls to this tag with the same attributes will receive the cached value for the specified length of time unless the cacheKey attribute is specified, in which case only tags with the same ID will be affected by the caching. Specify 0 or a negative number to disable caching.
  • Required: false
  • Type: int
  • Default: 0
createdBefore
A Date value specifying the last creation date for the search. Only files created since the given date will be returned. Values must be java.util.Date objects. Dates can be converted to Strings in Freemarker templates using the ?date, ?datetime, and ?time built-ins for Strings. See http://freemarker.org/docs/ref_builtins_string.html#ref_builtin_string_date for details.
  • Required: false
  • Type: Date
  • Default: none
createdSince
A Date value specifying the earliest creation date for the search. Only files created since the given date will be returned. Values must be java.util.Date objects. Dates can be converted to Strings in Freemarker templates using the ?date, ?datetime, and ?time built-ins for Strings. See http://freemarker.org/docs/ref_builtins_string.html#ref_builtin_string_date for details.
  • Required: false
  • Type: Date
  • Default: none
createdSinceDays
Only return results created in the preceding number of days specified by this attribute.
  • Required: false
  • Type: int
  • Default: none
escape
Whether to escape characters in the query so that it operates as a true full-text search rather than a more formal search engine query. When set to 'true', colons, parentheses, and other special characters used to structure queries will be escaped and treated as plain text. If false, the query will be treated as a formal search engine query, and syntax errors will cause failures. The default is false.
  • Required: false
  • Type: String
  • Default: false
  • Since: 4.2
facets
A space separated list of search facets. Facets do not affect search results.
  • Required: false
  • Type: Object
  • Default: null
  • Since: 4.2
filter
A search query to be used as a search filter. The search filter is applied as a secondary search on primary search results.
  • Required: false
  • Type: String
  • Default: null
  • Since: 4.2
groupBy
The property to group results by. This can either be a base property of SearchResultDocument or a metadata field. If a metadata field, the name should end in "_md". For instance, to group by "product" metadata, specify "product_md". The default value is "itemKey", meaning that different uses of the same DITA topic in different DITA maps will be grouped together.
  • Required: false
  • Type: String
  • Default: "itemKey"
  • Since: 4.2.1
highlightSize
The total number of characters before and after the given search term in a result. Note that if the search term is "*" then the highlight size will be 0. Must be an integer n 0 <= n <= 2147483647.
  • Required: false
  • Type: int
  • Default: 300
limit
The maximum number of results per page. Must be a positive integer n where 0 < n <= 2147483647. This does not limit the total number of search results, which may be limited by other settings.
  • Required: false
  • Type: int
  • Default: 10
maxFacetValues
The maximum number of facet values to return when executing the search. The resulting facets will be listed from most-hits to least-hits. Note that this can have a significant impact on search performance.
  • Required: false
  • Type: int
  • Default: 10
metadataFields
Space-separated list of metadata fields to return with each search result. If the list is empty, all default metadata fields will be returned. Otherwise, only the listed fields will be returned. An asterisk ("*") may be used in any term as a wildcard. Use just "*" to return all metadata fields (both default and custom).
  • Required: false
  • Type: String
  • Default: null
modifiedBefore
A Date value specifying the last modified date for the search. Only files modified since the given date will be returned. Values must be java.util.Date objects. Dates can be converted to Strings in Freemarker templates using the ?date, ?datetime, and ?time built-ins for Strings. See http://freemarker.org/docs/ref_builtins_string.html#ref_builtin_string_date for details.
  • Required: false
  • Type: Date
  • Default: none
modifiedSince
A Date value specifying the earliest modified date for the search. Only files modified since the given date will be returned. Values must be java.util.Date objects. Dates can be converted to Strings in Freemarker templates using the ?date, ?datetime, and ?time built-ins for Strings. See http://freemarker.org/docs/ref_builtins_string.html#ref_builtin_string_date for details.
  • Required: false
  • Type: Date
  • Default: none
modifiedSinceDays
Only return results modified in the preceding number of days specified by this attribute.
  • Required: false
  • Type: int
  • Default: none
portalContent
Whether and how to include portal content in search. Valid values are "exclude", "include", and "only".
  • Required: false
  • Type: String
  • Default: exclude
  • Since: 4.2
query
The search query. Must not be the empty string ("").
  • Required: false
  • Type: String
  • Default: *
resultsPerGroup
The maximum number of "contexts" to return per group.
  • Required: false
  • Type: int
  • Default: 5
scope
The scope of the @var attribute. Valid values are "page" and "request".
  • Required: false
  • Type: String
  • Default: "request"
sortBy
The property or properties to sort by, separated by whitespace or comma.
  • Required: false
  • Type: String
  • Default: none
sortDirection
A list of sort directions ('ASC' or 'DESC'), corresponding in order to the list of 'sortBy' fields. If the list is empty or shorter than the 'sortBy' list, the default of 'ASC' will be used for the missing directions.
  • Required: false
  • Type: String
  • Default: ASC
startAfter
Alternative pagination mode. The startNextAfter token from a previous page.
  • Required: false
  • Type: Object
  • Default: null
startPage
The page number to return. Must be an integer 0 <= n <= 10000. Note that ithe preferred pagination method is to use the startAfter attribute.
  • Required: false
  • Type: int
  • Default: 0
var
The name of the variable used to access the result.
  • Required: true
  • Type: String
  • Default: none
Example usage:

When content is written in DITA, search results are "grouped" by topic, with each individual search result within that group representing the topic as it appears in a given context. To facilitate this, the Group Search tag differs from the Search tag in the following ways.

  • It takes one additional optional attribute, resultsPerGroup that specifies the maximum number of "contexts" to return per group.
  • The data placed into @var is an instance of SearchResultsPage instead of a list of SearchResultDocuments.

The body of this tag can contain any number of <@td.metadataFilter> tags.

<@td.groupSearch var="results" query="Titania" />

<ul>
  <#list results.content as group>
    <li>${group.firstResult.title?html}</li>
    <ul>
      <#list group.results as result>
        <li><a href=<@td.viewerUrl searchResult=result />>${result.contextName}</a></li>
      </#list>
    </ul>
  </#list>
</ul>
Note: Non-DITA content and DITA topics not referenced by any DITA map will only have a single entry in result.content.results which will be identical to result.content.firstResult. Additionally, Portal Theme developers may want to create custom viewer pages for non-DITA content types.

2.11.7: <@td.httpRequest>

The httpRequest tag executes an http request and sets the given variable to the response body returned by the request. Added in version 4.2.

This tag contains the following attributes:

contentType
The request body content type. (Equivalent to setting "Content-Type" header.)
  • Required: false
  • Type: String
  • Default: If sending a file, the content type of the file. Otherwise, none.
contextId
Context ID to preserve request context, such as cookies and authentication status, across several requests.
  • Required: false
  • Type: String
  • Default: none
contextScope
The scope, for the context identified by the contextId to be retained. Possible values are "session" (the default), in which case the context persists for the duration of the user's HTTP session; "request", in which case the context expires at the end of the current (Titania Delivery) request; or "global", in which case the context is shared by all Titania Delivery users.
  • Required: false
  • Type: String
  • Default: session
defaultCharset
The default character set to use when interpreting the request entity, unless otherwise specified by the Content-Type header.
  • Required: false
  • Type: String
  • Default: UTF-8
followRedirects
Maximum number of redirects to follow. Negative means unlimited.
  • Required: false
  • Type: int
  • Default: -1
headers
The HTTP headers to set on the request.
  • Required: false
  • Type: Map
  • Default: none
itemKey
If sending the contents of a file, the item key of the file.
  • Required: false
  • Type: String
  • Default: None
method
The HTTP request method.
  • Required: false
  • Type: String
  • Default: GET
parameters
Additional parameters to set on the request. If the method is POST and there is no tag body, the parameters will be put into an application/x-www-form-urlencoded request entity. Otherwise, the parameters will be added to the request URL.
  • Required: false
  • Type: Map
  • Default: none
password
Password for HTTP authentication.
  • Required: false
  • Type: String
  • Default: none
projectKey
If sending the contents of a file, the project key of the file.
  • Required: false
  • Type: String
  • Default: None
rendition
If sending the contents of a file, the ItemRepresentationType of the rendition to send.
  • Required: false
  • Type: String
  • Default: ORIGINAL
scope
The scope of the @var attribute. Valid values are "page" and "request".
  • Required: false
  • Type: String
  • Default: "request"
timeout
Timeout value in seconds.
  • Required: false
  • Type: int
  • Default: 20
url
The URL to request.
  • Required: true
  • Type: String
  • Default: none
username
User name for HTTP authentication.
  • Required: false
  • Type: String
  • Default: none
var
The name of the variable used to access the result.
  • Required: false
  • Type: String
  • Default: none
writeResponse
Whether to write response body to tag output.
  • Required: false
  • Type: boolean
  • Default: false

2.11.8: <@td.markdown>

The markdown tag treats the contents of the tag as Markdown syntax to be rendered as HTML.

This tag has no attributes.

Markdown is a tool that can convert text to HTML but that uses a much simpler and more human-readable syntax than HTML.

Example Usage
<@td.markdown>#Hello, Titania!<@td.markdown>

The above would resolve to the following HTML

<h1>Hello, Titania!</h1>

2.11.9: <@td.metadataFilter>

The metadataFilter tag allows for adding filters to <@td.search>, <@td.groupSearch>, and <@td.searchResultUrl> tags.

This tag contains the following attributes:

metadataName
The name of the filter
  • Required: true
  • Type: String
  • Default: none
metadataValue
The value of the filter
  • Required: true
  • Type: String
  • Default: none
Example Usage:
<@td.search var="results" query="Titania" >
  <@td.metadataFilter name="foo" value="bar" />
  <@td.metadataFilter name="baz" value="qux" />
</@td.pageUrl>

The above code would place an array of SearchResultDocuments into results that match the query Titania and that have a metadata rule of foo with a value of bar, and another metadata rule of baz with a value of qux.

2.11.10: <@td.pageUrl>

The pageUrl tag computes the URL to a custom page in the portal's theme.

This tag contains the following attributes:

page
The path to the custom page, relative to the /pages/custom folder in the portal theme.
  • Required: true
  • Type: String
  • Default: none
scope
The scope of the @var attribute. Valid values are "page" and "request".
  • Required: false
  • Type: String
  • Default: "request"
urlQuery
The query string. This value will NOT be URL-encoded, so it should be encoded already. You can also use nested <@td.urlParam> tags to specify individual URL parameters in a URL-escaped way.
  • Required: false
  • Type: String
  • Default: none
var
The name of the variable used to access the result.
  • Required: false
  • Type: String
  • Default: none
Example usage:

Assuming the existence of a /pages/custom/demo.ftl:

<@td.pageUrl page="demo" />

would insert the url to the page created by the demo.ftl template into the DOM. It is most useful as the href attribute to an anchor tag.

<a href=<@td.pageUrl page="demo.ftl" />>Demo Page</a>

Alternatively, the url could be stored in a variable and referenced later in the page:

<@td.pageUrl var=url page="demo.ftl" />
.
.
.
<a href=${url}>Demo Page</a>

The value of the @page attribute must be a template file relative to /pages/custom/.

2.11.11: <@td.pipelineParam>

The pipelineParam tag is used exclusively inside content tags to specify pipeline parameters. The Content tag displays content after being processed through any number of processing pipelines. This tag allows for passing parameters to those pipelines.
Note: The pipeline currently supported is filterchain:/harp/portal/markFilteredTopics.xml, which can mark or remove links to topics not available in the portal. See the pipeline attribute for <@td.content>.

This tag contains the following attributes:

name
The parameter name.
  • Required: true
  • Type: String
  • Default: none
value
The parameter value.
  • Required: true
  • Type: Object
  • Default: none

2.11.12: <@td.portalUrl>

The portalUrl tag provides the URL of a page relative to the root of the Portal website. If the @var attribute is specified, the URL is stored in the given variable, otherwise it is written to the page.

This tag contains the following attributes:

scope
The scope of the @var attribute. Valid values are "page" and "request".
  • Required: false
  • Type: String
  • Default: "request"
urlQuery
The query string. This value will NOT be URL-encoded, so it should be encoded already. You can also use nested <@td.urlParam> tags to specify individual URL parameters in a URL-escaped way.
  • Required: false
  • Type: String
  • Default: none
value
The path within the Portal UI. If omitted, the Portal homepage URL will be used.
  • Required: false
  • Type: String
  • Default: The url to the Portal home page.
var
The name of the variable used to access the result.
  • Required: false
  • Type: String
  • Default: none
Example Usage:
<@td.portalUrl var="url" value="search" urlQuery="term=Titania" />
.
.
.
<a href=${url}>Search for "Titania"</a>
   

The above code snippet would generate a link pointing to the Search page with a query of Titania.

2.11.13: <@td.recordPageview>

The recordPageview tag inserts an invisible HTML <img> element that results in a 1x1 pixel, transparent graphic. The request of this graphic will cause the server to record a pageview of the document described by the tag's attributes. This enables pageviews to be recorded even when the page is served from a browser cache or other cache. If the content whose pageview is being recorded is subsequently requested separately, e.g. a graphic, IFRAME, or multimedia object, add ?recordPageview=false to the viewer URL to avoid double-counting the view. Added in version 4.2.

This tag contains the following attributes:

contextKey
The context key. Overridden by contextUrl and/or contextUrlString and/or the context key specified in url/urlString, in that order.
  • Required: false
  • Type: String
  • Default: none
contextUrl
The ContentLocator of the context from which to load the data. Used to configure hyperlinks and image references in the resulting content.
contextUrlString
The ContentLocator of the context from which to load the data. Used to configure hyperlinks and image references in the resulting content.
  • Required: false
  • Type: String
  • Default: none
itemKey
The item key. Overridden by url and/or urlString.
  • Required: false
  • Type: String
  • Default: none
projectKey
The Project key. Overridden by url and/or urlString.
  • Required: false
  • Type: String
  • Default: none
refId
The reference ID, in cases of a view of content in the context of another document, such as a top-level XML document or DITA map.
  • Required: false
  • Type: String
  • Default: none
url
The ContentLocator object describing the content to retrieve. Required if searchTerm attribute is not provided.
urlString
The ContentLocator object describing the content to retrieve. Required if searchTerm attribute is not provided.
  • Required: false
  • Type: String
  • Default: none

2.11.14: <@td.requestPart>

The requestPart tag is formatted as a multipart/form part of a request body. Added in version 4.2.

This tag contains the following attributes:

contentType
The part content type. (Equivalent to setting "Content-Type" header.)
  • Required: false
  • Type: String
  • Default: If sending a file, the content type of the file. Otherwise, none.
filename
The filename for this part.
  • Required: false
  • Type: String
  • Default: If uploading a file from a project, the name of the file. Otherwise, no default.
itemKey
If sending the contents of a file, the item key of the file.
  • Required: false
  • Type: String
  • Default: None
name
The name for this part.
  • Required: false
  • Type: String
  • Default: file
projectKey
If sending the contents of a file, the project key of the file.
  • Required: false
  • Type: String
  • Default: None
rendition
If sending the contents of a file, the ItemRepresentationType of the rendition to send.
  • Required: false
  • Type: String
  • Default: ORIGINAL

2.11.15: <@td.search>

The search tag executes a search with the given parameters and adds the results to the model available to the current template. The result is an array of DocumentSearchInfo objects. Note that the prevailing metadata search values (from the search request URL parameters) will be appended to the search query specified in the query attribute. This may lead to unexpected results if the tag is invoked in various search results contexts.

This tag contains the following attributes:

cacheKey
Unique identifier for this tag. If specified and cacheSecs is positive, this will serve as the cache key. Otherwise the relevant attributes will be used and all tags with the same attribute values will share the cache.
  • Required: false
  • Type: String
  • Default: none
cacheSecs
The number of seconds to cache the results. All calls to this tag with the same attributes will receive the cached value for the specified length of time unless the cacheKey attribute is specified, in which case only tags with the same ID will be affected by the caching. Specify 0 or a negative number to disable caching.
  • Required: false
  • Type: int
  • Default: 0
createdBefore
A Date value specifying the last creation date for the search. Only files created since the given date will be returned. Values must be java.util.Date objects. Dates can be converted to Strings in Freemarker templates using the ?date, ?datetime, and ?time built-ins for Strings. See http://freemarker.org/docs/ref_builtins_string.html#ref_builtin_string_date for details.
  • Required: false
  • Type: Date
  • Default: none
createdSince
A Date value specifying the earliest creation date for the search. Only files created since the given date will be returned. Values must be java.util.Date objects. Dates can be converted to Strings in Freemarker templates using the ?date, ?datetime, and ?time built-ins for Strings. See http://freemarker.org/docs/ref_builtins_string.html#ref_builtin_string_date for details.
  • Required: false
  • Type: Date
  • Default: none
createdSinceDays
Only return results created in the preceding number of days specified by this attribute.
  • Required: false
  • Type: int
  • Default: none
escape
Whether to escape characters in the query so that it operates as a true full-text search rather than a more formal search engine query. When set to 'true', colons, parentheses, and other special characters used to structure queries will be escaped and treated as plain text. If false, the query will be treated as a formal search engine query, and syntax errors will cause failures. The default is false.
  • Required: false
  • Type: String
  • Default: false
  • Since: 4.2
facets
A space separated list of search facets. Facets do not affect search results.
  • Required: false
  • Type: Object
  • Default: null
  • Since: 4.2
filter
A search query to be used as a search filter. The search filter is applied as a secondary search on primary search results.
  • Required: false
  • Type: String
  • Default: null
  • Since: 4.2
highlightSize
The total number of characters before and after the given search term in a result. Note that if the search term is "*", then the highlight size will be 0. Must be an integer n 0 <= n <= 2147483647.
  • Required: false
  • Type: int
  • Default: 300
limit
The maximum number of results per page. Must be a positive integer n where 0 < n <= 2147483647. This does not limit the total number of search results, which may be limited by other settings.
  • Required: false
  • Type: int
  • Default: 10
maxFacetValues
The maximum number of facet values to return when executing the search. The resulting facets will be listed from most-hits to least-hits. Note that this can have a significant impact on search performance.
  • Required: false
  • Type: int
  • Default: 10
metadataFields
Space-separated list of metadata fields to return with each search result. If the list is empty, all default metadata fields will be returned. Otherwise, only the listed fields will be returned. An asterisk ("*") may be used in any term as a wildcard. Use just "*" to return all metadata fields (both default and custom).
  • Required: false
  • Type: String
  • Default: null
modifiedBefore
A Date value specifying the last modified date for the search. Only files modified since the given date will be returned. Values must be java.util.Date objects. Dates can be converted to Strings in Freemarker templates using the ?date, ?datetime, and ?time built-ins for Strings. See http://freemarker.org/docs/ref_builtins_string.html#ref_builtin_string_date for details.
  • Required: false
  • Type: Date
  • Default: none
modifiedSince
A Date value specifying the earliest modified date for the search. Only files modified since the given date will be returned. Values must be java.util.Date objects. Dates can be converted to Strings in Freemarker templates using the ?date, ?datetime, and ?time built-ins for Strings. See http://freemarker.org/docs/ref_builtins_string.html#ref_builtin_string_date for details.
  • Required: false
  • Type: Date
  • Default: none
modifiedSinceDays
Only return results modified in the preceding number of days specified by this attribute.
  • Required: false
  • Type: int
  • Default: none
portalContent
Whether and how to include portal content in search. Valid values are "exclude", "include", and "only".
  • Required: false
  • Type: String
  • Default: exclude
  • Since: 4.2
query
The search query. Must not be the empty string ("").
  • Required: false
  • Type: String
  • Default: *
scope
The scope of the @var attribute. Valid values are "page" and "request".
  • Required: false
  • Type: String
  • Default: "request"
sortBy
The property or properties to sort by, separated by whitespace or comma.
  • Required: false
  • Type: String
  • Default: none
sortDirection
A list of sort directions ('ASC' or 'DESC'), corresponding in order to the list of 'sortBy' fields. If the list is empty or shorter than the 'sortBy' list, the default of 'ASC' will be used for the missing directions.
  • Required: false
  • Type: String
  • Default: ASC
startAfter
Alternative pagination mode. The startNextAfter token from a previous page.
  • Required: false
  • Type: Object
  • Default: null
startPage
The page number to return. Must be an integer 0 <= n <= 10000. Note that ithe preferred pagination method is to use the startAfter attribute.
  • Required: false
  • Type: int
  • Default: 0
var
The name of the variable used to access the result.
  • Required: true
  • Type: String
  • Default: none

The Search tag is similar in function to the <@td.searchResultUrl> tag. However, instead of generating a link to the search results page (/pages/searchResults.ftl) which renders the results of the given search parameters, this tag executes the search and places the results into a variable for use in the current page. This allows for adding search results from theme-defined parameters into any page.

Example Usage:
<@td.search var="results" query="Titania" />

The above would place an array of SearchResultDocuments into results. Those results could later be iterated over with:

<#list results as result>
  <#-- Do something with the "result" variable -->
</#list>

Metadata filters could be added to the search using any number of <@td.metadataFilter> tags.

<@td.search var="results" query="Titania" >
  <@td.metadatFilter name="Product" value="Delivery" />
</@td.search>

2.11.16: <@td.searchResultUrl>

The searchResultUrl tag generates a url to a tag-defined search.

This tag contains the following attributes:

query
The query.
  • Required: false
  • Type: String
  • Default: *
scope
The scope of the @var attribute. Valid values are "page" and "request".
  • Required: false
  • Type: String
  • Default: "request"
urlQuery
The query string. This value will NOT be URL-encoded, so it should be encoded already. You can also use nested <@td.urlParam> tags to specify individual URL parameters in a URL-escaped way.
  • Required: false
  • Type: String
  • Default: none
var
The name of the variable used to access the result.
  • Required: false
  • Type: String
  • Default: none
Example Usage:

This tag provides a way to generate links to "canned" searches to direct users to certain collections of content.

<@td.searchResultUrl query="Titania" />

The above would add a link to the Search page with a query for Titania. As with the other url-generating tags, this is best used as the value to the href attribute of an anchor tag:

<a href=<@td.searchResultUrl query="Titania" />>Search for "Titania"</a>

The url could also be stored in a variable and used later in the page:

<@td.searchResultUrl var=url query="Titania" />
.
.
.
<a href=${url}>Search for "Titania"</a>

Lastly, the search can be refined by adding any number of metadataFilter tags to the body:

<@td.searchResultUrl query="Titania" >
    <@td.metadataFilter name="product" value="Delivery" />
    <@td.metadataFilter name="version" value="2.0" />
</@td.searchResultUrl>

2.11.17: <@td.setContentFilter>

This tag allows you to specify a search clause that will be used for every content query, enabling the filtering of content returned by the search engine universally. For example, a user could specify a country on the portal landing page; that country could be used to set a global filter such that only content relevant for the selected country - for example, "country_md:US" - is served for the remainder of the session. The filter can be cleared by passing an empty string to the 'filter' attribute. Added in version 4.2.1.

This tag contains the following attributes:

filter
The search clause for filtering content.
  • Required: true
  • Type: String
  • Default: None
<#-- Assuming a function "getCountry()" will return a country code -->
<#assign targetCountry=getCountry()!"US"/>
<@td.setContentFilter filter="country_md:${targetCountry}" />

The above would add a session-scoped search filter to exclude all documents without the specified "country" metadata value.

This directive should be used carefully, because it affects all content queries in the user's session, and could have the unintended consequence of restricting access to needed content items. The filter can be removed with a directive like:

<@td.setContentFilter filter=""/>

2.11.18: <@td.themeFileUrl>

The themeFileUrl tag Generates the URL to a file in the Portal Theme's "static" folder.

This tag contains the following attributes:

scope
The scope of the @var attribute. Valid values are "page" and "request".
  • Required: false
  • Type: String
  • Default: "request"
urlQuery
The query string. This value will NOT be URL-encoded, so it should be encoded already. You can also use nested <@td.urlParam> tags to specify individual URL parameters in a URL-escaped way.
  • Required: false
  • Type: String
  • Default: none
value
The path within the "static" folder.
  • Required: false
  • Type: String
  • Default: The base URL to the "static" folder
var
The name of the variable used to access the result.
  • Required: false
  • Type: String
  • Default: none
Example Usage:

Assuming the presence of /static/logo.png in the Portal Theme:

<@td.themeFileUrl value="logo.png" />

The above would add a url pointing to logo.png to the page. As with other tags, this url could be used directly as an attribute value or stored in a variable:

<img src=<@td.themeFileUrl value="logo.png" /> />
<@td.themeFileUrl var="logoUrl" value="logo.png" />
.
.
.
<img src=${logoUrl} />

The file pointed to by the @value attribute must be a file relative to the static/ directory.

2.11.19: <@td.urlParam>

The urlParam tag specifies a URL parameter that will be appended to tag-generated urls.

This tag contains the following attributes:

name
The parameter name
  • Required: true
  • Type: String
  • Default: none
value
The parameter value
  • Required: true
  • Type: String
  • Default: none

This tag is available for use inside the body of the following tags:

Example Usage:
<@td.pageUrl var="url" value="demo.ftl" >
    <@td.urlParam name="foo" value="bar" />
    <@td.urlParam name="baz" value="qux" />
</@td.pageUrl>
.
.
.
<a href=${url}>Custom Page With Params</a>

The value of url in the above example would point to the page generated by /pages/custom/demo.ftl and have a query string of ?foo=bar&baz=qux.

2.11.20: <@td.viewerUrl>

The viewerUrl tag provides the URL to a given piece of content, represented by DocumentSearchInfo object

This tag contains the following attributes:

contextualizedChild
A Contextualization object. If specified, the preview rendition of this contextualized child will be used.
portal
The PortalIdentity object. If not specified, there must be a ${portal} attribute in scope.
scope
The scope of the @var attribute. Valid values are "page" and "request".
  • Required: false
  • Type: String
  • Default: "request"
searchResult
The SearchResultDocument object. This data is placed into the model of the searchResults.ftl page and returned by <@td.Search> and <@td.groupSearch> tags. If not specified, there must be a ${doc} variable storing a SearchResultDocument object in scope.
url
The ContentLocator of the item. If context information is provided via other attributes, it will override any context information in this object.
urlQuery
The query string. This value will NOT be URL-encoded, so it should be encoded already. You can also use nested <@td.urlParam> tags to specify individual URL parameters in a URL-escaped way.
  • Required: false
  • Type: String
  • Default: none
urlString
The ContentLocator of the item. If context information is provided via other attributes, it will override any context information in this object.
  • Required: false
  • Type: String
  • Default: none
var
The name of the variable used to access the result.
  • Required: false
  • Type: String
  • Default: none

Titania Delivery determines whether mapViewer.ftl or viewer.ftl will be rendered depending on the content.

Example Usage:

Assuming that a variable to a SearchResultDocument called ${doc} is available on the current page:

<@td.viewerUrl var="url" searchResult=doc />
.
.
.
<a href=${url}>Document</a>

2.11.21: <@td.xslParam>

The xslParam tag is used inside content tags to specify XSLT parameters.

This tag contains the following attributes:

name
The parameter name.
  • Required: true
  • Type: String
  • Default: none
value
The parameter value.
  • Required: true
  • Type: Object
  • Default: none

Chapter 3: Static Assets

The static directory can contain files such as graphics, JavaScript, CSS style sheets, and any other static resources that need to be made available to a portal.

3.1: Managing Static Files for a Portal

A Titania Delivery portal's purpose is to dynamically serve up content from Projects, but every website has files related to the presentation, such as graphics, CSS files, static HTML pages, and other files. This is the purpose of the /static folder.

The files in the /static directory should not be referenced via static URL. Instead, Titania Delivery provides the <@td.themeFileUrl> tag for generating URLs for static file references.

3.2: SASS and LESS Support

Titania Delivery supports the conversion of style sheets written using the SASS or LESS CSS extension languages.

When referenced by a portal page, files with a .sass or .scss file extension will be converted to CSS using SASS rules. Files ending in .less will be compiled using LESS. This means that the browser will be served a single compiled CSS file, but portal theme developers can take advantage of the advanced features and simplicity of these pre-compiled languages. If an error occurs during stylesheet compilation, the output will be a CSS comment containing the error message.

Chapter 4: XSLT Files

The xsl directory contains the XSLT stylesheets that can be used to transform content that is referenced using the <@td.content> tag in portal theme pages.

The @xsl attribute on the <@td.content> tag specifies a path relative to the xsl directory. This attribute specifies the stylesheet that will be applied to the content.

4.1: Writing XSLT for DITA

The default XSLT stylesheets provided with the built-in portal themes are extremely small. Instead of implementing the full transform functionality, they reference a number of utility XSLT modules that do most of the work. Then, they override the behavior in those modules to do specific processing for some elements, such as notes and images.

The theme XSLT templates are applied to the FLATTENED ItemRepresentationType of topics, or to the MONOLITH rendition of maps--not to the original XML document that was uploaded to the project. The processed version will include the results of conventional DITA processing expectations such as adding the DITA @class attribute on all elements, resolving external references and content inclusions, applying any profiling filters, and adding navigation links based on the topic's map context.

4.1.1: DITA XSLT Module URIs

Titania Delivery provides a number of built-in utility modules that can greatly simplify the development of XSLT stylesheets designed to operate against DITA topics and maps.

The built-in XSLT source files may be downloaded from a Titania Delivery server at a URL like: td-server-base-url/resources/td-xsl-lib.zip. An XSLT developer may override any of these templates in a portal theme.

urn:titania:dita-xsl:topic-html-fragment.xsl
This module will convert a DITA topic into basic HTML5 markup, without wrapping <html> or <body> elements.
urn:titania:dita-xsl:map-html-fragment.xsl
This module will convert a DITA map into basic HTML5 markup, without wrapping <html> or <body> elements. The root element is a <div class="map"> element containing <ul id="toc"> with the structure of the map.

4.1.2: Writing Specialization-Aware XSLT

When developing XSLT for DITA content, it's important to take specialization into account.

Specialization in DITA is implemented using the special format of the @class attribute. When writing a template, the @match attribute should not use a tag name.

<!-- DO NOT match on DITA element name! -->
<xsl:template match="note"><!-- bad practice -->

Instead, match on the @class attribute.

<!-- This is OK, but verbose. It will match on "note" elements and all specializations therefrom -->
<xsl:template match="*[contains(@class, ' topic/note ')]">

Titania Delivery provides a utility entity file that contains entity declarations for all of the base DITA elements mapping to the @class-qualified form, in a way that is easier to read. This allows the above to be rewritten as:

<xsl:template match="&note;"/>

For every element in the base DITA vocabulary, the entity file referenced above contains entries similar to the following:

<!ENTITY note-condition "contains(@class, ' topic/note ')">
<!ENTITY note "*[&note-condition;]">

To use these entities, link in the entity file using the public ID -//Titania//ENTITIES DITA XPath Definitions//EN.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [
<!ENTITY % tags PUBLIC "-//Titania//ENTITIES DITA XPath Definitions//EN" "dita-xpaths.ent">
%tags;
]>
<xsl:stylesheet version = "2.0" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform" >
  <xsl:import href= "urn:titania:dita-xsl:topic-html-fragment.xsl" />
  <xsl:template match="&note;" mode="html #default">
    <!-- Custom note handling -->
  </xsl:template>
</xsl:stylesheet>

4.1.3: DITA XSLT Conventions

The built-in DITA XSLT modules follow a number of common conventions.
  • The @class attribute in HTML output elements is comprised of:
    • Every tag name in the DITA specialization hierarchy for the source element.
    • The value of the @outputclass attribute, if any.
    For example, since <b> is a specialization of <ph>, <b outputclass="superBold"> would become <b class="ph b superBold">. This allows you to do most of your DITA styling using CSS instead of XSLT.
  • For the most part, the templates are configured in both the #default and html modes. The exception is <map> and <topicref> processing, which is generally processed using mode="toc", switching for mode="html" for visible content, like <navtitle> content.
  • Conditional processing attributes - @product, @platform, @audience, @otherprops, and any @props specializations identified in the @domains attribute of the root element - will be preserved in HTML output, prefixed with @data- (for example, @data-audience).
  • All templates in these modules have a negative @priority, so that they can be easily overridden by stylesheets that reference them.
  • @id attributes are converted into named anchors of the form <a name="{@id}" class="xmlId"/>
  • The @xtrc and @xtrf attributes, generated during processing to identify the source of a given XML element, are represented as @data-xtrc and @data-xtrf in the HTML output.
    Note: The Comment Manager uses @data-xtrc and @data-xtrf to trace comment locations back to source XML.
  • @xml:lang attributes are converted to @lang, and @dir is preserved.

4.1.4: Customizing the Built-In DITA XSLT Output

The included DITA stylesheet modules follow some conventions that enable simple, straghtforward customization.

The built-in DITA XSLT stylesheet uses a common pattern for all elements, enabling simple customization using custom template modes.

html-tag-name
Use this mode to specify the HTML element name.
<xsl:template match="&topic;/&topic;//&topic;/&title;" mode="html-tag-name">
  <xsl:text>h4</xsl:text>
</xsl:template>
html-atts
Use this mode to specify additional attributes to be placed on the HTML element for the tag.
<xsl:template match="&note;" mode="html-atts">
  <xsl:attribute name="data-note-type" select="@type"/>
</xsl:template>
output-class
Use this mode to contribute tokens to the HTML @class attribute on the generated HTML element. It's generally a good idea to end such a template with <xsl:next-match/> to ensure all contributions to @class are processed.
<xsl:template match="&tm;" mode="output-class">
  <xsl:value-of select="@tmtype"/>
  <xsl:next-match/>
</xsl:template>
Note: By default, all element names in the specialization hierarchy are automatically included in the HTML @class attribute. For example, the @class attribute for <xmlelement>, which is specialized from <markupname> and <keyword>, is class="keyword markupname xmlelement". In addition, the @outputclass values also appended to the HTML @class.
gentext-before-outer
gentext-before-inner
gentext-after-inner
gentext-after-outer
These modes enable you to easily insert content before and/or after an element's content. The inner variants will place the generated code inside the wrapper element; the outer variations, outside the wrapping elements. When using this mode, it's best practice to end with <xsl:next-match/> to ensure that all generated text is processed.
<xsl:template match="&q;" mode="gentext-before-inner gentext-after-inner">
  <xsl:text>"</xsl:text>
  <xsl:next-match/>
</xsl:template>

4.1.5: Default DITA Element Mappings

The included XSLT modules group tags into a number of categories for transforming into HTML.

By default, all DITA elements are converted into <div> tags, unless otherwise specified below.

HTML Equivalents
The following elements and their specializations are converted directly into their HTML equivalents.
  • <p>
  • <b>
  • <i>
  • <u>
  • <sup>
  • <sub>
  • <dl>
  • <ul>
  • <ol>
  • <li>
  • <pre>
  • <object>
  • <param>
  • <dt>
  • <dd>
  • <q>
Simple Inline Elements
The following elements and their specializations are converted into simple <span> tags.
  • <ph>
  • <keyword>
  • <cite>
  • <term>
  • Children of <personname>
  • Children of <locality>
  • <foreign>
  • <unknown>
Inline Code Elements
The following elements and their specializations are converted to <code> tags.
  • <filepath>
  • <codeph>
  • <cmdname>
  • <apiname>
Deleted Tags
The following elements and their contents are not represented in the output HTML at all by default using these modules.
  • <abbreviated-form>
  • <fn>
  • <data>
  • <data-about>
  • <indexterm>
  • <reltable>
  • <titlealts>
  • <prolog>

4.1.6: DITA Elements with Special Processing

A few DITA elements are given special processing instead of simple element mappings.
<image>
Converted into an <img> tag, setting @width and @height values, if any, using @style, and representing any <alt> tag in both the @alt and @title attributes. The @href attribute becomes @src, but is otherwise untouched; link and graphic references are updated to web URLs automatically after the XSLT runs.
<xref>

Converted into <a> tags. The <desc> element, if present, is converted into @title attribute. The @href attribute is untouched; link and graphic references are updated to web URLs automatically after the XSLT runs. However, you can customize this behavior by overriding the named get-xref-href template.

Processing of <xref> also calls the named extra-xref-attrs template. By default, this sets target="_blank" on links whose @scope is not "local".

<note>
  • The @type attribute value is included in the output @class attribute.
  • A <h4 class="noteHeader"> element containing the @type value is prepended to the note contents.
<table> and <simpletable>
The table structures represented by both <simpletable> and <table> are converted to the HTML table model.
<title>
  • Top-level map and topic titles become <h1>.
  • Second-level topic titles become <h2>.
  • All other topic titles become <h3>.
  • Section and example titles also become <h3>.
  • All other titles become <h4>.
<dl>
Definition lists are converted into simple HTML <dl> tags; <dlhead> elements are removed.
<dlentry>
These are removed unless they contain profiling attributes or attributes from the DITA metadata attribute group, in which case it will be converted into a <div> to carry the HTML versions of those attributes. (This is not valid HTML5, but HTML5 does not provide a valid way to wrap <dt> and <dd> tags, and the use of <div> renders invisibly on all modern browsers.)
<xmlelement>
Wrapped in angle brackets (<>).
<xmlatt>
Prefixed with "@".
<tm>
Appends the appropriate symbol (as specified by @tmtype) to the content. In addition, the @tmtype, @trademark, @tmowner, and @tmclass attributes are converted into HTML data- attributes.
<menucascade>

The children of this element are joined together using a joining character that can be controlled using the $menucascade-separator parameter ( ⇒ by default).

<object data="*.mp4">
When a <object> tag references an MPEG-4 video (as identified by the extension of the @data attribute), it is converted into an HTML5 <video> tag.
<lines>
This becomes <pre class="lines"> instead of a <div>.
<sl> and <sli>
These are converted into <ul> and <li>, respectively.

4.1.7: Related Link Handling

The built-in XSLT modules use named templates to help group and sort the related links in a topic.

Following standard DITA rules, the Titania Delivery DITA processing engine will insert the appropriate related links based on the map structure and reltables, as well as preserving any manually-authored links, all with the appropriate @role attributes applied. The processing order can be customized.

Link Groupings

The default template for <related-links> looks like this:

<xsl:template match="&related-links;[.//&link;]"
  priority="-20" mode="#default html">
  <div>
    <xsl:call-template name="generate-common-attrs" />
    <xsl:call-template name="do-child-links" />
    <xsl:call-template name="do-sequence-links" />
    <xsl:call-template name="do-sibling-links" />
    <xsl:call-template name="do-link-groups" />
    <xsl:call-template name="do-other-links" />
    <xsl:call-template name="do-parent-links" />
  </div>
</xsl:template>

It calls the following named templates, in this order.

  1. do-child-links (role="child")
  2. do-sequence-links (role="previous" and role="next")
  3. do-sibling-links (role="sibling")
  4. do-link-groups (links wrapped in <linklist>)
  5. do-other-links (all other links except @role="parent")
  6. do-parent-links (role="parent")

You can override this template to change the order of links in HTML output, or to eliminate various groups of links.

Default Link Structures

The <linklist> element is converted into an <ol>, and each link it contains is wrapped in <li>. Other links are simply placed within <div> elements. Within its container, each link consists of

  1. An <a> tag, with the link itself.
  2. For child links, a <div> containing the short description of the topic, if present. (The DITA engine also generates or populates <desc> tags for related links when possible in the XML presented to the stylesheet.)

Customizing Link Presentation

The built-in related links module includes two special modes for customizing link presentation.

get-link-label
Used to get a label for the link. This is separate from the link text. By default, the module provides "Previous Topic," "Next Topic," and "Parent topic" for @role='previous', @role='next', and @role='parent' links, respectively.
get-link-class-tokens
This mode is used to retrieve any additional HTML @class attribute tokens to include on the generated wrapper element for the link. By default, the @class attribute will include the link tag's specialization hierarchy as well as the value of the @role attribute.

For example, to provide custom @class and label for links whose @role is "sibling", you could add code like the following.

<xsl:template match="&link;[@role='sibling']" mode="get-link-label">
  <xsl:text>Sibling Topic:</xsl:text>
</xsl:template>

<xsl:template match="&link;[@role='sibling']" mode="get-link-class-tokens">
  <xsl:text>sibling-link my-special-class</xsl:text>
</xsl:template>

4.1.8: Profiling Attributes

The default XSLT transform preserves profiling and metadata attributes as data- attributes in the output HTML.

Profiling and metadata attributes include

Any such attributes that survive the DITA preprocessing step, including conref processing and DITAVAL-based profiling, will be added to the appropriate HTML elements prefixed with data-. For example,

<p audience="Expert" importance="urgent">Expert Paragraph</p>

Would be converted into

<p data-audience="Expert" data-importance="urgent">Expert Paragraph</p>

4.2: Writing XSLT for Non-DITA XML

Titania Delivery provides several convenience mechanisms for simplifying the HTML transform for non-DITA doctypes.

Non-DITA content is processed and decorated with namespaced attributes describing each element's role using rules configured in the document type. This means that all non-DITA document types can be treated similarly by reading the decoration attributes instead of the element names. Additionally, non-DITA doctypes may include a basic, universal XSLT transform that should be applied in all portal themes.

Titania Delivery provides a "virtual" XSLT module, called urn:titania:xsl:modules:decorated-html.xsl, that can be used to style most non-DITA XML content. This module will pull in the document type's default transform, if present, as well as supply default styling based on the configuration-based role attributes. Most portal theme stylesheets for non-DITA XSLT can probably be a simple inclusion of this module.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0">
  
  <xsl:include href="urn:titania:xsl:modules:decorated-html.xsl"/>
  
</xsl:stylesheet>
Utility Modes

This stylesheet also includes a number of template modes that can be used to easily customize certain aspects of certain elements.

html-tag
This mode is called on every XML element to determine the HTML element in the output. You can use this mode to customize this behavior.
<xsl:template match="sidebar" mode="html-tag">
  <xsl:text>section</xsl:text>
</xsl:template>
html-attrs
This mode will be called on all elements, and can be used to override the attributes placed on the resulting HTML dlement for specific source XML elements. The default implementation calculates the @class attribute and then applies this mode to all attributes on the element, so to add a representation of a specific attribute in the HTML output, you can simply match on that attribute.
<!-- Customize attributes on a specific tag -->
<xsl:template match="info" mode="html-attrs">
  <xsl:attribute name="class">info</xsl:attribute>
  <xsl:attribute name="data-label" select="concat(@label, ' ', @date)"/>
</xsl:template>

<!-- Customize a specific attribute on a specific tag -->
<xsl:template match="sidebar/@appliesTo" mode="html-attrs">
  <xsl:attribute name="data-appliesTo" select="."/>
</xsl:template>
class-tokens
This mode can be used to augment the HTML @class attribute contents for specific elements.
<xsl:template match="graphic" mode="class-tokens">
  <xsl:text>img-fluid contentGraphic</xsl:text>
</xsl:template>
By default, the @class attribute contains the source element name as well as the roles described by the document type's doctype.xml file for the element.

See Non-DITA XML Processing for additional details.

4.3: Localizing Generated Text

The built-in XSLT modules provide a simple and extensible mechanism for managing translations of generated text.

Gentext translation is implemented using a simple XSLT function called {http://www.titaniasoftware.com/namespace/puck-saxfilter-dita}getString() (the namespace is commonly mapped to the prefix p, so p:getString()). This function takes the English string as a parameter and returns the translated string. The default Titania Delivery portal theme implements this function to load strings from XML dictionaries stored in the portal theme itself under the xsl/i18n folder.

String Lookup Strategy

The default portal theme uses the following algorithm to determine the effective language to use.

  1. If the root element of the document carries an @xml:lang attribute, that value is used. You can disable this behavior by specifying the parameter docLangOverrides=no to the transformation.
  2. If there is no @xml:lang or the docLangOverrides=no is used, the $defaultLang parameter is used. The default value for this parameter is en.

You can override this behavior, or the entire translation scheme itself, by modifying the /xsl/i18n/strings.xsl module in the portal theme.

The default p:getString() implementation follows the following algorithm to look up a string.

  1. Look for an XML document matching the current language. If it exists, look up the string in that document. If found, return.
  2. If the current language is regionalized - that is, contains an underscore or dash (like 'en-US' or 'fr_FR'), strip off the suffix and look for a document matching only the language code (so 'en-GB' would fall back to 'en'). Start over at step one with the truncated language code.
  3. If the current language is different from the $defaultLang parameter (because of @xml:lang), start over at step one with $defaultLang.
  4. Otherwise, simply return the input parameter as the string to use.
Adding or Modifying Translated Generated Text Strings

To add new strings, simply modify the XML documents in the /xsl/i18n folder of your theme. These documents use an extremely simple format:

<strings>
  <string key="englishString">Translation</string>
  <!-- Other Strings -->
</strings>

Modify these documents to update the strings used in generated text. To pull in a new string, simply use the p:getString() function from your XSLT files. For example:

<xsl:value-of select="p:getString('My Custom Gentext')"/>
Adding New Languages and Language Variants

To add new languages to the generated text system, simply create a new string library file named for the locale and add it to the /xsl/i18n folder.

You can also add new language variants, such as en_GB or fr_CA by adding appropriately-named XML files. If a string is missing from a regionalized document, the translation system will fall back to the unlocalized library. For example, if a string is missing from fr_CA.xml, the system will look for it in fr.xml.

4.4: Troubleshooting XSLT

When an XSLT transformation fails when referenced by a <@td.content> directive, a Content Rendering Error message is displayed to the user. The cause of the error will be embedded in a comment in the page source, near the location of the error message.

For example:

<!--ERROR
Error parsing stylesheet "harp://559d46e9e4b09b79fafb191c/p/xsl/topic/topic.xsl". Errors:
Unexpected token "<eof>" in path expression; SystemID: harp://559d46e9e4b09b79fafb191c/pr/ORIGINAL/xsl/topic/topic.xsl; Line#: 36; Column#: 18
-->
<div class="renderingError">
<h2>Content Rendering Error</h2>
<p>An error occurred rendering this content. Please contact your system administrator.</p>
</div>

Chapter 5: Offline Packagers

An Offline Packager is a collection of files containing the rules for building a ZIP archive from the documents in a Titania Delivery portal.

Offline packagers are features of portal themes, and are placed beneath the root-level /packagers folder in the theme, for example, /packagers/webhelp.

The /packagers directory may include other files and directories that are used by more than one packager. Packager directories are distinguished by having a packager.ftl file and an optional config.json file directly within them. For example, in the following directory list, only the webhelp directory is a packager directory. The others contain common templates and resources that may be used by all packagers.

packagers/
  common/
  helpers/
  scripts/
  webhelp/
    config.json
    packager.ftl

The packager folder consists of the following files.

  • A packager.ftl Freemarker script, which is the entry-point for the creation of the package's contents.
  • An optional configuration file called config.json that specifies various options for the packager.
  • Any Freemarker component scripts referenced by packager.ftl.
  • Files to be included in all generated packages, like styling assets, licensing boilerplate, or other static assets not used by the online theme.

In terms of the actual output file format, all packages are ZIP archives. However, many other file formats, such as ePub, Java WAR applications, and even Microsoft Office documents are actually implemented as ZIP archives. A packager script could be written to emit archives meeting any of these specifications.

The packager.ftl file will have access to a set of APIs specific to building packages. It will not have access to most of the tag library used for online portal pages, though similar tags will be provided by the API in some cases. For example, a tag called <@packager.docContents/> will serve a similar purpose to <@td.content/> in online templates, namely, inserting XML, HTML, or text content into a file, possibly with an XSLT transform.

The config.json file specifies metadata for a package, such as its file name, MIME type, and the algorithm to use when determining where in the package to place the documents being packaged, and what their output extensions will be. These algorithms are expressed using Freemarker expressions.

When a portal user requests a package for a document or documents, the server will execute the packager.ftl template. That script will receive the list of documents to be packaged as well as a number of other configuration options. Using those variables and the packager APIs, the script will add content to the package, and then the built package will be delivered to the user.

A Simple Packager

A very basic package builder might look like this.

<#-- Add the /static folder from the theme to the root of the archive -->
<@package.put src="/static"/>

<#-- Compile the SASS stylesheet -->
<@package.preprocessCSS src="/static/style/main/default.scss"
    dest="/static/style/main/default.css"/>

<#-- Add a LICENSE statement from the current packager's directory -->
<@package.put src="LICENSE.txt" dest="/about/LICENSE.txt"/>

<#-- Generate homepage with links to the documents -->
<@package.generate dest="/index.html">
<!DOCTYPE html>
<html>
  <head>
    <title>${(parameters.title!'WebHelp')?html}</title>
    <link rel="stylesheet" href="static/style/main/default.css"/>
  </head>
  <body>
    <h1>${portal.displayName?html} Export for ${user.displayName?html}</h1>
      <ul>
        <#list documents as doc>
          <li>
            <a href="${package.relativeUri(doc)?html}">
              ${doc.metadata.title[0]?html}
            </a>
          </li>
        </#list>
      </ul>
  </body>
</html>
</@package.generate>

<#-- Generate web pages for the content -->
<#list documents as doc>
  <#-- Actual page templates managed as standalone files -->
  <#if doc.contentType?contains('pdf')>
      <#include "docPages/pdf.ftl"/>
    <#elseif doc.properties.is_ditaMap??>
      <#include "docPages/map.ftl"/>
    <#elseif doc.properties.is_ditaTopic??>
      <#include "docPages/topic.ftl"/>
    <#else>
      <#-- Just copy the source into the package. -->
      <#-- The getDocLocation() function above will determine 'dest' -->
      <@package.putDocument document=doc/>
  </#if>
</#list>

5.1: Packager Variables

The packager infrastructure provides a specialized Freemarker environment for processing templates.

The packager.ftl template is provided with the following global variables.

documents
The list of documents to be included in the packaged, represented as a Freemarker array of PackageDocument objects, the structure of which is described below.
parameters
A hash of the parameters passed when the package was requested.
online
A boolean specifying whether the script is running in 'online' or 'offline' mode. In a packager, this will always be false. This value is also available to all online portal theme pages with a value of true. This will allow page components used for both online pages and package pages to determine whether or not they're running for a packager, and adjust themselves accordingly.
<#if online>
  <#-- Generate live page contents with online APIs -->
<#else>
  <#-- Generate offline content with packager APIs -->
</#if>
offline
The opposite of online.
The PackageDocument Data Structure

The documents array contains a list of objects describing the properties and metadata of each document to be included in the package. The packager script iterates over these documents, placing each into the appropriate location in the bundle, possibly after transformation (e.g. XML to HTML5).

In addition, each document contains an array referring to the other files, like graphics or, for DITA Maps, contextualized topics. The packager infrastructure provides various utility mechanisms for automatically including graphics and contextualized DITA topics into generated packages, but you can use the relationships to perform custom, manual processing of such files.

The formal definition of the PackageDocument structure follows (TypeScript syntax).

class PackageDocument {
  // Properties of the file itself.
  key: string;
  project: ProjectInfo;
  label: string; // file name, e.g. foo.xml
  projectPath: string; // path within the project, e.g. /path/to/foo.xml
  folderPath: string; // The path of the folder containing the file, including leading /
  basename: string; // The base name of the file
  extension: string; // The file extension
  contentType: string;
  size: number;
  lastModified: Date;
  createDate: Date;
  packageRoot: boolean; // Whether this document is a package entry-point, that is,
  // included explicitly in the package request.
  isVirtual: boolean; // Typically for topichead virtual documents.
  // Indicates if there is a search record for this document.
  
  // A map of strings to arrays of strings
  metadata: {[name: string]: string[]}
  
  // The properties of the document, analogous to the 'properties' variable in online
  // viewer page templates.
  properties: {[name: string]: any}
  
  // For documents referenced by other documents
  // (map-to-topic references, images, etc.), the
  // referencing document.
  context: PackageDocument;
  hasContext: boolean;
  
  // For topics referenced by maps, the topicref ID.
  refId: string;
  
  // 'Child' references, e.g. topicrefs, graphics.
  outgoingReferences: Reference[];
  
  // Helpful for building search indices.
  keywords: string[];
  textContents: string;
}

enum ReferenceType {
  // Inclusions, like DITA topicrefs or xincludes
  PARENT_CHILD,
  // Non-contextualizing references, like hyperlinks
  LINK,
  // Graphic references
  GRAPHIC,
  // XML doctype references
  DOCTYPE,
  // Generic, non-DITA links
  FILEREF
}

class Reference {
  parent: PackageDocument;
  child: PackageDocument;
  type: ReferenceType;
}

class ProjectInfo {
  key: string;
  name: string;
}
Freemarker package directive template output

A packager freemarker template's primary purpose is to create a zip archive as specified by the @package.* directives. It will also produce text output as a result of normal freemarker template processing. The output generated by the @package.* directives will be an informational transcript of the actions taken, including warnings about any requested actions that may not have been completed as expected. If the configuration buildLogEntry is not null, the output will be written to the designated package entry.

5.2: Packager Configuration

Packager configuration settings.

The config.json file is used to configure certain behaviors of the packager and its output. The file is optional; if not present, various fields' default values will be used. The packager infrastructure uses the following fields:

buildLogEntry
The location in the generated archive of the file containing the output of the packager.ftl script. Set to null to omit the build log. Default: .tdbuild.log.
cacheDays
Because packages may be expensive to build, they are cached such that whenever the same package is requested, the already-built version will be sent. The cache will be cleared when any of the documents in the package are modified, or after this number of days. If not specified or negative, caches never expire. If zero, the cache is not used (not recommended). Default: -1.
contextualizedDocLocation
Similar to docLocation, but used for documents (almost always DITA topics) that have been contextualized by another (almost always a DITA map). Since the same source document may appear with multiple contextualizations, it may need to be represented by distinct output artifacts. Default: ${doc.project.name}/${doc.context.folderPath}/${doc.context.basename}_files/${doc.refId}.${config.outputExtensions[doc.extension?lower_case]!doc.extension}
defaultContentEncoding
The default encoding used for files built using <@package.generate>, <@package.generateDoc>, and <@package.preprocessCSS>, when those elements' @encoding attributes are not specified. By default, the encoding setting is used.
docLocation
The location in the generated archive for the file representing a document, as specified via the doc Freemarker variable. The doc variable is of type PackageDocument . This template is used to generate the default locations of generated files, as well as when resolving links between documents. Default: ${doc.project.name}/${doc.folderPath}/${doc.basename}.${config.outputExtensions[doc.extension?lower_case]!doc.extension}
encoding
The character encoding used for metadata and file paths in the generated zip archive. Default: UTF-8.
filename
The name of the generated package file. This value is evaluated as a Freemarker template; see Packager Variables for the list of valid variables. Default: ${params.packageBasename!portal.displayName}-package.zip
logLevel
The threshold level for writing log messages. Must be one of DEBUG, INFO, WARNING or ERROR. Default: INFO.
mimeType
The MIME type of the packages generated by this packager. Used when delivering the package over HTTP. Default: application/zip.
outputExtensions
Maps the source document file extensions to the extension of the file in the package. This allows certain file types, especially XML documents, to use .html as their file extension in the package instead of .xml. .dita, or .ditamap. The default mappings are:
xml html
dita html
ditamap html
md html
eps png
cgm png
tif png
tiff png
scriptOutputMaxSize
How many characters of the build log to store with the package information document. Because the build logs can be quite large, only a portion of the log should be stored. (The full build log is in the package archive at the buildLogEntry location.) Default: 50000.

The docLocation, contextualizedDocLocation, and filename entries are Freemarker scripts that are used to dynamically calculate values at runtime. These scripts have access to the following variables:

portal
The portal through which the package is being built.
config
The data structure described in config.json.
params
The parameters passed to the packager.
doc (docLocation only)
The PackageDocument whose filename to render.

Here is a sample config.json, which uses default settings for all fields:

{
  "mimeType": "application/zip",
  "encoding": "UTF-8",
  "defaultContentEncoding": "UTF-8",
  "cacheDays": -1,
  "scriptOutputMaxSize": 50000,
  "buildLogEntry": ".tdbuild.log", 
  "logLevel": "INFO",
  "filename": "${params.packageBasename!portal.displayName}.zip",
  "docLocation": "${doc.project.name}/${doc.folderPath}/${doc.basename}.${config.outputExtensions[doc.extension?lower_case]!doc.extension}",
  "outputExtensions": {
    "xml": "html",
    "dita": "html",
    "ditamap": "html",
    "md": "html",
    "eps": "png",
    "cgm": "png",
    "tif": "png",
    "tiff": "png"
  }
}

5.3: The package API

The directives, functions, and variables related to package generation are all bound to the package Freemarker namespace.
Directives (Tags)
<@package.add>

Adds a document to the archive. The document is not modified in any way, and is simply placed as-is. This is primarily for non-textual document formats, like PDF, Microsoft Office, images, video, and audio files.

@document
Required. The PackageDocument instance to insert.
@dest
Optional. The location in the archive to place the document. If not specified, the location in the archive will be determined by the docLocator or contextualizedDocLocator patterns from the config.json file.
@failOnDuplicate
Whether to fail the packager if an attempt is made to add a duplicate entry to the archive. If false (the default), subsequent adds to the same package entry will fail silently. Otherwise the packager will fail with an exception.
@representation
Optional. An array of ItemRepresentationTypes, in priority order. The first type for which there is database content for the specified document will be placed in the package. This attribute supports conversion of non-web-friendly graphics to web-friendly renditions for display inline in an HTML page. For example, if the current pkgDoc is an EPS file referenced from a web page, the following directive would place the PNG rendition in the package (if available), or fall back to the original file type.
<@package.add document=pkgDoc representation=["PREVIEW","ORIGINAL"]/>

For example:

<@package.add document=curDocument/>
<@package.put>

Puts files and directories from the portal theme to the archive.

@src

The portal theme file or directory to add, as a path-like string. If the value begins with a forward slash (/), the location is found from the root of the portal theme. Otherwise, the path from the portal theme root is calculated from the current lexical template location. Relative src paths may include "../" components to refer to the parent directory.

If the source is a folder, its contents will be recursively copied. If the path ends with "/*", only the contents of the named folder will be copied. Otherwise, the final folder in the src path (and its contents) will be copied.

@dest
Optional. The path in the archive where to put the copied source files or folder. If not specified, files and folders are added at the root of the archive. The @dest attribute value is implicitly an absolute directory path, so leading and trailing slashes are assumed, whether or not the template author writes them.
@failOnDuplicate
Whether to fail the packager if an attempt is made to add a duplicate entry to the archive. If false (the default), subsequent adds to the same package entry will fail silently. Otherwise the packager will fail with an exception.

For example:

<#--
Copy the theme's /static directory and its contents to /stuff/static in the archive.
-->
<@package.put src="/static" dest="/stuff/"/>

<#--
Copy the contents of the theme's /static directory into the /stuff directory in the archive.
-->
<@package.put src="/static/*" dest="/stuff"/>

<#--
Copy favicon.ico from the theme's /static directory to the root of
the archive.
-->
<@package.put src="/static/favicon.ico"/>

<#--
Copy all files from the template's parent directory's "images" sibling directory 
into the archive root "images" entry.
-->
<@package.put src="../images/*" dest="images"/>

<#-- 
Same as preceding example.
-->
<@package.put src="../images"/>
<@package.preprocessCSS>

TD online portals support LESS or SASS stylesheets, which must be compiled into CSS before being used by browsers. This directive is similar to <@package.put>, except that it compiles the LESS or SASS file, as determined by file extension, to CSS before placement in the archive.

Note: LESS stylesheets are deprecated in favor of SASS source stylesheets. However, the packager will handle both types.
@src

Path to a LESS or SASS file in the portal theme. Relative paths will be resolved from the current lexical template location, and may include "../" components to refer to the parent directory.

@dest

The path in the archive where to put the compiled CSS file. This is implicitly an absolute path from the archive root. A value must be supplied; there is no default destination.

@encoding
The character encoding to use for the contents of the generated CSS file. Optional. If not specified, the configuration's defaultContentEncoding property is used.
<@package.preprocessCSS src="/static/style/main/default.scss" dest="/static/default.css"/>
<@package.generate>

Executes the nested Freemarker code to generate a file, placing it in the archive at the specified location.

@dest
The location in the archive for the generated file.
@encoding
The character encoding to use for the contents of the generated file. Optional. If not specified, the configuration's defaultContentEncoding property is used.

For example, the following code will generate a /COPYRIGHT.txt file in the bundle using the current year:

<@package.generate dest="/COPYRIGHT.txt">
Copyright (c) ${.now?string.yyyy} Titania Software. All rights reserved.
</@package.generate>
<@package.generateDoc>

Similar to <@package.generate> but instead generates a file using Freemarker that will contain/represent the content of the document. Many document types, like XML documents, should not be included in a package as-is. Instead, it should be wrapped in a page containing branding and navigation.

@document
The PackageDocument instance whose page to generate. The location in the archive will be determined by the config.json file's docLocator or contextualizedDocLocator patterns.
@encoding
Optional. The character encoding to use for the content of the file. If not specified, the configuration's defaultContentEncoding property is used.
<@package.docContents>

Used for inserting a textual, XML, or HTML document within the context of a <@package.generateDoc> or <@package.generate> directive. This is similar to the <@td.content> directive for online templates, adapted for package use.

@document
Required. The document instance to insert.
@expandReferences
A boolean indicating whether references to other documents should be rendered inline; primarily for use with DITA maps. The default is false.
@installMedia

A boolean indicating whether referenced media should be installed in the archive. The default is true. Specifically, the HTML will be scanned for the following elements and attributes:

  • img/@src (images)
  • video/@src
  • audio/@src
  • source/@src
  • object/@data

When generating the HTML representation of content, any references in the source should be left unchanged. The packager will automatically rewrite these HTML references to use the appropriate relative URI in the package.

This works in addition to the packager XSLT function package:mediaReference() function described below.

@storeResult
If set to false, the rendered output will not be stored in the system cache. The default is true.
@timeout
Optional. Set the XSLT transformation timeout in milliseconds.
@xsl

If the content is XML, this directive will specify the XSL transformation in which to pass the content through. If omitted, the XML will be inserted as-is. If the value begins with a slash (/), it will be considered relative to the /xsl directory in the portal theme. Otherwise, it will be considered relative to the packager root directory.

Nested <@package.xslParam> directives may be used to pass parameters to the XSL transformation.

<@package.xslParam>

Used to specify xsl parameters inside a <@package.docContents> directive. This is similar to the <@td.xslParam> directive for online templates.

@name
Required. The name of the XSL parameter.
@value
The value of the parameter. This can be a string, numeric, boolean, sequence, or hash freemarker value.
<@package.statusMessage>

Updates the status message of the offline package request with the rendered directive contents. The status message may be displayed in the status window as the package is being prepared.

<@package.log>

Writes a log message to the freemarker template output. Log messages will only be written if the level specified in the directive attribute is at or above the log level configured in the packager.

level
The log level at which to write the message. The allowed values are, "DEBUG", "INFO", "WARNING", or "ERROR". If not specified, defaults to "INFO".
Functions
package.relativeUri(to[, from])

Used within <@package.generate> or <@package.generateDoc>, this function determines the relative URI to the given package location from the given source location.

to
Required. The object being referred to. This can either be a string containing the target path within the package, or a PackageDocument object, in which case the configuration's @docLocator will be used to determine the target location.
from
Optional. The package location to use as the base when generating the relative URI.

If from is omitted, the current location (as specified by the parent <@package.generate> or <@package.generateDoc> directive) is used.

<a href="${package.relativeUri('/index.html')?html}">Link</a>
<a href="${package.relativeUri(doc)?html}">${doc.metadata['title'][0]?html}</a>
<img src="${package.relativeUri('/static/images/logo.png')?html}">
package.getDocLocation(doc)

This function returns the default location in the archive for the output artifact corresponding to the given input document, as calculated using the rules in config.json.

doc
The PackageDocument instance whose output location to retrieve.
package.setStatusMessage(message)

This is a functional version of the <@package.statusMessage> tag; updates the current status message for the package as it executes.

Variables
package.currentPackageLocation

When inside a directive that is writing to a location in the package, like <@package.generate> or <@package.generateDoc>, this variable contains the location of the entry in the output archive.

package.hexSignatures

When digital signing is enabled, this variable will contain a map of digital signatures generated for files placed into the package. The key in the map is the location in the package of the files that have been generated so far; the values are the digital signatures for those files, as hex-binary strings.

package.base64Signatures

When digital signing is enabled, this variable will contain a map of digital signatures generated for files placed into the package. The key in the map is the location in the package of the files that have been generated so far; the values are the digital signatures for those files, as Base64-encoded binary strings.

package.signaturesXmlDoc

When digital signing is enabled, this variable will hold a DOM Document representing an XML Signatures file in the EPUB signature format for all files in the package (so far). This document can be manipulated using Freemarker's XML processing APIs. For example, the XML could be serialized to the package using instructions like the following:

<@package.generate dest="META-INF/signatures.xml">
  ${package.signaturesXmlDoc.@@markup}
</@package.generate>

5.4: Packager XSLT Support

The <@package.docContents> directive provides functions and parameters that can be used by XSLT stylesheets to interact with the generated package.
Parameters
td.online and td.offline
Boolean parameters passed by the <@package.docContents> directive. td.online will always be false and td.offline will always be true. These parameters will also be passed to templates used by the online <td.content> tag, where the reverse values will be used. This will allow the use of common XSLT modules for both online and offline.
td.packageLocation
The package folder path containing the file being written in the package from the primary output of the stylesheet. Any secondary result documents will be written to the package relative to this location.
td.packager
The name of the packager.
Functions

The following functions are all in the http://www.titaniasoftware.com/xmlns/td-packager name space.

package:relativeUri(to[,secondaryFromLocation]) function

Essentially the same as package.relativeUri() in the Freemarker Functions list. Determines a relative URI to the specified location, except to is always a string. The optional secondaryFromLocation argument specifies the location of the current secondary result document relative to the primary output (given by the td.packageLocation parameter). An empty secondaryFromLocation argument will be ignored.

When TD processes an XML document, it normalizes all references into URIs of the form harp://stuffIdentifyingTheDocument or puckditaxref:referenceId. If the specified target is one of those formats, this function will determine the location of the specified document in the package, and use the package configuration to determine the target URI. Otherwise, the path should be an absolute location within the archive.

<xsl:template match="xref[@scope='local' and @format='dita']">
  <a href="{package:relativeUri(@href)}">
	<xsl:apply-templates/>
  </a>
</xsl:template>

The following example illustrates how package:relativeUri() can be used while creating a secondary result document.

<xsl:variable name="result-doc" select="concat('sub/doc',position(),'.html')"/>
<xsl:result-document href="{$result-doc}" method="html">
  <xsl:apply-templates>
    <xsl:with-param name="doc-href" tunnel="yes" select="$result-doc"/>
  </xsl:apply-templates/>
</xsl:result-document>
<xsl:template match="xref[@scope='local' and @format='dita']">
  <xsl:param name="doc-href" tunnel="yes"/>
  <a href="{package:relativeUri(@href,$doc-href)}">
    <xsl:apply-templates/>
  </a>
</xsl:template>
package:mediaReference(href[,secondaryFromLocation]) function
This will determine the packaged location of the referenced media object, add the media object to the archive if necessary, and return a relative path to the media object. The optional secondaryFromLocation argument specifies the location of the current secondary result document relative to the primary output (given by the td.packageLocation parameter). An empty secondaryFromLocation argument will be ignored.
<xsl:template match="image">
  <img src="{package:mediaReference(@href)}"/>
</xsl:template>
Important: Stylesheets making use of this function should always be called by passing storeOutput=false and/or ignoreCache=true on the <@package.docContents> directive. Otherwise subsequent renderings for the same input will not add the media to the archive.
Other XSLT Processing Details

The following XSLT constructs behave slightly differently when being used in the context of <@package.docContents>.

document() function
The document() function is a built-in XSLT function for reading an XML document. In the context of the packager, attempts to open a harp:// or puckditaxref: URL will attempt to read the referenced TD document.
<xsl:result-document>
The <xsl:result-document> element will write its contents to some location other than the default XSLT output. When used within a packager, the result will be an entry in the package. In this way, package developers can "burst" a single XML document into multiple package artifacts.

5.5: Offline Package Management API

Titania Delivery portal themes can use various Freemarker directives and/or Javascript APIs to request, list, download, and delete offline packages.
Freemarker Directives

The following Titania Delivery tags are available for managing packages in the portal.

<@td.offlinePackages>
Analogous to HARPPortal.listPackages(), this directive lists the packages requested by the current user and stores them in a variable.
@var
The variable to hold the list.
@startPage
The page number to return.
@limit
The max number of results to return. Must be a positive integer n where 0 < n <= 2147483647.
@anonymous
Whether to return the list of anonymous packages or those for the current user. The default is 'false'.
<@td.offlinePackageDownloadUrl>
Retrieves the download URL for an offline package, either inline in the page or in a variable.
@package
The package whose URL to load. Either this or the @key attribute must be specified.
@key
The key of the package whose URL to load. Either this or @package must be specified.
@var
The variable in which to store the URL. If not specified, the URL is output to the page.
Javascript APIs

The HARPPortal Javascript library will have new methods for managing offline packages.

requestPackage(request: PackageRequest, [anonymous: boolean]): Promise<PackageInfo>

Requests a package be created using the info in the given request. The package will be private to the currently-authenticated user unless anonymous is true. This will send an HTTP POST request to the new URL endpoint /portals/rpc/{portalPath}/offline/request containing the JSON representation of the PackageRequest object.

getPackageStatus(key: string): Promise<PackageStatus>
Requests the status of the given package key. The result will be one of "QUEUED", "WORKING", "FINISHED", or "FAILED".
getPackageInfo(key: string): Promise<PackageInfo>
Retrieves details about the given package.
getPackageDownloadUrl(key: string): string
Retrieves the URL from which the finished package can be downloaded.
getPackageHashUrl(key: string[, format: string = 'sha512']): string
Retrieves the URL of the SHA-512 checksum of the finished package. The optional format parameter can be one of the following:
sha512
The hex representation of the checksum, followed by the filename. This is the default.
binary
The raw binary bits of the digital checksum.
hex
A hex-encoded string of the checksum.
base64
A base64-enoded string of the checksum.
getPackageSignatureUrl(key: string[, format: string = 'binary']): string
Retrieves the URL of the digital signature for the finished package, if available. The optional format attribute can be one of the following:
binary
The raw binary bits of the digital signature. This is the default behavior.
hex
A hex-encoded string of the signature.
base64
A base64-enoded string of the signature.
deletePackage(key: string): Promise<boolean>
Deletes the package with the given key.
listPackages(anonymous: boolean): Promise<PackageInfo[]>
Lists the currently-authenticated user's packages. If not logged in, or anonymous is set to true, packages created by anonymous users are listed. Otherwise, only packages created by the currently-authenticated user are listed.
Javascript Data Structures

The data structures used by these methods are defined as follows, using TypeScript syntax.

class PackageRequest {
  packager: string;
  parameters: {[key: string]: any};
  documents?: PackageRequestDocument[];
  search?: string;
  includeContextualizedTopics?: boolean; // true by default
  ignoreCache?: boolean; // false by default
  signContents?: boolean; // false by default
  signaturePathPrefix?: string;
  signaturePathSuffix?: string;
  signatureAlgorithm?: string;
  makeXmlSignatures?: boolean;
}

class PackageRequestDocument {
  projectKey: string;
  itemKey: string;
  contextKey?: string;
  refId?: string?;
}

class PackageInfo {
  key: string;
  portalKey: string;
  packager: string;
  createdBy: string;
  createDate: number; // Epich timestamp
  documents: PackageDocument[];
  params: {[key: string]: any};
  status: PackageStatus;
  statusMessage: string;
  filename: string;
  mimeType: string;
  scriptOutput: string;
  hash: string; // Hex string of the SHA-512 hash
  signature: string; // Hex string of the digital signature, if any
}

enum PackageStatus {
  QUEUED, WORKING, FINISHED, FAILED
}
PackageRequest Properties
packager
The packager to execute. This will be the name of the folder beneath the packagers folder in the portal theme containing the packager.ftl entry point.
parameters
A map of parameters that control various aspects of package generation, accessible from within the packager execution via the parameters variable.
documents
The documents to include.
search
A search string; the results of the search will be packaged.
Note: If neither search nor documents are specified, all documents in the portal will be packaged. If both are specified, search takes precedence.
includeContextualizedTopics
When packaging compound documents, like DITA maps or chunked non-DITA XML files, the search or documents need only specify the root documents and set this to true to capture the root document and all of its component topics.
ignoreCache
Generated package contents are typically cached to improve performance for subsequent re-generation of the package; setting this to true skips that caching.
signContents
Whether to automatically generate and store side-files containing digital signatures of each file in the package. If set to true, one or both of signaturePathPrefix and signaturePathSuffix must be specified or no signatures will be added to the package. This is false by default.
signaturePathPrefix
A string to prepend to the paths for digital signature side files, commonly used to put signatures in their own root-level folder in the package. For instance, set to /signatures/ to create a mirror of the package folder structure beneath the signatures folder for digital signatures. This is blank by default.
signaturePathSuffix
A string to append to file paths for digital signature side files. This is .signature by default, meaning signature files will be in the same folder as the file they're signing, with a .signature extension.
signatureAlgorithm
The signature algorithm to use to generate digital signatures of the package itself and its contents. Must be one of the following values:
  • SHA512withRSA (default)
  • SHA384withRSA
  • SHA256withRSA
  • SHA224withRSA
  • SHA1withRSA
makeXmlSignatures
If set to true, the packager will build up an XML document that contains digital signatures for each file in the package. This document is available via the package.signaturesXmlDoc variable and can be inserted into the package using Freemarker code like this:
<@package.generate dest="META-INF/signatures.xml">
  ${package.signaturesXmlDoc.@@markup}
</@package.generate>
Sample Javascript Code for Requesting a Package

For example:

HARPPortal.requestPackage({
  packager: 'WebHelp',
  parameters: {
    title: 'My Fancy WebHelp Package'
  },
  documents: [
    'harp://blahblahblah',
    {projectKey: 'abc', itemKey: '123'}
  ]

}).then(checkStatus);

function checkStatus(info) {
  switch(info.status) {
    case 'QUEUED':
    case 'WORKING':
      console.log('Current status is ' + info.status + '. Checking again in 5 seconds.');
      setTimeout(function() {
        HARPPortal.getPackageInfo(info.key).then(checkStatus);
      }, 5000);
      return;

    case 'FAILED':
      alert('Packager failed.\n' + info.scriptOutput);
      return;

    case 'FINISHED':
      if (confirm('Package completed. Download?')) {
        window.open(HARPPortal.getPackageDownloadUrl(info.key));
      }
      return;
  }
}
JSON HTTP Endpoints for Package Management
POST /portals/rpc/portalPath/offline/request[?anonymous=true]
Requests the creation of a portal. The response will be a JSON document describing the package. The result is a PackageInfo document. Use the anonymous query parameter to request a public package not associated with the current user.
DELETE /portals/rpc/portalPath/offline/packageKey
POST /portals/rpc/portalPath/package/packageKey/delete
Deletes the specified package.
GET /portals/rpc/portalPath/offline/packageKey/status
Requests the status of the given packager. The return value will be a JSON string containing one of the following values:
  • "QUEUED"
  • "WORKING"
  • "FINISHED"
  • "FAILED"
GET /portals/rpc/portalPath/offline/packageKey/info
Returns a PackageInfo document describing the package. Some fields will not be present for some statuses.
GET /portals/rpc/portalPath/offline/packageKey/download
Downloads the package, if it is in the FINISHED state. Otherwise callers will receive a JSON document containing a "error" or "message" keys describing the reason for the refusal.
GET /portals/rpc/portalPath/offline/list[?anonymous=true]
Lists the PackageInfo objects created by the currently-authenticated user. use the anonymous query parameter to request anonymous packages even if logged in.
GET /portals/rpc/portalPath/offline/packageKey/hash
Retrieves the SHA-512 checksum of the package. This parameter takes an optional format URL parameter which can have one of the following values:
sha512
The hex representation of the checksum, followed by the filename. This is the default.
binary
The raw binary bits of the digital checksum.
hex
A hex-encoded string of the checksum.
base64
A base64-enoded string of the checksum.
GET /portals/rpc/portalPath/offline/packageKey/signature
Retrieves the digital signature of the package, if any. This endpoint takes an optional format URL parameter which can have one of the following values:
binary
The raw binary bits of the digital signature. This is the default behavior.
hex
A hex-encoded string of the signature.
base64
A base64-enoded string of the signature.

5.6: Offline Search Support

Packagers can include search indexes to support browser- and filesystem-based searching in packages.
Overview

One major disadvantage to offline browsing is that users cannot use the features provided by the TD search engine, like full-text or faceted search. However, there are some document formats that may want a specially-formatted index file or files to enable search in the archive's viewer.

The PackageDocument object has a textContents property that can be used to extract the textual contents of the file without any markup or styling, primarily for building a full-text index.

Building the search index

The Freemarker template, packagers/common/search-index.ftl, will prepare lunr.js indexes (in multiple languages) of text content in package documents.

In addition to the normal package variables, this page uses the following Freemarker variables to communicate with the template process.

docsToIndex
A sequence of package documents to index. This could be used to exclude certain package documents, such as large PDF documents, from indexing. Defaults to package documents list.
lunrLangScripts
A sequence of lunr-languages source file names that must be loaded in the browser to support searching.

This template uses <@package.generate> to load required libraries from lunr.js and lunr-languages into Nashorn script engine, then runs the lunr indexer to build search indexes for specified package documents. For each document to be indexed:

  1. The document language is identified from document metadata locale (preferably) or lang fields.
  2. Determine if language can be indexed. Since lunr-languages does not handle regional variants, only 2-letter language codes are considered. Only Chinese language codes indicating Simplified Chinese (zh-CN, zh-SG, zh-HANS) are handled.
  3. Group documents by language, for more efficient indexing.
  4. Load needed javascript source files to handle languages.

After grouping the documents by language (omitting documents in unhandled languages), the index is built. The indexing function is defined in packagers/scripts/server/search-index.js

The result of the indexing process is a javascript snippet that defines a structure containing the lunr.js indexes (in all applicable languages) and minimal document metadata to support navigation to documents returned from searches. This snippet is printed to the template content.

Indexing a large multi-lingual document collection can take several minutes. Since TD version 4.2, indexing progress will be updated in the package status dialog. (Prior to 4.2, only the start of indexing will be shown in the status dialog.) Indexing Simplified Chinese documents is particularly slow.

Browser searching

The utility template, helpers/searchHelpers.ftl will write the required <script> elements into the HTML page heads, and add search input control and page division for displaying search results.

The script, scripts/browser-search.js, executes the search and formats the results. The results will show a snippet of the document with search hits highlighted. Since lunr.js will in many cases return only the stemmed token that scored the hits, the script tries to find complete words that match the token. This process is not completely accurate, but normally will show several valid variants of the stem token that produced the hit. In some cases, there is no match found, and the snippet will be empty.

Chapter 6: Portal Parameters

A single portal theme can be built using parameters that affect its behavior. These parameters are set when the theme is associated with a portal. This enables the same portal theme to be reused with many different portals, while providing potentially vast differences in the user experience provided by the theme. These parameters are configured using a config.xml file at the root level of the theme.

6.1: Authoring config.xml

The config.xml file allows portal theme developers to parameterize portal themes with settings that can be set when the theme is associated with a portal.

The structure of this files is as follows.

Groups

The input configurations are organized into <group> elements. In the User Interface, each group will be rendered as a tab using the group's @label attribute.

Parameter Descriptor Elements
<string>
A simple string, without line breaks.
<password>
A password or other secret string value. This value will never be displayed in the admin interface, and will be masked when it is modified.
<text>
larger amounts of text, with three possible formats
  • @type="html" - Text is interpreted as HTML code.
  • @type="markdown" - Text is converted to HTML using Markdown rules.
  • @type="text" - Any HTML characters are escaped.
Note: While you almost always should escape strings displayed on portal pages using the ?html directive, you should not do this for <text> parameters, as the appropriate HTML markup will already be built as the value of the parameter.
<number>
A numerical value.
<checkbox>
A boolean value.
<select>
Enables the user to select a value from a list of possible values. A select element will have one or more <option> child elements listing the available values. In addition, the @multi attribute is used to describe whether multiple values may be selected.
  • @multi="true" - Select an array of options (including order) from a set
  • @multi="false" - Select a single option from a set (the same effect can be achieved by omitting the @mutli attribute)
<themeFile>
Enables the user to select a file from a specific folder in the portal theme. This is similar to <select> except that the available <option> values are derived from the files beneath the specified folder. Attributes:
@dir
The directory path in which to search for files, relative to the folder implied by the @type attribute.
@type
Controls the type of file being listed, which controls the base folder for the @dir attribute as well as how the value is provided when requested.
customPage
Relative to /pages/custom. The value provided to portal pages will be suitable for use with the <@td.pageUrl> tag.
static
Relative to /static. The value provided to portal pages will be suitable for use with the <@td.themeFileUrl> tag.
xsl
Relative to /xsl. The value provided to portal pages will be suitable for use with the <@td.content> tag's @xsl attribute.
<linkList>
A container for <link> elements describing links to be displayed somewhere in the UI. Each <link> element carries @href, @label, and @target attributes describing the link to be rendered.

In addition to these inputs, the <instructions> element can be inserted anywhere within a <group> or as the first child of any input. Instructions will be rendered as user-visible text in the form.

Parameter Attributes

All parameters require a @name attribute which is used to access them from the Freemarker templates. The @label attribute is also recommended and supplies an external label for the parameter that is shown when the form is rendered. Authors can hide a parameter (and force each portal to use its default value) by setting the @hidden attribute to true .

Default Values

The @default attribute is used to define default values for parameters (excluding those of type text , select , and linkList ). Defaults for select elements are define by setting the @default attribute on the options desired within the select tag. Defaults for text elements are defined by a separate <default> tag within the <text> tag. The @default attribute should be included if possible when writing your config.xml, but is not required.

Using Parameters

These parameters must be defined in config.xml , but may be used anywhere in the portal theme. Access to these parameters is gained by using the parameters collection on the portal object. For example, if using a parameter named "stylesheet", it would be accessed with the syntax portal.parameters.stylesheet.

<link rel="stylesheet" href="<@td.themeFileUrl value="${portal.parameters.stylesheet}" />"/>
Parameter Configuration DTD
<?xml version="1.0" encoding="UTF-8"?>
<!-- =============================================================== -->
<!-- Titania Delivery Portal Theme Parameter Descriptor              -->
<!-- Describes a portal theme's parameters and the User Interface    -->
<!-- used to populate their values.                                  -->
<!--                                                                 -->
<!--    <!DOCTYPE parameters PUBLIC                                  -->
<!--      "-//Titania//DTD Portal Theme Parameter Configuration 1.0//EN" -->
<!--      "theme-param-config.dtd">                                  -->
<!-- =============================================================== -->
<!ENTITY % commonAtts-nodeflt
    "name ID #REQUIRED
     label CDATA #IMPLIED
     hidden (true|false) 'false'
     tooltip CDATA #IMPLIED"
>

<!ENTITY % commonAtts
    "%commonAtts-nodeflt;
     default CDATA #IMPLIED"
>

<!ELEMENT parameters (group)+>

<!ELEMENT group
    (string |
    text |
    number |
    checkbox |
    select |
    themeFile |
    linkList |
    instructions
)*>
<!ATTLIST group
    label CDATA #REQUIRED
>

<!ELEMENT string (instructions?)>
<!ATTLIST string %commonAtts;>

<!ELEMENT text (instructions?, default)>
<!ATTLIST text
    %commonAtts-nodeflt;
    type (text | markdown | html) 'text'
>

<!ELEMENT default (#PCDATA)*>

<!ELEMENT number (instructions?)>
<!ATTLIST number %commonAtts;>

<!ELEMENT checkbox EMPTY>
<!ATTLIST checkbox %commonAtts;>

<!ELEMENT select (instructions?, option+)>
<!ATTLIST select
    %commonAtts-nodeflt;
    multi (true|false) 'false'
>

<!ELEMENT option (#PCDATA)>
<!ATTLIST option
    default (true|false) 'false'
    value CDATA #IMPLIED
>

<!ELEMENT themeFile (instructions?)>
<!ATTLIST themeFile
    %commonAtts;
    type (customPage | static | xsl) #REQUIRED
    dir CDATA #IMPLIED
>

<!ELEMENT linkList (instructions?, link*)>
<!ATTLIST linkList
    %commonAtts-nodeflt;
>

<!ELEMENT link EMPTY>
<!ATTLIST link
    href CDATA #REQUIRED
    label CDATA #REQUIRED
    target (_blank|_self) '_blank'
>

<!ELEMENT instructions (#PCDATA)*>

6.2: Example Portal Parameter Configurations

This topic contains several samples of configuration markup.
Portal Display Name and Description
<string name="title" label="Portal Display Name"/>
<text name="description" label="Portal Description" type="markdown">
  <default>
  <![CDATA[Welcome to *Titania Delivery*!]]>
  </default>
</text>

This sample defines two portal theme parameters, title and description, which can be used to provide a site-wide label and homepage description for the portal. The matchinf form would look something like this:

The values could be used in the portal theme pages via Freemarker like the following:

<h1>${portal.parameters.title?html}</h1>
<div>${portal.parameters.description}</div>
Note: While you almost always should escape strings displayed on portal pages using the ?html directive, you should not do this for <text> parameters, as the appropriate HTML markup will already be built as the value of the parameter.
Configurable Styling Details
<themeFile name="stylesheet" label="Stylesheet" type="static"
  dir="style/main" default="default.less"/>

<text name="css" label="Custom CSS" type="html"/>

<themeFile name="siteLogo" label="Site Logo" type="static"
  dir="images/logos" default="harp_128.png"/>

<themeFile name="header_bg" label="Header Background Image" type="static"
    dir="images/backgrounds" default="orange-arch.jpg"/>

This sample provides four parameters.

stylesheet
This lists all of the files in the static/style/main folder of the theme for the user to select from.
css
This provides a textbox allowing the administrator to add some custom CSS code that can be injected on every page.
siteLogo
This lists all of the files in the static/images/backgrounds folder of the theme for the user to choose from. This could be used as a logo on the homepage and/or in the header of every page.
header_bg
This lists all of the files in the static/images/backgrounds folder. This could be used as the background for the banner at the top of the homepage, and/or the background of the header.

You can use these parameters in the theme as follows:

<!DOCTYPE html>
<html>
<head>
	<title>Portal Title (probably also a parameter)</title>
	<link rel="stylesheet" href="'<@td.themeFileUrl value=portal.parameters.stylesheet/>">
	<style>
	${portal.parameters.css}
	</style>
</head>
<body>
	<div style="background: url('<@td.themeFileUrl value=portal.parameters.header_bg/>');">
	  <img src="<@td.themeFileUrl value=portal.parameters.siteLogo/>">
	  <h1>Site Title</h1>
	</div>
</body>
</html>
Multi-Select

The following markup enables users to specify the available sections on a homepage, and to sort them into a particular order.

<select name="homepage-modes" label="Homepage Sections" multi="true">
  <option default="true" value="document-lists">
    Recently Modified Document Lists
  </option>
  <option value="onemap-circles">
    Single DITA Map with Circular Graphics
  </option>
  <option value="multimap-divs">
    Multiple DITA Map as Page Divisions
  </option>
</select>

This is rendered as follows, allowing the end user to use drag-and-drop to add/remove sections, as well as sort them.

The parameter value is the array of selected values. From a portal theme page, you can use this parameter as follows:

<#list portal.parameters['homepage-modes'] as mode>
  <#if mode = 'document-lists'>
    <#include 'document-lists.ftl'/>
  <#elseif mode = 'onemap-circles'>
    <#include 'onemap-circles.ftl'/>
  <#elseif mode = 'multimap-divs'>
    <#include 'multimap-divs.ftl'/>
  </#if>
</#list>

Chapter 7: Portal Pages

Portal pages are delivered as HTTP responses to web client HTTP requests.

7.1: Content-Security-Policy and Portal Pages

Implementing a Content-Security-Policy regime for portal pages.

Content Security Policy (CSP) is a W3C recommendation for specifying restrictions on how browsers should handle potentially dangerous components in an HTTP response entity. As of this writing, 2 levels of the recommendation are available.

  • CSP Level 2 (recommendation 2016-12-15)
  • CSP Level 3 (working draft; many features implemented in modern browsers)

By default, the TD platform does not include any Content-Security-Policy HTTP headers on portal pages. If your corporate security guidelines require CSP directives on your TD portal pages, here are some options.

Adding CSP header to portal pages

The portal theme provides the ability to add <meta> tags to the <head> element of any HTML page generated by the portal. One way to deliver CSP headers to the browser is by adding a <meta> tag like:

<meta http-equiv="Content-Security-Policy" value="{your-csp-here}"></meta>
Replace {your-csp-here} with the actual policy desired.

Note: CSP headers delivered in http-equiv <meta> tags may be superseded by HTTP CSP headers added to the response.

If all portal pages on a site should be subject to the same CSP, contact Oberon Support to add the desired CSP to the site settings. This policy will be delivered as the Content-Security-Policy HTTP header on all portal pages.

Generating CSP-compliant portal pages

The portal theme developer is responsible for ensuring that all pages generated in the portal comply with the effective CSP.

When a Content-Security-Policy is in effect for a web page, disallowed components in the page will be disabled. This could affect the appearance, behavior, and usability of the web page. Disallowed unsafe-inline and unsafe-eval script and style sources are a common cause of problems. Many TD portal themes now in use violate these policies (including the built-in and provided themes, as well as custom themes derived from them).

Before implementing strict CSP on portal pages, review and test the portal theme to find and remove components that would violate the intended CSP. For example, if the CSP includes script-src 'self';, then <script> elements and script attributes will be disabled. Any part of the theme that generates these elements and attributes must be modified.

Any theme that relies on TD XSLT modules for transforming XML to HTML could generate inline style elements and attributes in a portal page. These will be disallowed by CSP unless the policy includes style-src 'self' 'unsafe-inline';. The impact on portal page display will range from minor to severe. In cases where style unsafe-inline is not allowed, significant changes may be required to over-ride the built-in XSLT stylesheets to eliminate inline styles.

Part II: Document Type Developer's Guide

Chapter 8: Document Types

A Document Type is a special kind of Project that contains the XML grammar definition files – DTDs and XML Schemas – that are used to parse XML content.

More specifically, a document type consists of:

  • The DTD and/or XML Schema files describing the grammar of the document type.
  • A catalog file for resolving references to document type files from projects and other document types.
  • For non-DITA document types, a doctype descriptor file describing the roles of the elements in the document type, such as links, graphics, and chunk divisions. (DITA documents are self-describing and do not require this file.)

Document types may also contain the following optional files.

  • An XSLT stylesheet for generating the administrative HTML preview for files using this document type.
  • XSLT files for custom pre-processing of documents using this document type and/or, for DITA map document types, the topics referenced from maps of this type.
  • An XSLT for generating HTML from the document type that can be dynamically referenced from portal theme XSLT stylesheets.
  • An XSLT transform defining metadata rules for files using the document type.
  • A file mapping embedded properties in non-XML documents, such as PDF and Microsoft Office files, to Titania Delivery metadata. Any project using a document type with one of these metadata mapping files will leverage those mappings.

A document type may be based on another "parent" document type, in which case it inherits all of the catalog entries, preview stylesheets, and metadata rules. For example, you could create a custom DITA topic specialization based on the base "DITA" document type, inheriting all of the catalog entries, metadata configurations, and preview stylesheet from that document type, without having to copy or build your own.

The default Titania Delivery installation includes the DITA 1.3 document types and DocBook 5.0, as well as a document type container with some default non-XML metadata mappings.

Chapter 9: Installing a Document Type

Follow these steps to create a new document type in Titania Delivery.
  1. Create the Document Type container in the appropriate organization.
    1. Click the New button on the Document Types section for your organization to create the empty Document Type object.
    2. Specify a name for the document type.
    3. Optional: If customizing an existing document type, select that document type from the list of "Based On" document types. For example, if setting up a DITA specialization, Select the DITA 1.3 document type.
    4. Click Save to create the empty doctype container.
  2. Add the DTD and/or XSD modules for your specialized shells to the document type project.
    The structure of these files doesn't matter. All that matters is that they are present.
  3. Add a catalog file.
    This file defines mappings for resolving public identifiers and/or URIs from project documents that use this document type to DTD or XSD modules in the document type container. This file must exist at the top level of the document type, and must be named catalog.xml, catalog.txt, or catalog. The contents of the file must be in either the XML Catalog format or TR 9401 "text" catalog format. Both these formats have the ability to reference other catalog files, and those features are fully supported and encouraged.
  4. For non-DITA document types, add a doctype descriptor.
    Create a file at /HARP-META/doctype.xml describing the important elements and attributes in your document type. See Doctype Descriptor Format for details.
  5. Optionally, create an XSLT module for converting documents of this doctype to HTML.
    If desired, create a HARP-META/html.xsl file that can be linked into portal HTML transforms. See Writing XSLT for DITA and Writing XSLT for Non-DITA XML for details.
  6. Create a preview stylesheet.
    If your document type is an extension of another document type, this is optional, and the stylesheet from the base doctype will be used by default. Otherwise, create /preview.xsl for building an admin HTML preview for your document type. This can frequently be a simple inclusion of HARP-META/html.xsl.
  7. Optionally, create custom pre-processing transforms for your document type.
  8. Add the document type to existing projects.
    In order for content in a project to use the new document type configuration, the document type must be added to the XML Document Type list for that project.
    Note: From this point forward, any new projects created under the same organization as the document type will automatically have a link to the document type. You only need to do this for existing projects.
    1. Select the root node in the project you want to update and select the XML Document Type tab.
    2. Click the Add button and select the document type from the list, and click OK.
    3. Re-process any content that should use the new document type.

Chapter 10: Doctype Descriptor Format

Document types must provide a /HARP-META/doctype.xml file describing how their elements behave.
Important: Document types conforming to the DITA standard do not need a doctype descriptor, as DITA documents are self-describing via the @class attribute.

Technically, the only configuration that must be in doctype.xml are

  • The ID and language attributes
  • Profiling attributes
  • Links
  • Graphics
  • Divisions (chunk boundaries)
  • Titles, for populating empty ID-IDREF links and creating ToC navigation of chunked divisions.

Titania Delivery needs this information in order to properly handle the references in a document. Most of the other configuration that can go into doctype.xml is more presentational. However, describing presentational elements in this file allows TD to apply default styling, leaving little to no need for doctype-specific XSLT.

Structural Elements
<doctype>
This is the root element of the file. It has the following attributes.
@id
Required. An identifier for the document type, captured as a property on documents of this type.
@id-attr
Optional. The ID attribute from the document type. TD will look for @xml:id by default.
@lang-attr
Optional. The attribute in the DTD used to identify the language of an element. TD will look for @xml:lang by default.
<comment-element>
A single top-level tag describing the markup to use when embedding comments in XML. If no <comment-element> is present, then TD will use namespaced markup for comments.
@name
Required. The commenting element name.
@dispositionAttr
The attribute to use for disposition values.
@authorAttr
The attribute to use for the author's name.
@timestampAttr
The attribute to use for the timestamp.
@dataElement
The element to use for comment metadata, nested within a comment.
@dataKeyAttr
The attribute on the data element for the metadata key.
@dataValueAttr
The attribute to use for metadata values on the data element.
@wrapReplies
True or false. True by default. If true, replies will be wrapped in a data element with a key of "replies." (This is necessary in many content models where comment tags cannot be directly nested.)
<links>
Container for link element descriptions <idref>, <external>, and <fileref>.
<graphics>
Container for graphic element descriptions (<graphic>).
<specials>
Container for <special> descriptions. May carry a @type attribute containing the default special type for the nested descriptions.
<profling-atts>
Container for <profiling-att> descriptions.
Descriptive Elements

Each of the elements in the structural group above can optionally start with a <title> and/or <description> element providing a human-readable overview of the elements contained within. These are not required for the system, but are usful when maintaining the file.

Common Attributes to All Markup Description Tags

The following attributes appear on every markup description tag described below.

@match
The XPath pattern used to identify the elements being described. This will most often be a simple element name, but could also be an OR-ed list of tag names, or use a predicate to describe different elements with the same name differently based on attributes or context. For example:
<special match="title" type="title"/>
<special match="literallayout | programlisting | screen" type="preformatted"/>
<special match="emphasis[@role='bold']" type="bold"/>
<special match="emphasis[@role='italic']" type="italic"/>
<special match="emphasis[@role='underline']" type="underline"/>
<special match="emphasis[@role='strikethrough']" type="strikethrough"/>
<special match="emphasis" type="emphasis"/>
When there is ambiguity between two configurations - for example, <emphasis role="bold"> is matched by both emphasis[@role='bold'] and emphasis - the earlier configuration in the document will apply. When creating a configuration file, you should put your most specific rules first, and your most generic rules last.
@html-element
Optional. The default HTML element to be generated by the default HTML transform for elements that match the pattern in @match. The HTML equivalent is frequently inherent in the description (e.g. links should be come <a>, graphics should become <img>, bold elements should become <b>, etc.)
Profiling Attribute Configuration

The <profling-atts> element can contain any number of <profiling-att> elements defining the attributes used for profiling. The available attributes are:

@name
The name of the attribute.
@delm
The delimiter for multiple values. The default is a space.
Link Descriptors
<idref>
Describes an ID-IDREF linking element.
@linkend
Required. The attribute that will contain the ID of the link target.
Note: In order for ID-IDREF links to work properly, the @id-attr attribute on the root <doctype> element must be set correctly.
<idref match="xref" linkend="linkend"/>

This will match <xref linkend="abc"/>.

<external>
Identifies external (generally web) links.
@href
The name of the attribute identifying the URI of the target.
<external match="ulink" href="href"/>

This will match <ulink href="http://whatever"/>.

<fileref>
Identifies links that refer to another file in the system.
@href
The attribute containing the URI of the target file.
<external match="fileref" href="href"/>

This will match <fileref href="note.xml"/>

Graphic Descriptors
<graphic>
Names a graphic element.
@src
The attribute naming the URI of the target graphic.
Note: Currently, there is no configuration for sizing attributes, placement (inline vs. block), or other presentational aspects of graphic references. We should consider adding these. For now, these can be accounted for in html.xsl.
Specials

The <special> element is a sort of catch-all "everything else" configuration element. It carries a @type attribute describing the type or types of element it represents.

An element may have more than one type. For example, an inline code snippet would be described with type="preformatted inline".

The available type values are:

inline
Inline elements. The generic stylesheet styles everything as a block by default, so inline elements should be enumerated explicitly unless they only appear inside other elements configured with type="para".
titled-block
Blocks with titles. Empty IDREF links to titled blocks will be populated with the block's title.
title
The element used for titles of titled blocks and divisions.
division
A document division. When processing a document, it will be chunked at the division elements, replaced by a link to the division in the root chunk (essentially establishing a ToC).
para
Paragraphs. Any element appearing within a paragraph style element, and which is not described in doctype.xml, will be treated as inline instead of block.
preformatted
Preformatted text. This will be rendered as a block of text unless the element is also marked as inline.
ordered-list
unordered-list
list-item
Elements describing lists.
definition-list
dlentry
dt
dd
Elements describing definition lists.
bold
italic
underline
emphasis
strikethrough
superscript
subscript
monospace
Inline styles.
blockquote
A block quote.
indexterm
An index term. The contents will be hidden in output by the default stylesheet but boosted in the search record for the document.
no-search
The text will appear but not be included in the full-text index.
hidden
The text will be included (but not boosted) in the search index, but not displayed.
Namespaces in doctype.xml

If you need to identify elements or attributes in doctype.xml that are in a namespace, you do so by declaring the namespace with a prefix on the root <doctype> element, then simply using that prefix when you identify content. For example, to describe DocBook 5, which has a default namespace, you would do something like:

<doctype xmlns:d="http://docbook.org/ns/docbook">
  <links>
    <idref match="d:xref" linkend="linkend"/>
  </links>
  <!-- etc. -->
</doctype>
Doctype Descriptor DTD

Here is the full DTD of doctype.xml.

<?xml version="1.0" encoding="UTF-8"?>
<!-- =============================================================== -->
<!-- Titania Delivery Document Type Descriptor                       -->
<!-- Describes a document type's elements for processing by Titania  -->
<!-- Delivery. Accessed via the public identifier                    -->
<!--    <!DOCTYPE doctype PUBLIC                                     -->
<!--      "-//Titania//DTD Document Type Descriptor 1.0//EN"         -->
<!--      "doctype.dtd">                                             -->
<!-- =============================================================== -->
<!ENTITY % heading "title?, documentation?">
<!ENTITY % common-atts "match CDATA #REQUIRED html-element NMTOKEN #IMPLIED">
<!ENTITY % types
    "inline |
    titled-block |
    title |
    division |
    para |
    preformatted |
    monospace |
    ordered-list |
    unordered-list |
    list-item |
    definition-list |
    dlentry |
    dt |
    dd |
    bold |
    italic |
    underline |
    emphasis |
    indexterm |
    no-search |
    hidden |
    blockquote |
    strikethrough |
    superscript |
    subscript"
>
<!ENTITY % type "type (%types;) #IMPLIED">

<!ELEMENT doctype
    ((%heading;),
    (links |
    graphics |
    specials |
    profiling-atts |
    comment-element)*)
>
<!ATTLIST doctype
    id CDATA #REQUIRED
    id-attr NMTOKEN #IMPLIED
    lang-attr NMTOKEN #IMPLIED
>

<!ELEMENT title (#PCDATA)*>
<!ELEMENT documentation (#PCDATA)*>

<!ELEMENT specials ((%heading;), special*)>
<!ATTLIST specials %type;>
<!ELEMENT special EMPTY>
<!ATTLIST special
    %common-atts;
    %type;
>

<!ELEMENT links ((%heading;), (idref | external | fileref)*)>
<!ELEMENT idref EMPTY>
<!ATTLIST idref
    %common-atts;
    linkend NMTOKEN #REQUIRED
>
<!ELEMENT external EMPTY>
<!ATTLIST external
    %common-atts;
    href NMTOKEN #REQUIRED
>
<!ELEMENT fileref EMPTY>
<!ATTLIST fileref
    %common-atts;
    href NMTOKEN #REQUIRED
>

<!ELEMENT graphics ((%heading;), graphic*)>
<!ELEMENT graphic EMPTY>
<!ATTLIST graphic
    %common-atts;
    src NMTOKEN #REQUIRED
>

<!ELEMENT profiling-atts ((%heading;), profiling-att*)>
<!ELEMENT profiling-att EMPTY>
<!ATTLIST  profiling-att
    name NMTOKEN #REQUIRED
    delim CDATA ' '
>

<!ELEMENT comment-element EMPTY>
<!ATTLIST comment-element
    name NMTOKEN #REQUIRED
    dispositionAttr NMTOKEN 'disposition'
    authorAttr NMTOKEN 'author'
    timestampAttr NMTOKEN 'time'
    dataElement NMTOKEN #IMPLIED
    dataKeyAttr NMTOKEN 'name'
    dataValueAttr NMTOKEN 'value'
    wrapReplies (true|false) 'true'
>

Chapter 11: Non-DITA XML Processing

Decoration

When a non-DITA XML document is processed, one of the first things TD will do is read the doctype's /HARP-META/doctype.xml file and use its settings to add namespaced attributes to the document. This will, in effect, make the document self-describing for purposes of downstream processing.

The namespace prefixes added during the decoration process are as follows.

@xmlns:td
http://www.titaniasoftware.com/ns/titania-delivery/decoration
@xmlns:profiles
http://www.titaniasoftware.com/ns/titania-delivery/decoration/profiling
  • Every element described in /HARP-META/doctype.xml gets a @td:role attribute describing its purpose. Elements described by <special> get the appropriate @type values. Other elements are described below.
  • If the configuration element specifies an @html-tag, the element will get a @td:html-tag attribute with that value.
  • IDREF links get a @td:role of "link-idref". In addition, they get @td:linkendAttrs with the @linkend value.
  • External links get a @td:role of "link-external" and @td:hrefAttrs with the @href value.
  • Fileref links get @td:role="link-fileref" and @td:hrefAttrs value with the @href value.
  • Graphics get @td:role=imageref and @td:hrefAttrs with the @src attribute value.
  • Every element with an ID or language attribute as described in the config file gets @xml:id and @xml:lang, respectively.
  • For every profiling attribute, the root element gets a @profiling:{attribute} attribute whose value is that profiling attribute's delimiter.
  • Every element gets a @td:pointer attribute with the element name and number, for comment traceability (e.g. td:pointer="para:5" for the fifth <para> element).

Once the document is decorated, downstream processing and styling can use the decorations to determine the appropriate processing and styling. Processing includes resolving links and graphics, and chunking the content at divisions.

Chunking

When working with non-DITA doctypes, elements identified as type="division" in doctype.xml will be broken out into their own discrete chunk. The chunk will be indexed as an individual document, and presented to portal users as its own web page. In the top-level document, the chunked content will be replaced by generated ToC-style markup.

<?xml version="1.0" encoding="UTF-8"?>
<td:toc td:role="unordered-list">
  <td:tocentry td:role="list-item">
    <td:toclink href="puckditaxref:preface-online" type="preface" td:role="link-fileref"
      td:hrefAttrs="href" targetId="preface-online">Preface to the DocBook V5.2 Edition</td:toclink>
  </td:tocentry>
  <td:tocentry td:role="list-item">
    <td:toclink href="puckditaxref:preface" type="preface" td:role="link-fileref"
      td:hrefAttrs="href" targetId="preface">Preface</td:toclink>
  </td:tocentry>
  <td:tocentry td:role="list-item">
    <td:toclink href="puckditaxref:docbook-intro" type="part" td:role="link-fileref"
      td:hrefAttrs="href" targetId="docbook-intro">Introduction</td:toclink>
    <td:toclevel td:role="unordered-list">
      <td:tocentry td:role="list-item">
        <td:toclink href="puckditaxref:ch-gsxml" type="chapter" td:role="link-fileref"
          td:hrefAttrs="href" targetId="ch-gsxml">Getting Started with DocBook
        </td:toclink>
      </td:tocentry>
    </td:toclevel>
  </td:tocentry>
</td:toc>
<td:toc>
The root level of the table of contents with a @td:role of unordered-list.
<td:tocentry>
Each chunk will be represented by a <td:tocentry> element (td:role="listitem".
<td:toclink>
Within a <td:tocentry> there will be exactly one <td:toclink> which is the link to the resolved chunk. When styling for HTML, the @href attribute should be passed as-is to the resulting <a> tag, and post-processing will resolve it automatically. The content of the link will be the title of the chunk. It carries these attributes.
@type
The name of the chunked element.
@td:role
link-fileref
@td:hrefAttrs
Names the @href attribute. The base stylesheet handles links automatically by determining the link target attribute from this attribute.
@targetId
The ID attribute value on the chunked element. This value will be generated when the chunk doesn't already carry an ID.
<td:toclevel>
When a level in the ToC has chunked child elements, they will be included in this element within the <td:tocentry>, after the <td:toclink>.
Styling

Once a document has been decorated with @td:role and other attributes, it becomes possible via XSLT to apply default styling to decorated elements. Titania Delivery provides such an XSLT module, which can be referenced from any other XSLT at the URN urn:titania:xsl:modules:decorated-html.xsl.

In addition, the document type can specify a /HARP-META/html.xsl file containing universal HTML styling rules for its elements. If present, this file will automatically be included via the decorated-html.xsl module. This is most necessary for non-HTML table models, which will not be handled by the decorated-html.xsl transform by default, but may include much more. The built-in DocBook 5 document type, for example, includes the entire DocBook XSLT suite via an inclusion from html.xsl.

All that should be needed for most styling purposes is to include the decorated-html.xsl module. For example, the preview stylesheet for most non-DITA doctypes can, in their entirety, look like this:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0">
  
  <xsl:include href="urn:titania:xsl:modules:decorated-html.xsl"/>
  
</xsl:stylesheet>

This will automatically apply the styling in html.xsl, as well as the default styling for elements decorated with @td:role.

Chapter 12: Custom Processing of XML Content

There are several XSLT files you can optionally place in your document type to automatically pre-process any document of that type.
/HARP-META/preprocess.xsl
This file will be applied early in the processing of any content using the document type. It will be applied after XInclude resolution and application of default attributes, but before any other processing occurs. You can use this hook to perform TD-specific cleanup or modifications to your document. This is valid for both non-DITA and DITA document types.
/HARP-META/preprocessTopic.xsl
When processing a DITA map, this transform will be applied to topics referenced by that map before they are processed and contextualized.
/HARP-META/postprocessTopic.xsl
When processing a DITA map, this transform will be applied to contextualized topics after all other processing has completed.
/HARP-META/preprocessMap.xsl
When processing a DITA map, this transform will be applied after the map is flattened but before other processing is applied.
/HARP-META/postprocessMap.xsl
When processing a DITA map, this transform will be applied after all other DITA pre-processing, including topic contextualization.

For example, say you wanted to impose a rule that for Titania Delivery, all second-level <topicref> elements should be presented as a single chunk. You could use /HARP-META/preprocess.xsl to add chunk="to-content" to such topicrefs.

<xsl:template match="/&map;/&topicref;/&topicref;[not(@chunk)]">
  <xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:attribute name="chunk">to-content</xsl:attribute>
    <xsl:apply-templates/>
  </xsl:copy>
</xsl:template>

Chapter 13: Custom Preview Stylesheets

For custom XML document types, you can specify the XSLT to use when generating the preview used in the administrative screen by placing a preview.xsl file at the root level of the document type.

Preview stylesheets should generally follow the same conventions used for portal transforms. The resulting HTML will be inserted into the preview panel, so should only contain elements suitable within an HTML <body> element. Generally, the root of the HTML output should be a <div> element. If your document type has a /HARP-META/doctype.xml file or is based on DITA, graphics and links will automatically be resolved by the application; the @href attributes in the source do not have to be manipulated by the stylesheet.

Chapter 14: Custom Metadata Rules

The rules used to extract metadata from content are extensible.

14.1: Extracting Metadata from XML Files

You can customize metadata gathering using XSLT to transform the XML content into a simpler XML structure listing the metadata names and values for that content. The XSLT should be placed at /HARP-META/metadata.xsl either in a document type or a project. If placed within a document type, it will apply to all content using that document type in any project. If placed within a project, it will apply to all XML content in that project.
Note: This means that the XSLT should be able to successfully be applied to all XML content that might appear in the project, whatever its structure or doctype. It should probably contain template rules matching only on the expected root element names and/or DITA classes.

The output of this XSLT must follow this structure:

  • The root element can have any name.
  • Directly within the root element there must be a list of <metadata> elements. The @name attribute specifies the metadata name, and the textual contents of the element are its value.
  • If a given metadata name should have more than one value, then generate multiple <metadata> elements with the same @name attribute.
  • By default, metadata assigned to DITA maps will also be applied to the contextualized copies of the topics referenced by that map. If you do not want this to be the case, specify cascades="false" on the <metadata> element.

Here is an example structure that represents two metadata fields:

  • mdName=First Value, Second Value
  • mdName2=Second Metadata Entry, and will not cascade to topics.
<root>
  <metadata name="mdName">First Value</metadata>
  <metadata name="mdName">Second Value</metadata>
  <metadata name="mdName2" cascades="false">Second Metadata Entry</metadata>
</root>

The output of all applicable metadata transforms will be combined together and applied to the content during processing.

Capturing Keywords as Metadata

If we want to capture all of the DITA keyword elements in a topic and store them in a keywords multi-valued metadata, our XSLT might look like this:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="2.0">
    
    <!-- Create the root tag -->
    <xsl:template match="/">
        <root>
            <xsl:apply-templates/>
        </root>
    </xsl:template>
    
    <!-- Capture keywords -->
    <xsl:template match="keyword">
        <metadata name="keywords">
            <xsl:value-of select="normalize-space(.)"/>
        </metadata>
    </xsl:template>
    
    <!-- Suppress text -->
    <xsl:template match="text()"/>
    
</xsl:stylesheet>

14.2: Extracting Metadata from Non-XML Files

Many non-XML document formats, like PDF and Microsoft Office files, have the ability to carry metadata embedded within them. Titania Delivery can extract this metadata and use it for its own purposes.

There are a wide variety of metadata schemas used in non-XML content types. For metadata to be most useful in Titania Delivery, it should follow the same schema across all files. For this reason, Titania Delivery allows you to specify a mapping file that maps the metadata keys embedded in various file types to the metadata keys used within the system. These mapping files can be added to document types that are associated with a project, or with the project itself. Whether the mapping file exists in a project or document type, it must be placed at /HARP-META/mappings.conf. When extracting metadata from a piece of content, all mapping files available to the project and its associated doctypes will be used.

In addition to using embedded properties as metadata, Titania Delivery will also use any title metadata as the object's title for purposes of searching.

You can determine the metadata keys present on a given file by looking at its Details tab.

Metadata Mapping Config File Format

The HARP-META/mappings.conf file format is a simple text format describing mappings between embedded metadata names and Titania Delivery metadata names, grouped by file type. For example:

# Settings for all applicable file formats
[all]
dc:Title=title

# Settings for PDF files
[pdf]
cp:subject=Description
# This line will look in the metadata for the first key it finds,
# and use that value for 'author'.
dc:author|meta:author|pdf:author|Author|dc:creator|producer=author

If a metadata key is not represented in the settings file, it will not be copied into Titania Delivery.

There are three types of lines allowed in the mappings file.

Comments
Lines beginning with a pound sign (#) are treated as comments.
Groups
Lines that begin and end with square brackets ([]) are groups. The contents of the square brackets is either the file extension or the MIME type of the files for which the following mappings apply. For example, [pdf] rules will apply to any file with a .pdf extension, while [application/pdf] will match any content with that MIME type. The special group [all] can be used for mappings that apply for all non-XML content.
Mappings
All other lines are considered mapping lines. A mapping line consists of:
  1. The metadata key or keys from the source file. Multiple metadata keys can be combined with a pipe (|) character. If multiple keys are specified, the system will take the first metadata value it finds for one of the keys, scanning from left to right, and store it in Titania Delivery.
  2. An equal sign, =.
  3. The Titania Delivery metadata name to use for the file metadata key.

Only the metadata key is required. If no mapping is specified, the metadata name will be used as-is. For example:

[all]
title
description
dc:author=author

These rules will look for title and description metadata keys and store them with those names in Titania Delivery, while dc:author metadata will be stored as author.

If multiple keys are mapped to the same Titania Delivery metadata name, the unique values of those keys will be combined and stored as distinct values in the Titania Delivery metadata. For example:

description=description
dc:description=description

If a file contains both description and dc:description entries, and their values are different, both values will be stored in the description metadata entry in Titania Delivery. To force the system to choose one or the other, combine them with |, e.g.

dc:description|description=description
Specifying Delimiters for Multiple Values

Some systems make it difficult or impossible to create embedded properties with multiple values. In such cases, multiple values can be specified with all values concatenated together into a single value, separated by a delimiter. Delimiters are specified using the special group [delimiters]

In the [delimiters] group, the key to each entry is the Titania Delivery metadata name, and the value is the delimiter to use. The default entry, if any, will be applied to all metadata without a specific rule. If there is no default entry, then only those metadata fields with explicit rules will have their values split.

The delimiter value should be the exact character or string of characters used to separate values. The following special rules apply:

  • A value of \t, \n, or \s indicates tab characters, newline characters, or all whitespace characters, respectively.
  • A value beginning and ending with a forward slash character (/) indicates that the value is a Regular Expression.

Here is an example delimiter configuration:

[delimiters]
default=,
platform|audience=;
country=\n
author=/[;, ]/

This example specifies the following rules:

  • The default delimiter, applied to all metadata properties extracted from embedded properties, is a comma.
  • The delimiter for platform and audience metadata is a semicolon.
  • The country metadata will be split at newlines.
  • The author metadata will be split using a regular expression matching on semicolons, commas, or spaces.
Applicable File Types

The HARP-META/mappings.conf file will be supported with the following file formats:

  • PDF
  • Microsoft Office
  • RTF
  • MP3
  • MP4
  • PNG
  • JPG
  • TIFF
  • DWG
  • EPUB
Default Mappings

Titania Delivery includes a built-in pseudo-doctype that carries the default mappings file. That document type is called Non-XML Metadata Mappings. This doctype will automatically be added to new projects by default. The default mapping file's contents are as follows.

# This file defines mappings from embedded metadata in file formats like PDF,
# Microsoft Office, and some graphic formats, to the metadata keys to use
# for those values in Titania Delivery.
#
# Each rule consists of a embedded_name=td_name pair. If the embedded name
# and the TD name are identical, the TD name is optional. For example, to
# map the embedded 'title' metadata to the name 'title' in TD, the rule
# can simply be coded as "title".
#
# If multiple embedded metadata names contribute to a multi-valued TD metadata
# entry, provide multiple rules. For example:
#
#     a=z
#     b=z
#
# Will result in the TD metadata 'z' having both the values from 'a' and 'b'.
#
# To have a piece of embedded metadata copied into multiple TD metadata
# entries, again, provide multiple rules. For example:
#
#     a=x
#     a=y
#     a=z
#
# Will cause the embedded metadata 'a' to be copied to x, y, and z in TD.
#
# To populate a piece of metadata from any one of several metadata, but not
# all of them, specify each embedded metadata key in the same rule, separated
# by a pipe (|) character. For example:
#
#     a|b|c=d
#
# - Will use 'a' if it is present, ignoring 'b' and 'c'
# - Will use 'b' if it is present and 'a' is not, ignoring 'c'
# - Will use 'c' if neither 'a' nor 'b' are present.
#
# Rules for specific formats can be grouped beneath a [type] line, where the
# type is either the file extension or MIME type of the file type in quesiton.
# Rules occuring before any groups, or occurring in the [all] group, apply to
# all non-XML file types.

[all] # These mappings apply for all file types

title|Title|dc:title=title
subject|Subject|dc:subject|cp:subject=subject
Revision-Number|cp:revision=revision
author|Author|Last-Author|meta:author|meta:last-author=author
Creation-Date|created|meta:creation-date|dcterms:created=created
Last-Modified|modified|dcterms:modified=modified

# Duplicate language for both 'lang' and 'locale' metadata.
language|Content-Language=lang
language|Content-Language=locale

Part III: Portal Security Configuration

Organizations can configure OpenID Connect authorization providers, SAML 2.0 identity providers and/or LDAP repositories to secure portals owned by the organization. An organization may have any number of connected authentication services, but a given portal may only be associated with a single authentication system.
Note: Security, LDAP, SAML 2.0 and OpenID Connect are all very complex. A deep overview of these subjects is outside the scope of this document. Users attempting to set up security configurations should have knowledge of how LDAP, SAML 2.0, and/or OpenID Connect function before reading this section.
Organization Security Configuration Page

III.1: Configuring OpenID Security Profiles

Note: Oberon Technologies cannot guarantee that all OpenID Connect providers are compatible with Titania Delivery and its underlying OpenID framework and implementation. Experience has shown that different software producers have implemented the OpenID and OAuth2 standards in slightly different ways that may be incompatible with Titania Delivery. The OpenID providers that are known to have been configured to work with Titania Delivery are:
  • Microsoft ADFS
  • Salesforce
Note: The Titania Delivery OpenID framework may not work with Symantec SiteMinder v12.8 OpenID providers, due to an incompatibility in handling some values during the protocol exchange.
Note: Current customers who intend to use OpenID authorization should contact Titania Product Support (through their support portal) to arrange testing on a non-production Titania Delivery platform, to verify that their provider and configuration will work as expected. New customers who intend to use OpenID authorization should contact their project manager for support.
Note: Oberon Technologies does not provide detailed technical support for setting up and configuring a customer's OpenID provider.
A Titania Delivery system administrator will need the following configuration items in order to set up an OpenID provider. Consult your OpenId provider’s documentation for details on how to create and register a Titania Delivery instance as an authenticated client application (or relying party).
  1. The “/.well-known” endpoint — this is a provider-specific URL which returns essential information about an OpenID application. Some examples include “https://[server name and app ids]/.well-known/openid-configuration, where [server name and app ids] is the provider host name plus any identifiers specific to the desired client application. Each OpenID provider and each registered client application on the provider will be unique.

  2. The Client Id is generated by the authorization provider when a client application is created by your OpenID system administrator.

    Note: Some providers allow Client Id values to be user-generated.
  3. The Client Secret is also generated by the provider when the client application is created.

  4. When configuring the provider, the OpenID system administrator must specify the redirection URI for your Titania Delivery platform. This URI must conform to the following pattern: https://[hostname]/login/oauth2/code where [hostname] is your Titania Delivery server platform.

    Note: OpenID providers use various names for the redirection URI, such as “Callback URLs” or “Authorized redirect URIs”.

This section describes the configuration of OpenID providers with Titania Delivery. The details of registering OpenID clients such as Titania Delivery vary according to the provider. Titania Delivery follows the OpenID Connect 1.0 specification.
  1. Go to, or create, a Titania Delivery Organization to add a new authentication system.
  2. Click Authentication Systems.
  3. In the Portal Authentication Systems page, click New in the OpenID Providers section.
    The OpenID Provider window will appear.
  4. In the Display Name field, enter a label for the OpenID provider. This label should be unique among all OpenID profiles on your TD platform, to avoid ambiguity when selecting a security profile for a portal.
  5. In the Issuer field, enter your OpenID Provider Issuer URL. (See Section 2 of the OpenID Connect 1.0 specification and your OpenID provider documentation.)
    Note: TD expects to find a JSON document describing the OpenID provider configuration by concatenating /.well-known/openid-configuration to this URL as described in Section 4 of the OpenID Connect 1.0 specification. DO NOT INCLUDE the /.well-known/openid-configuration in the issuer URL.
  6. In the Client ID field, enter the Client Id created or generated when the client application was created by your OpenID system administrator.
  7. In the Client Secret field, enter the Client Secret generated when the client application was created by your OpenID system administrator.
  8. If your OIDC provider requires custom scopes to process an authorization request, enter them in the Custom scopes field, separated by spaces. Otherwise, leave this field blank. If present, custom scopes will be appended to the default scopes used for the OpenID Connect protocol.
  9. Click the Save button.
    Note: Before saving the configuration, the system will validate the issuer URL. If the URL cannot be reached, the configuration will not be saved.
Titania Delivery will display the newly-created OpenID Connect provider in the Portal Authentication Systems window and will store the configuration information, dynamically updating the server configuration.
Note: If the configuration information needs to change, the Save button will update the existing OpenID configuration.
    Note:
  • The OpenID administrator must ensure the authorization provider supports the “openid” scope and either the “email” or the “upn” scopes. This may require modifying the default configuration with some OpenId Connect providers when setting up a client application.

  • The OpenID administrator must ensure the authorization provider complies with Authorization Code flow as described in Section 2.1.5.1 End-User Grants Authorization and RFC 6749.

III.2: Configuring SAML Security Profiles

How to set up a Titania Delivery security profile to use an external SAML IdP for portal authentication.
Note: Titania Delivery administrators who are creating SAML security configurations should have a basic understanding of SAML 2.0 as described in the SAML V2.0 Technical Overview. Titania administrators must work with a SAML administrator in their organization who can configure the chosen SAML Identity Provider (IdP) application to register Titania Delivery as a SAML Service Provider (SP), and configure it appropriately. The details of registering service providers with an IdP varies by vendor; consult your IdP's documentation for details on how to register and configure service providers.
Overview

The high-level steps to set up a TD security configuration for a SAML IdP are:

  1. (Titania Administrator) Export the Titania Delivery SAML Service Provider metadata as an XML file. This is the "SP metadata".
  2. (SAML Administrator) Register Titania Delivery with the chosen SAML IdP, using the information in the SP metadata, and additional requirements as specified in SAML Identity Provider SSO Configuration.
  3. (SAML Administrator) Export the "federated metadata" from the IdP, or provide the URL for the federated metadata.
    Note: Alternatively, the required information can be entered manually in the TD security configuration form. See Manual SAML Security Configuration.
  4. (Titania Administrator) Set up the SAML security profile in Titania Delivery, either manually, or by importing the federated metadata.
  5. (Titania Administrator) As needed, associate the SAML security configuration with portal(s) that will use this IdP for authentication. See Portal Security

SP Metadata and certificates

The Organizations Authentication Systems admin page provides persistent links to the following SAML resources, under the SAML 2.0 Identity Providers heading:

Service Provider Metadata
The Entity Descriptor (SP metadata) XML for Titania Delivery, for use in configuring IdPs to accept authentication requests from Titania Delivery. The URL for this metadata is persistent for the Titania Delivery platform, and can be registered with IdP services that support automatic metadata refresh.
SP Signing Certificate
The certificate corresponding to the key that will be used by Titania Delivery when signing authentication requests.
SP Encryption Certificate
The certificate that must be used by Identity Providers when encrypting authentication responses being sent to Titania Delivery.
Important: The keys used by all SAML services generally have expiration dates of 6 months to 1 year, and will be replaced over time. Be sure to consult your Identity Providers and Titania Software to ensure that keys used for SAML authentication remain up-to-date.
Entity ID and IdP-Initiated SSO

If you plan to use IdP-initiated SSO (as described in SAML V2.0 Technical Overview, Section 5.1.1), you should configure your SAML identity provider to send SAML SSO <Response> messages with a form control or URL query parameter named RelayState whose value is the fully-qualified URL of the requested portal. Otherwise, if there are multiple SAML IdP Configurations with the same Entity ID on the TD platform (across all organizations), Titania Delivery may fail to fulfill the request. If the RelayState parameter is not provided, then Titania Delivery will not display a portal page.

If you create SAML IdP Configurations with duplicate Entity IDs, and do not use IdP-initiated SSO, this will not be a problem. Duplicate Entity IDs will typically be created automatically when importing IdP federated metadata from the same provider.

Additional SAML user assertions

SAML IdPs may be configured to include additional assertions (properties) about the user with a successful authentication response. These properties are stored with the portal user object, and are available for use in portal themes and for dynamic content filtering.

Is it possible for the authentication request to specify what properties to include in the response, or is it entirely up to the IdP?

SAML user assertions do not need to be registered with Titania Delivery in advance. All such assertions in an authentication response are put into a map object, using the SAML technical name as the key. This map is available as the user.properties property.

Provide example of Assertion markup and freemarker code to access user property.

Microsoft ADFS IdP requirements

From TD version 4.5.0 and on, Microsoft ADFS SAML IdP Relying Party Trusts property must be set as described below.

An ADFS administrator must use a PowerShell command to modify the SamlResponseSignature property of any relevant Relying Party Trusts.

Use the following PowerShell command to view the property:

Get-AdfsRelyingPartyTrust -Name [Relying Party Trust Name]
where [Relying Party Trust Name] is the Display Name of the trust. Search for the value of the SamlResponseSignature property. The required value is MessageAndAssertion.

Use the following PowerShell command to set the property:

Set-AdfsRelyingPartyTrust -Name [Relying Party Trust Name] -SamlResponseSignature MessageAndAssertion
where [Relying Party Trust Name] is the Display Name of the trust.

III.2.1: Manual SAML Security Configuration

Manually creating a SAML security configuration.

From the Organizations Authentication Systems page in the TD admin application, click the New... button next to the SAML 2.0 Identity Providers heading. This dialog allows the manual configuration of the Identity Provider settings.

SAML Identity Provider Details Dialog
Name
The display name for the configuration. This is not used in the authentication process, and is simply a label for the configuration.
Organization Name
The Organization Name for the IdP.
Entity ID
The entity ID for the IdP service.
NameID Policy
The NameID policy to specify when making authentication requests with the IdP.
Note: Not all Identity Provider implementations honor the NameID policy specified in authentication requests. It is often the case that the NameID value must be separately configured in the Identity Provider service's configuration settings.
Important: The NameID provided by the IdP is used as the persistent user ID within the portal for purposes of tracking comments made by the user, and assemblies owned by the user. SAML 2.0 allows for transient NameIDs, such that every authentication results in a different value for the same user. While this will work for authentication purposes, Titania Delivery will treat each new session as if it were a new user, and so the ability to manage assemblies and comments from previous sessions will be lost. In general, we strongly recommend using a persistent NameID value, such as e-mail address or other persistent ID.
Display Name Attribute
The attribute that will be included in any authorization response which corresponds to the user's display name.
Single Sign-On (SSO) Endpoint URL
The URL that the IdP service uses to accept authentication requests. This may be found in the federated metadata provided by the IdP, or directly from the IdP configuration.
Request Mode (SSO)
The mode to use when issuing authentication requests to the IdP service. It is recommended to use "HTTP POST".
Single Log Out (SLO) Endpoint URL
The URL that the IdP service uses to accept logout requests. This is typically the same URL as the Single Sign-On Endpoint URL. It may be found in the federated metadata provided by the IdP, or directly from the IdP configuration.
Request Mode (SLO)
The mode to use when issuing logout requests to the IdP service. It is recommended to use "HTTP POST".
Sign Authorization Requests
When checked, authorization requests sent to the IdP will be signed using the key corresponding to Titania Delivery's signing certificate. It is recommended always to sign requests, for greatest security.
X.509 Certificate String
The Base64-encoded string representing the certificate that will be used to verify signed values in authentication responses provided by the IdP. This value is provided in the IdP''s federated metadata, or it can be obtained directly from the IdP configuration. It is recommended to configure your IdP to sign responses, for greatest security.

III.2.2: Importing SAML IdP Settings

SAML IdP service provider settings may be exported from the IdP as a "federated metadata" XML file; alternatively, it may be retrieved from a specified URL on the IdP.

To import the federated metadata and automatically configure the TD security profile, select the Organizations Authentication Systems page in the TD admin application, and click the Import... link next to the SAML 2.0 Identity Providers heading.

SAML IdP Import Dialog

SAML federated metadata can be imported either from a public URL or by uploading the metadata file. Select the appropriate radio button and provide the URL or file. Then click OK.

Note: The Entity Descriptor markup will not include the display name property, which is required for Titania Delivery to function as expected. After importing the IdP metadata, open the resulting details and provide the appropriate value in the Display Name Attribute setting. By default, Titania Delivery uses displayName for this purpose if available, and will use the provided NameID value otherwise.

III.2.3: SAML Identity Provider SSO Configuration

Configuring a SAML Single Sign-On (SSO) provider for security and compatibility with Titania Delivery.

Best practices for configuring a SAML identity provider to register Titania Delivery as a service provider.

Signing Requests and Responses

Requiring signed requests is one way the IdP can verify that requests are sent from a trusted relying party. The IdP should configured to required signed requests, and to send signed responses.

Assertion Consumer Service URL

The Assertion Consumer Service of a SAML relying party is responsible for receiving and processing SAML responses . On the Titania Delivery platform, the assertion consumer service is available at a URL like https://[td-hostname]/portals/saml2/assert. The <AssertionConsumerServiceURL> element of the TD service provider metadata XML file contains the specific value for each TD platform. When registering Titania Delivery as a service provider with your IdP, the configuration will include this URL, which is used to route responses from the IdP to Titania Delivery.

The assertion consumer service URL provided in the SP metadata may be over-ridden in SAML authentication requests to the IdP. All requests originating from Titania Delivery will include an <AuthnRequest>/@@AssertionConsumerServiceURL attribute with a value like https://[td-hostname]/portals/saml2/assert/[registration-id], where [registration-id] is an arbitrary alphanumeric string. The SAML 2.0 Core specification requires IdPs to:

  1. Recognize URLs like this as valid assertion consumer service URLs for this service provider. Different IdPs will have different ways of configuring this. It might be possible to define the valid assertion consumer service URLs using a pattern with a wildcard, such as https://[td-hostname]/portals/saml2/assert/*. In other IdPs, if the requests are signed, they will use the assertion consumer service URL provided in the request.
  2. Return the authorization reponse to the URL provided in the request (including the registration-id).

RelayState

The SAML 2.0 Bindings specification defines a "RelayState" mechanism for preserving state between SAML entities during message exchanges. The specification requires responders to faithfully include any received relay state data in their response. In HTTP POST and redirect protocols, relay state is transmitted as a URL-encoded value of a URL parameter (or form control) named "RelayState".

Authentication requests originating from Titania Delivery will include a "RelayState" URL parameter (or form control) with a value representing the secured portal URL. This value must be returned with the authorization response.

For IdP-initiated authentication, the SAML authorization response sent to the assertion consumer service must include a "RelayState" parameter with the URL-encoded value of the URL of the requested portal.

III.3: Configuring LDAP Connections

Titania Delivery connects to your authentication server via the LDAP protocol. The specific configuration can be set up in the administrative application.

LDAP configurations are associated with Organizations. To establish an LDAP connection, select the Security category of the organization and click the New... link next to the LDAP Connections header to raise the LDAP Security Configuration dialog.

LDAP Configuration Dialog
Name

The field names on this page do not appear in bold like in other sections.  For example, see Part III.1.

A name identifying the LDAP connection.
Host
The hostname or IP address of the LDAP protocol listener on the directory server.
Port
The port number on which to connect to the directory server.
Use Secure Connection
Whether to use LDAPS to communicate with the directory server.
Base DN
The full Distinguished Name of the node within the directory server.
Admin User DN
The full Distinguished Name of the account Titania Delivery will use to connect to the directory server.
Admin Password
The password for the account identified in the Admin User DN.
Additional User DN
Additional Distinguished Name segments to prepend to Base DN when querying the directory server for users.
User Name Property
The property in the directory system that will be used to test the user name for authentication attempts.
Additional User Filter
An LDAP query to append to the default query used when searching for users in addition to the User Name Property.
User Properties
Additional object properties from the directory server to retrieve when a user logs in. These properties will be made available in the properties collection on the PortalUser object available to all portal pages once a user logs in.
Additional Group DN
Additional Distinguished Name segments to prepend to Base DN when querying the directory server for user groups.
Group Name Property
The property in the directory system that will be used as the group's name.
Additional Group Filter
An LDAP query to append to the default query used when fetching groups.
Group Membership Determination
This is the method used to determine whether a user is a member of a group.
None
No group membership checks will be performed, and group-based access control will be disabled.
User Attribute
Use this when the directory server supports a dynamic property on the user object listing the groups to which a user belongs. For example, the memberOf property in Active Directory.
Group Query
Use this when the directory server does not support any sort of memberOf property. When group-based access control is configured for a portal, Titania Delivery will first authenticate the user, and then execute a second query to verify that the user is a member of the specified group.

III.4: Portal Security

A portal can be configured to allow or require a user to authenticate before being granted access.

You assign a security system to a portal using the portal's Authentication tab.

Portal Security Association

The list of available security configurations is populated by those available in the organization that owns the portal, as well as those set up in the System Administrators organization, which are global. Once the portal is associated with a security configuration, additional details can be specified.

If using an LDAP-based configuration, you can specify the lists of users and/or organizations within the repository that will have access to the portal. You can also specify which users should be granted Comment Moderator rights within the portal, meaning that they can edit, delete, and set dispositions of other users' comments.

Note: Only Owners and Administrators in the security configuration's organization can modify the LDAP allowed users and organizations. If the security configuration was defined in the System Administrators organization, only members of that organization will have this privilege.

If using an OpenID-based or SAML-based service, you can only specify the list of NameID values to be granted comment moderator rights.

If using OpenID authentication, you may select more than one OpenID provider for the portal. The portal theme must include a template file at pages/oauth2login.ftl, which will show the available OpenID providers from which the user may choose one. Theme developers may customize the style of this page. A default template is provided in the Titania Default Theme.

To unset all security providers for the portal, click the Delete button.

After making changes, click the Save button.

The default portal theme includes a page that can be used to review the current session's security information, including the authenticated user, their properties, and the results of the dynamic security script, including print() output. This page can be included into any portal theme, though it is generally not linked to from any of the main pages, and primarily exists as a debugging tool. It can be found at the URL /portalUrlPath/pages/userInfo. This page can be used to view the properties and, for SAML 2.0, NameID value for the currently logged-in user, as well as details about the dynamic filter script output, if any.

III.4.1: OIDC Bearer Tokens

Portal requests may be authenticated by means of an OIDC Bearer token.

For portals that use OpenID Connect (OIDC) authentication, portal requests may use Bearer tokens to authenticate. The OIDC provider must support token generation and validation (and optionally, refreshing tokens). The process for configuring OIDC providers for token support will vary with the provider. Consult your OIDC provider administrative documentation for details and requirements. The OIDC client registration must support scopes "openid" and "offline_access", and conform to the OAuth2 "Authorization Code" flow. Titania Delivery only supports OIDC bearer tokens in JWT (JSON Web Token) format.

The OIDC security configuration must be set up in an organization accessible by the portal, and the portal must be configured to use the OIDC security configuration.

The process for using Bearer tokens is:

  1. A third-party application (or web page) authenticates itself with the OIDC provider.
  2. The client application obtains a bearer token from the OIDC provider.
  3. The client makes an HTTP request to a portal URL, and includes the bearer token. The bearer token may be attached to the request in one of these ways:
    • Put the bearer token in the Authorization request header, preceded by the the literal string "Bearer ". (This is the most secure, and preferred, method. See "Authorization Request Header Field" section in the "OAuth 2.0 Bearer Token Usage" RFC for additional details.)
    • In a POST request, add the bearer token as the value of a form url-encoded parameter named "access_token" in the body of the request. (This is less secure, and should be used only when the client application is unable to set request headers. See "Form-Encoded Body Parameter" section in the "OAuth 2.0 Bearer Token Usage" RFC for additional details.)
    • In a GET or POST request, add the bearer token as the value of a URL query parameter named "access_token".
      Note: This is considered a higher security risk than the other methods. By default, Titania Delivery does not support this method. Customers may request Oberon Technologies to enable this method for their site after acknowledging and accepting the security risks. (See "URI Query Parameter" section in the "OAuth 2.0 Bearer Token Usage" RFC for additional details.)
  4. Titania Delivery will validate the token using information from the OIDC provider. If the token is valid, the request will be processed normally. If the token has expired, the portal will attempt to obtain a fresh token, and, if successful, will return a 4xx HTTP response with the new token in the WWW-Authenticate response header. The client application may resubmit the request with the new token, or take other action. If the token cannot be validated, a 4xx HTTP response will be returned to the client.

The following industry standard specifications provide additional details about the processes and entities described here:

III.4.2: Troubleshooting SAML Authentication

Tips for troubleshooting SAML authentication problems.

The Titania Delivery SAML authentication framework is designed to implement the preferred security recommendations available in the SAML protocol. There are a variety of SAML Identity Provider (IdP) applications available. If your SAML IdP supports the recommended security features that TD uses, it will be possible to configure TD to use your SAML IdP.

Note: Each Titania Delivery customer is responsible for configuring their SAML provider with details of the Titania Delivery portal(s) that will use SAML authentication. The user interface techniques and terminology for configuring IdPs will be different for each IdP vendor, but the authentication protocols and messages used by your IdP and Titania Delivery are standardized in accordance with the SAML 2.0 specification.
SAML Tracer

SAML protocol tracers are available as plugins or addons for various browsers. One such tool is SAML Tracer for Mozilla Firefox. Oberon Technologies make no representation or guarantees about the safety or suitability of this tool. Consult your enterprise IT security policies before installing or using any SAML tracer plugin.

A SAML tracer can display the details of the various SAML requests, responses, and redirects that occur during SAML authentication. This can help an experienced SAML administrator identify the possible causes of a SAML authentication problem.

Troubleshooting
  1. Review the configuration of the SAML security profile that is associated to the portal. Be aware that many security profiles with similar settings and names may have been defined on your system.
  2. Check the IdP configuration for the IdP to verify that the settings agree with settings in the SAML security profile, and meet the requirements described in SAML Identity Provider SSO Configuration.
  3. Make sure all X.509 security certificates are up to date on your SAML IdP. Instructions for checking security certificates should be available in your IdP's administration guide.
  4. Do the certificate strings on Titania Delivery's SAML configuration form match the expected data configured (or auto-configured) on your SAML IdP? Some IdP’s can be configured to auto-update if Titania’s “Service Provider Metadata” changes.
  5. Does the IdP’s X.509 certificate string match the SAML configuration on Titania Delivery?

III.5: Secure Portal Session Timeout

System behavior when a secure portal session times out due to inactivity.

By default, all Titania Delivery portal sessions time out after 15 minutes of inactivity. When the portal is unsecured, or the user is browsing anonymously, a new session will automatically be created when the user navigates to a portal page after a timeout. The user will typically not notice any interruption of service.

Contact Oberon Support to request a change to the portal timeout value. Any change will apply to all portals on the site.

When the user is authenticated in a portal session that times out, the behavior may be different, depending on what authentication method is used and how the system is configured.

Note: To reduce the risk of unauthorized portal access, all portal pages should include a "Logout" button, and users should be advised to log out of the portal before leaving the site. Do not rely on session timeouts or closing the browser window.
OpenID Connect authentication

When an OpenID-authenticated portal session expires, the system will automatically reauthenticate a new session.

SAML2 authentication

In the default deployment configuration, when a SAML-authenticated portal session times out, the system will automatically reauthenticate a new session with the SAML IdP.

The default behavior may not be desired in all cases, because it raises the risk of "tailgating" access by unauthorized users. For example, if a secure portal is available on a public computer, and one user fails to log out of the portal, another user with different privileges could subsequently gain access to the portal, even after the session has timed out.

Customers may request Titania Support to change the deployment configuration for their site, to force re-authentication after session timeouts. This change will apply to all SAML-authenticated portals on the site. Users will be challenged for login credentials every time they attempt to navigate to a portal page after the session has timed out.

LDAP authentication

When an LDAP-authenticated portal session expires, the user will be challenged to re-enter credentials when navigating to a new portal page or refreshing the current page.

III.6: Dynamic Content Filtering

In addition to global metadata filters, administrators can implement dynamic filtering based on a user's identity using Javascript.

The script will be given a user variable containing the identity of the authenticated user, if any. If there is no user, it will be null. The script should end with a return statement returning one of the following.

  • A string containing a search query for content access filtering. This query will be included in all content queries during the user's session. The user will only be able to see content that matches the given filter query.
  • false, in which case the user will be prevented from accessing the portal entirely.
  • null or undefined (or no return statement at all), which will result in no additional filtering being applied.
Important: Dynamic filtering does not apply to monolithic views of DITA map content. This means if there are cases where the topics within a DITA map will be visible to some users but not others, you should disable the Monolithic Map feature in your portal theme(s).
Interrogating the User's Properties

The identity provider may provide additional properties associated with the authenticated user. This script can access those properties to make decisions about what content the user can access using the following properties of the user object.

Map properties
The user properties pulled from the authentication service. For SAML-secured and OpenID-secured portals, this will contain whatever attributes were included in the authentication response message sent by the Identity Provider. For LDAP-secured portals, this will contain the properties listed in the LDAP connection configuration. NOTE: This collection allows for multi-valued properties, so values are arrays, not single values. Properties with single values will be lists of size 1.
boolean hasProperty( String name, String value)
Tests whether there exists a property with the given name containing the given value. If the value is not specified, this method tests whether the property exists.

The script also provides the following utility functions.

escape(str)
Escapes reserved characters in the given string so that it can be used inside a search string.
quote(str)
Like escape(), but additionally wraps the text in quotes.
print(str)
A degugging function that prints the given string to the execution log. See below for how to read the log output.

The provided script will be executed at the following times.

  • If the portal supports anonymous access, the script will be run when a user first accesses the portal, with a null value for user.
  • Immediately after a user authenticates with the portal, with the user populated with the username and properties provided by the identity provider.
  • If the portal supports anonymous access, the script will also be run after an authenticated user logs out, again with null for user.
Script Testing

Once the script is entered, it can be tested using the Test Script section of the page, which allows you to construct a user and their properties to run through your filter function.

  1. Specify whether or not an anonymous user will be used.
  2. Specify the username and properties for the dummy user, if desired.
    Note: User properties are multi-valued. The provided interface allows you to add multiple values for properties (smaller buttons), as well as adding and removing additional properties (larger buttons).
  3. Click the Test Script button.

The script will be executed on the server, and the results of the script will be printed below the form, including strings passed to the print() function.

The /pages/userInfo page in the default portal theme can also be used to view the script output for the currently-authenticated user in a live portal.

4 Appendix

Appendix A: Search Syntax

Titania Delivery provides a search component to quickly and easily find content within a Portal. While TD retrieves relevant search results using plain-text searches, it also allows for advanced metadata-based queries. The search syntax is the same whether entered in the Portal user interface or used as the query attribute of any of the search tags in the Titania Tag Library. Portal Theme developers can utilize Titania Delivery's search capabilities to customize the content of their Portal pages.

Standard search results are "scored" as a function of the frequency of the search term in a given document and the frequency of that search term within all documents in the Portal. All queries are case insensitive.

Full-text indexing

TD can index the textual contents of the following file formats.

  • XML and HTML Content
  • PDF
  • Microsoft Word, Excel, and Powerpoint
  • Text and Markdown

All other content types, regardless of whether or not they contain textual content, only have their filename indexed.

Indexed Fields

Beyond doing a full-text index, TD indexes text into numerous different fields where possible. These fields have different weightings on search results, so that search results with "hits" in one field may be returned with a higher relevancy score than those with a hit in a different field. The indexed fields, in order of their weighting, are:

  1. title
  2. searchTitle
  3. keywords

The remaining indexed fields are all weighted the same, and all less than the above. Those include:

  • itemKey
  • projectKey
  • filename
  • id
  • contextKey
  • created
  • lastModified
  • content

For example, given two documents, if docA has the term "Titania" in its title field, and docB has that same term only in its content field, a query for "Titania" will result in both documents, with docA returned before docB.

If a document does not have a title, its filename is used for the title field. If a document does not have a search title, its title is used for the searchTitle field.

TD extracts the title and kewords from XML content. In addition, it extracts the searchTitle from DITA topics and maps. The title, description (for generic text), and keywords on non-text-based content, like zip archives or images, can be set in the admin application.

Single Word Searches

The simplest search in TD is a single word. The search engine will match on exact matches and also partial matches. For example, "Big" will match "Big", "Bigger", and "Biggest". The search is case insensitive.

Multi-Word Searches

Search queries containing multiple words are considered as "OR"s by the search engine. For example, a search for Titania Delivery will match documents containing either Titania or Delivery, with documents containing both terms being returned with a higher relevancy than those with only one.

To search for the phrase Titania Delivery place the query in double quotes, as in "Titania Delivery"

Operators

TD supports "AND" and "OR" operators. For example, to find documents containing both the words Titania and Delivery, use the query Titania AND Delivery. Complex queries can be achieved by combining these operators. For example, the query Titania AND Delivery OR Sync will return documents that contain the term "Titania" and also either "Delivery" or "Sync".

The not operator (-) can be use to exclude documents that contain a certain term. A search for -Titania will return all documents that do not contain the term "Titania".

The "*" (asterisk) wild card can be used to mean zero or more of any character. A query of * by itself will return all documents accessible to the portal. A search of *ania will return all documents with words ending in "ania." A search for Del* will return all documents with words starting with "Del". It can also be used in the middle of a word. A search for D*y will return all documents with words starting with "D" and ending in "y", such as "Delivery". As always, these searches are case insensitive.

Proximity Searching

The "~" (tilde) operator can be used to search for words within a certain distance of each other. For example, "Titania Delivery"~2 would return documents where the terms "Titania" and "Delivery" appear with no more than two words in between. Documents with "Titania offers a Delivery solution...", "Titania does Delivery...", or "Titania Delivery" would all be returned.

A document with "Titania specializes in the Delivery of..." would not be returned.

The order of the terms is not important. The words could be found in any ordering so long as the absolute distance between them does not exceed what is specified. Therefore, the same query specified above would also return documents with "...delivery of Titania products...".

Field searches

Searches can be executed on a specific indexed field or fields. For example, to find documents with "Titania Delivery" in the title, the query title:"Titania Delivery" could be used.

Metadata Fields

TD indexes metadata set on documents. The name of the indexed field is the name of the metadata rule followed by _md. The value of that field is an array of all of the metadata values associated with that name.

For instance, if a document has a metadata rule of extension with a value of docx, the metadata field would be called extension_md and the value would be "docx".

If a document has a metadata rule of audience with the values novice and intermediate, the metadata field name would be called audience_md and the value would be ["novice", "intermediate"].

You can test the existence or absence of a metadata field using the special range-based query [* TO *].

audience_md:[* TO *]

To query for all documents that do not have a particular metadata rule:

-audience_md:[* TO *]

To query for documents that have a particular value for a metadata rule:

audience_md:novice

Note: The search engine has a hard limit on the number of results that can be scrolled through of 10,000. For instance, when paginating through large datasets and viewing records 9,990-9,999, attempting to fetch the next page will result in an error.

Appendix B: Freemarker Tips and Tricks

Freemarker is a well-documented server-side templating engine for generating text output. Titania Delivery utilizes it to generate the HTML for Portal pages. There are several constructs and patterns that are commonly used in Portal Themes that are posted here for reference.
Declaring the Titania Delivery Tag Library

In order to use the Titania Tag Library, the following must be included at the top of the template.

<#assign td=JspTaglibs['http://www.titaniasoftware.com/harp/taglib']/>
Includes

Freemarker allows for the modular development of HTML pages. Smaller templates can be combined together to make a full page. To include one template in another, add the following to the destination template, wherever you want the target template to be included:

<#include "path/to/template.ftl" />
Page Variables

Titania Delivery populates the templates with a model consisting of many variables. These variables can be accessed via:

${propertyName.fieldName}
Escapes

It is strongly advised to escape the text received from a data model. Freemarker provides a number of built-in functions to accomplish this. This allows Freemarker to format text in a manner that the destination is expecting. For example, it can escape special characters for insertion into the html page, or format a String so that it can be used as a JavaScript variable. To escape data that will be output directly to a page, use:

${propertyName.fieldName?html}

To escape data passed into JavaScript strings, use:

${propertyName.fieldName?js_string}

For example:

<script>
  var portalTitle = "${portal.title?js_string}";
</script>
Comments

You can mark comments in your template that will not be rendered to the HTML using: <#-- and -->.

<#-- This is a comment. -->
Conditional Processing

Freemarker supports conditional processing via the <#if>, <#else>, and <#elseif> expressions.

<#if condition1>
...
<#elseif condition2>
...
<#else>
...
</#if>
Loops

You can iterate over arrays of values using <#list>.

<ul>
  <#list array as value>
    <li>${value_index} - ${value?html}</li>
  </#list>
</ul>

When iterating through a list, a number of special values become available.

${item_index}
The numerical index within the array of the current entry.
${item_has_next}
A boolean value indicating whether there is another value in the list.
Data Existence

It is sometimes the case that a data model may or may not exist on a page. In these instances, the template must check for their existence before referencing them. This can be accomplished via:

${propertyName.fieldName??}

which returns a boolean value. For example:

<#if modelName.fieldName??>
  ${propertyName.fieldName?html}
<#else>
  <b>No Value!</b>
</#if>
Important: If a Freemarker template attempts to reference a field that is not present, an error message will be rendered at the location of the error, and further rendering will halt, resulting in a broken page. You should always check for the existence of optional properties before attempting to render them.
Default Values

In cases where a default value is available, you can avoid using <#if> by using the default value operator (!).

${propertyName.optionalProperty!"Default Value"}
Attempt/Recover

Often, if there is an error in a template, the page can fail to render entirely, without an error message that could help track down the error. You can alleviate this problem by wrapping part of a template, or even the whole template, in an <#attempt> block, using <#recover> to report any errors.

<#attempt>
<#-- code here -->
<#recover>
ERROR: <pre>${.error?html}</pre>
</#attempt>

If a Freemarker error occurs inside the <#attempt> block, the code after the <#recover> tag will be executed instead, and you can access the error via the .error variable.

Assigning Custom Model Properties

Freemarker templates can declare custom properties using the <#assign> directive. For example:

<#assign customProperty="some value"/>

Or

<#assign customMarkupSnippet>
  <p>Markup Snippet</p>
</#assign>

You can then use the custom model property the same way as you would any other. With the above declarations, this template:

<p>Custom property: ${customProperty?html}</p>
${customMarkupSnippet}

would result in the following markup:

<p>Custom property: some value</p>
<p>Markup Snippet</p>
Passing Parameters to Included Modules

The properties declared using <#assign> are made available for the rest of the page, and remain in effect when processing template language referenced via <#include>. You can use this fact to parameterize behavior in reused template modules. For example, you could have the following in a reused module:

<#-- If trigger is defined and true -->
<#if trigger?? && trigger>
  Optional Stuff
</#if>

In a template using this module, you could assign 'trigger' to true to enable the optional behavior.

<#assign trigger=true>
<#include "otherModule.ftl"/>
Macros

Freemarker allows for creating additional user-defined directives called macros; essentially a reusable template fragment assigned to a variable. For instance, the following macro could be defined:

<#macro greet>
  <#if user??>
    <p>Hello, ${user.userName}</p>
  <#else>
    <p>Hello, Titania Delivery</p>
  </#if>
</#macro>

Later in the template (or in any template that includes a template with the above macro definition), the following could be used to execute the macro:

<@greet/>

Appendix C: SASS and LESS Tips and Tricks

SASS and LESS are CSS pre-processors leveraged by Titania Delivery to ease the maintenance and development of CSS stylesheets. While the official documentation is thorough, there are several important notes that are posted here for reference.
It's just CSS

One could completely ignore the features of LESS, write a .css file as normal but give it a .scss or .less extension, and it would work just fine. If not using the features of one of these languages, a .css extension could be used.

Including in a Portal Page

Titania Delivery takes care of compiling .scss, .sass, or .less to .css behind the scenes. To include a pre-compiled stylesheet in a page, use the following:

<link rel="stylesheet" href="<@harp.themeFileUrl value="/path/to/file.scss" />"></link>
Imports

In both SASS and LESS, one stylesheet can be imported into another by using the @import directive. This helps to keep styles modular and more organized. To import one stylesheet into another, use:

@import "path/to/file.scss";

Variables

SASS and LESS support the use of variables, which help prevent copying and pasting colors, font sizes, and other frequently-reused items.

SASS Variables
@name: value;

Variables are referenced via the syntax $name . For example:

color: $brand-primary;
LESS Variables
@name: value;

Variables are referenced via the syntax @name . For example:

color: @brand-primary;
Nesting Rules

Both SASS and LESS support nesting of rules to avoid long strings of selectors. If we have the following in CSS:

#container {
  color: blue; 
}
#container h1 {
  margin: 0;
}
The equivalent SASS or LESS could be written as:
#container {
  color: blue;
  h1 {
    margin: 0;
  }
}

Troubleshooting Stylesheet Compilation
If the automated conversion to CSS fails for some reason, the Portal page will render without those styling rules. Titania Delivery will, instead, serve the browser an empty CSS stylesheet containing only a comment with the error that caused the conversion to fail. When a pre-compiled stylesheet fails to compile:
  1. View source on the page and find the reference to the LESS stylesheet.
  2. Access that URL in your browser.
The cause of the error will be displayed in a comment. For example:
/*
NameError: variable @harp-primar is undefined in /temp/tmp4943953957435847557less.tmp on line 6795, column 12:
6794     font-family: @headings-font-family;
6795     color: @harp-primar !important;
6796 }

*/

Appendix D: Included Libraries

The following libraries are not visible in the Portal Theme editor, but are available for use within a Portal Theme.

D.1 Third Party javascript Libraries in Portal Themes

A list of third party libraries, versions, and licenses included Titania Delivery portal themes.

Most portal themes in production use are customized from the built-in default theme, or from a starter theme provided with the platform. All of these themes include third-party open-source javascript libraries. This topic lists those libraries and the version of each that was included with the product as of this release.

As part of normal theme maintenance practices, it may be necessary for the site TD administrator to update these libraries from the delivered versions.

Default Titania Theme

The default Titania theme is packaged with all Titania Delivery deployments.

Default Theme Libraries
Library Version Licenses
bootstrap 4.1.1 MIT
es6-promise 3.2.2 MIT
image map resizer 1.0.3 MIT
jQuery 3.5.2 MIT
Popper 1.X MIT
Technical Content Theme

The technical content theme is a purpose built theme for delivering XML and all other online content.

Technical Content Libraries
Library Version Licenses
bootstrap 4.1.1 MIT
es6-promise 3.2.2 MIT
image map resizer 1.0.3 MIT
jQuery 3.5.2 MIT
jquery.validate 1.15.0 MIT
jstree 3.3.6 MIT
lunr 2.3.6 MIT
pdf.js 1.4.20 Apache License
Popper 1.X MIT
showdown.js 1.8.6 jquery.validate
summernote - MIT
typeahead 0.11.1 MIT
EIFU Theme

The eIFU theme is a baseline theme for delivering eIFU (Electronic Instructions for Use) content and order book management.

EIFU Libraries
Library Version Licenses
bootstrap 3.3.6 MIT
es6-promise 3.2.2 MIT
image map resizer 1.0.3 MIT
jQuery 3.5.2 MIT
jquery.validate 1.15.0 MIT
pdf.js 1.4.20 Apache License
Popper 1.X MIT
typeahead 0.11.1 MIT

D.2 jQuery

jQuery is a common and powerful JavaScript library that abstracts browser compatibility issues, simplifies element selecting, and provides a simplified interface to AJAX and DOM manipulation.

Include jQuery on any page by adding the following to the <head> of the template:

<#assign c=JspTaglibs['http://java.sun.com/jsp/jstl/core']/>
<script type="text/javascript" 
        src="<@c.url value="/resources/scripts/libs/jquery-1.11.0.min.js"/>"></script>

D.3 The JSP Standard Tag Library

The Freemarker templating system used by Titania Delivery is a Java library. As such, Java Server Pages tags can be used inside Freemarker templates.

Include JSP tag libraries by #assigning them to a variable. For example:

<#assign c=JspTaglibs['http://java.sun.com/jsp/jstl/core']/>

The Titania Tag Library is a JSP tag library, so it can be included in the same way.

Once assigned to a variable, the tag libraries can be utilized using dot syntax. For example, given the <#assign> statement above, one could write:

<@c.if test="${foo}">
 Do this
</@c.if>
Note: Many functions made available by the JSP Standard Tag Library are included natively in Freemarker. It is up to the Portal Theme developer to decide which API they prefer to use. Titania recommends using the Freemarker constructs where possible and practical.

Standard XML Tag Library

Freemarker has some ability to handle XML node-valued variables in the data model, but no native ability to generate XML nodes in a template. To do that, use the JSP xml tag library. This library is old and unmaintained, but can work for limited uses.

See the "XML Tag Library" topic in "The Java EE 5 Tutorial" for documentation.

Include the tag library by assigning to a variable.

<#assign x=JspTaglibs['http://java.sun.com/jsp/jstl/xml']/>

Parse an XML string into a Freemarker node variable.

<#assign xmlString = '<doc>text</doc>'/>
<@x.parse var="xmlDoc" doc=xmlString/>
<#-- now process variable 'xmlDoc' using jsp or freemarker -->

See the Freemarker XML Processing Guide for general information on XML processing in Freemarker templates.

If the XML content uses namespaces, it might be helpful to set namespace prefixes for the template. Refer to the #ftl directive documentation for the ns_prefixes parameter. Among other things, this will declutter serialized output from Freemarker xml builtins.

Note: The xml taglib <@x.parse> directive does not always use the namespace prefixes defined in the Freemarker ns_prefixes hash. If you have any problem processing namespaced XML from <@x.parse> in Freemarker, check the markup to see what namespace prefixes are used. If you do not need namespaces for processing, it might be easiest to remove namespace declarations from the input string before calling <@x.parse>.

D.4 Comment Management with the Titania Annotator Plugin

Commenting in a Titania Delivery Portal is managed by a plugin to the jQuery JavaScript library called TitaniaAnnotator.js. This plugin is highly configurable and meant to provide a venue to receive information back from Portal users.

You can enable commenting on any element in a page by calling $(<selector>).titaniaAnnotate(). Replace <selector> with a CSS selector, identifying the element or elements on the page on which to allow commenting. The titaniaAnnotate() function accepts as an argument either an object with the fields listed below, or a String with the single legal value of off which turns off commenting.

The following code snippet should be included in the <head> element of every page that uses this library. The first and third lines are required. The middle line is optional, but provides for nicer time formatting on comments than would exist without it.

<link rel="stylesheet" href="<@c.url value="/resources/cm/cm.css"/>"></link>
<script type="text/javascript" src="<@c.url value="/resources/scripts/libs/moment.min.js"/>"></script>
<script type="text/javascript" src="${cmScriptLocation}"></script>
serverUrl
  • Type: String
  • Required: Yes
  • Default: None

The server endpoint that handles the persistence of the comments.

siteKey
  • Type: String
  • Required: Yes
  • Default: None

The key that associates comments to a particular site.

pageId
  • Type: String
  • Required: No
  • Default: The URL of the current page

A unique identifier that associates a comment with a given page. This allows for retrieving all the comments on a given page with a single call.

author
  • Type: Object
  • Required: Yes, unless readOnly is set to true
  • Default: {name: 'Anonymous', id: ''}

The name and id of the currently logged-in user. If the readOnly option is set to true, then this field may be ommitted. Otherwise, a valid user must be passed in.

targetId
  • Type: String or function
  • Required: false
  • Default: 'id'

If the value of this option is a String, then it must be the attribute on the element selected for commenting whose value uniquely identifies that element as the target of that comment. By default, this is the id attribute of the element.

If the value of this option is a function, then it must return a string that uniquely identifies the element being commented upon. This is useful if the unique identifier is the combined values of more than one attribute.

If this value is changed after comments have been left, those comments will no longer be associated with the element unless it is later changed back to its original value. Once this value has been determined, it is a good idea to leave it as-is.

metadata
  • Type: Object or function(jQuery $element)
  • Required: No
  • Default: None

The metadata field can be used to store additional arbitrary data on the comment. Each field name must be unique within the metadata object, and each value must be a String. If the value of the metadata field is a function, it must return an object. The element being commented on is passed in via $element

Moderators
  • Type: Array
  • Required: No
  • Default: []

The moderators array contains the user IDs that have moderator privileges. Each ID must be a String. A moderator can view, edit, and delete any comment.

showLoadingIndicator
  • Type: boolean
  • Required: No
  • Default: true

Comments are loaded asynchronously, after the page has loaded. This can take several moments. If this value is true, a modal loading indicator will be displayed while comments are being loaded.

hoverClass
  • Type: String
  • Required: No
  • Default: tAnnotatableHover

The value of the HTML class attribute that is added to an annotatable element when the mouse is hovering over it. Subtly changing the look of the element gives the user feedback that it is clickable.

readOnly
  • Type: boolean
  • Required: No
  • Default: false

If this is set to true, comments from the server will be loaded, but editing, deleting, and creating comments will be prevented.

hintText
  • Type: String
  • Required: No
  • Default: 'Leave a comment'

The default text that appears in the comment entry box before the user starts typing.

slideDuration
  • Type: Integer
  • Required: No
  • Default: 100

Several interaction with the Comment Manager, such as loading comments, saving a new comment, and deleting comments, result in a "sliding" animation. This value sets the duration, in milliseconds, of the animation. A value of 0 will disable animations entirely.

render
  • Type: function(Object m, jQuery $e)
  • Required: No
  • Default:
function(m, $e) {
    if (m.safeHtml) {
        $e.html(m.safeHtml);
    } else {
        $e.text(m.message);
}

The function that controlls the rendering of comments. It takes two parameters; m is the message object itself, and $e is the element that will contain the body of the comment.

currentUserOnly
  • Type: boolean
  • Required: No
  • Default: false
Set this value to true if you want users to only be able to see comments they left, and not those by other users.
maxReplyDepth
  • Type: Integer
  • Required: No
  • Default: 1

This option sets how deeply replies can be nested relative to "top-level" comments. Setting to zero dissalows replying to comments.

Setting a high maxReplyDepth adds more flexibility to comment sections, but can have the undesireable affect of leading users to reply to a comment when it would have been more appropriate to leave that comment at a different level. Additionally, as each "level" of comments is indented, deep nesting can cause UI problems.

Reducing the maxReplyDepth of a comment thread, after replies have already been left beyond that level, will effectively hide those comments until the maxReplyDepth is increased again. It is not recommended to change this value after there has been activity in a comment thread.

addCommentLinkText
  • Type: String
  • Required: No
  • Default: 'Continue the conversation'

The addCommentLinkText is the text on the widget that allows users to add a comment to the end of a comment thread.

D.5 lunr (javascript search support)

lunr is a javascript library for indexing and searching text.

The lunr library is used in the offline packager to index documents in the package, and provide offline search capabilities in the browser. Information is available at the lunr project home page. The lunr.js github project has been forked into the TitaniaSoftware github area.

Building a search index involves configuring a lunr.Builder, adding documents, and then building the index. (The Builder class was added in lunr v2; the earlier idiom of simply adding documents to an index is still supported.) lunr documents are hash objects, where each hash entry represents a named search field. The field list is configurable, but must be the same for all documents indexed. Refer to the lunr documentation for details. For offline packaging, the index is built during packaging, then saved as JSON in the package. The HTML pages load the index and provide for client-side searching.

By default, the following fields are indexed when building the lunr search index:

  • id, containing the path and filename.
  • title
  • body, containing the full text contents of the document
  • keywords, containing all keyword metadata associated with the document
  • lang

lunr only fully supports English. However, the default packager theme uses lunr.languages plugins for non-English languages, when one is available. As a fallback, the English tokenizing process seems to work acceptably for many non-English Western languages.

The lunr search syntax is quite limited compared to lucene, solr, or elasticsearch (which supports online portal search). These are the features supported:

  • Wildcard ("*")
  • Required and prohibited presence indicators ("+" or "-" before term)
  • Field tag ("field:" before term)
  • Edit distance ("~n" after term). This will match indexed terms that can be created with no more than n edits to the search term.
Caution: Using a single exclusion term in a search may cause script errors. Best practice is to always combine an exclusion term with a restrictive positive term. For example: +pizza -anchovy.
Multiple terms are combined with OR. There is no grouping, phrasing, or proximity search support. Some of these features could probably be emulated by preprocessing the search input to handle enhanced syntax features. It might also be possible to enhance the indexing to provide token metadata that could support detection of word proximity. These modifications could be made to the default offline packager theme by a javascript developer.

D.6 lunr-languages (non-English search support)

lunr-languages provide plugins to extend lunr for non-English languages.

The lunr-languages project provides plugins for lunr to provide specialized stemming and tokenizing for non-English languages. These plugins are used in the offline packager to index foreign language documents in the package. Some usage information is available at the lunr project home page. The lunr-languages github project has been forked into the TitaniaSoftware github area. The Titania fork has a plugin for Simplified Chinese.

Using a lunr-languages plugin can be as simple as calling builder.use(plugin) on a lunr.Builder instance. That will configure the builder to use the stemmer, trimmer, and stopwords provided by the plugin. Alternatively, the builder can be manually configured to use the components provided by the plugin.

It is possible to index several different languages in one index. However, experience with indexing multiple languages for the offline packager shows that separate indexes for each language provide a better search experience. All the indexes can be written out in one JSON structure. The browser search function must search in each index and merge the results.

The lunr-languages project includes tools and procedures for writing plugins for additional languages. Indexing CJK (Chinese-Japanese-Korean) documents, or other languages that do not typically include clear word separators (such as Thai) is particularly challenging. One approach to this problem is to provide a morphological segmenter in place of an ordinary tokenizer. The Japanese and Thai lunr-languages plugins use this approach. TitaniaSoftware added a Simplified Chinese plugin based on another open-source segmenter, Rakuten MA. These tools can slow down indexing, and usually are not completely accurate because they are based on machine learning.

D.7 Titania PDF.js

A custom PDF viewer for portals.

PDF.js is a popular HTML5 PDF viewer. It is the default PDF viewer in recent Mozilla browsers. Titania uses it for admin preview of PDF files.

Titania PDF.js is a fork of PDF.js that has been customized to add a fragment metadata index as a sidebar view. Other customized features could be added if needed.

Refer to README_TITANIA in the Titania PDF.js project for further details.

An example of using the custom PDF.js library is in the Service Information theme.

D.8 Upgrading Portal Libraries

Titania Delivery portal themes rely on front end libraries, which may need to be updated periodically to comply with security standards or enable new features.
Upgrading Bootstrap 4.x to 4.y
Note: Some older themes may use bootstrap version 3.x. These instructions do not apply to those themes. Upgrading from bootstrap 3.x to 4.x requires more extensive changes to the theme.
Note: These instructions apply to themes built from, or modeled after, baseline themes supplied with the Titania Delivery platform. They may not apply to customized themes. Experienced theme developers may choose to implement the upgrade differently.
  1. Download the source files for the desired 4.y bootstrap release corresponding to the theme. Example link : https://getbootstrap.com/docs/4.5/getting-started/download/
    Note: Some older themes may still use bootstrap 3, always check the file in the theme.

    Titania uses the sass files and minified bootstrap.min.js from this download. In the downloaded source files, the minified javascript file is located at [bootstrapRoot]/dist/js. The sass files are located in [bootstrapRoot]/scss for bootstrap 4.

  2. Titania themes typically store the minified boostrap javascript under [themeRoot]/static/scripts/lib/bootstrap. Copy the minified javascript and license into the folder.
  3. Delete the bootstrap sass files in the Titania theme. These are typically located in [themeRoot]/static/style/bootstrapx.x.x.
  4. Create a new folder with the correct version number and copy the new sass into it.
  5. Edit static/style/common/_bootstrap-config.scss to update each file reference to the proper path/version.
    Note: The bootstrap.min.js filename did not change, so existing references to that file in the theme will work as before.

Appendix E: Browser Support

Titania Delivery supports most major browsers. Portals can be made to support virtually any browser by modifying portal themes.
Administrative UI

The Titania Delivery administration application is supported on the following browsers:

  • Google Chrome
  • Mozilla Firefox
  • Microsoft Edge
  • Microsoft Internet Explorer 11
  • Apple Safari

Titania Delivery supports the two most recent versions of these browsers.

Portals

In general, most of the portal applications provided by Titania target the same browser support as the administrative application. However, since the code behind portals can be fully configured, a Portal Theme can be developed to target virtually any user agent/web browser offering.