System Velocity Macro Reference

CM1 System Velocity Macros
In this section we’ll review the System macro’s provided for CM1 templates and evaluate opportunities for Page Speed Optimization.

Macro: imageurl
Purpose: Used to get the Nav Image Url
Source:

#macro(imageurl $image $status)
#if ($status == “rollover” )
#imghelp($image “rffBnRolloverImage”)
#elseif ($status==“active”)
#imghelp($image “rffBnActiveImage”)
#else
#imghelp($image “rffBnInactiveImage”)
#end
#end

Notes: Not Page Speed relevant.

Macro: imghelp
Purpose: helper macro … context=0 needs to be dynamic
Source:

#macro(imghelp $image $template)
$rx.location.generate($template, $image,
$rx.location.folderPath($image),
$sys.assemblyItem.getFilter().getName()
, $sys.assemblyItem.getSiteId().longValue(),
$tools.number.toNumber($tools.list.get($sys.params.get(‘sys_context’), 0)) )
#end

Notes: Not Page Speed relevant.

Macro: region
Purpose: Render a region on a template including any Widgets in the reason
Source:

#macro(region $regionId $header $before $after $footer)##
##
#if($perc.regions.get($regionId))##
$header##
#foreach($result in $perc.regions.get($regionId))##
$before##
#if($result.getType() == "WIDGET")##
## compute the tool-tip for the asset in the widget, which is the full path ## for a shared asset, but 'Local content' for local asset
#set($owner_id = "")##
#set($asset_id = "")## 
#set($tooltip = "")##
#if($result.widget.ownerAssetIds.size() > 0)##
#set($owner_id = $result.widget.ownerAssetIds.get(0).getFirst())##
#set($asset_id = $result.widget.ownerAssetIds.get(0).getSecond())## 
#set($sharedPath = $rx.pageutils.getItemPath($result.widget.ownerAssetIds.get(0).getSecond()))##
#if($sharedPath != "")##
#set($tooltip = $sharedPath)##
#else##
#set($tooltip = "Local content")##
#end##
#end##
#set($widgetName = $!result.widget.item.name)##
#if(!${widgetName})##
#set($widgetName = "")##
#end##
#set($widgetDescr = $!result.widget.item.description)##
#if($tooltip == "" && $widgetDescr != "")##
#set($tooltip = $widgetDescr)##
#end##
#if($widgetName != "" && $tooltip != "")##
#set($widgetName = $widgetName + ": ")##
#end##
#set($tooltip = $widgetName + $tooltip)##
<div class="perc-widget" 
#if($perc.editMode)
widgetId="$result.widget.item.id" widgetDefId="$result.widget.definition.id" assetId="$asset_id" ownerId="$owner_id" title="$tooltip" widgetName="$!result.widget.item.name"#end>
#end##
#perc_displayText("$result")##
#if($result.getType() == "WIDGET")##
</div>
#end##
$!{after}##
#end##
$!{footer}##
##
#end##
#end##

Notes: This macro is interesting as it is the base rendering for a region on a template, including the Widgets within the Region. As the Region and Widget markup in this macro doesn’t really affect Pagespeed and the markup is wired closely with the Page and Template Editor, this macro is not a good candidate for overriding.

Macro: loadWidgetContents
Purpose: Loads widget contents (items) for the current page / template with the specified finder and its parameters. It defaults to related finder if the finder is not specified.
Source:

#macro(loadWidgetContents $finder $params)##
$perc.setWidgetContents($rx.pageutils.widgetContents($sys.assemblyItem, $perc.widget, $finder, $params))##
#end##

Notes: This is a utility macro for loading up a Page with it’s widget contents. Not useful for Page Speed.

Macro: loadRelatedWidgetContents
Purpose: Loads widget contents (items) that are related to current page / template through the current widget instance
Source:

#macro(loadRelatedWidgetContents)##
#loadWidgetContents($NULL, $NULL)##
#end##

Notes: Not useful for Page Speed.

Macro: perc_templateHeader
Purpose: Common header for all base templates
Source:

#macro(perc_templateHeader)##
#set($selectedDoctype = $perc.template.docType.selected)##
#foreach($option in $perc.template.docType.options)##
#if($option.option.equals($selectedDoctype))##
#perc_displayText("$!option.value")##
#end##
#end##
<head>
##See IE10 Required for drag drop CMS-79 and CMS-3403
#if($perc.isEditMode())
<meta http-equiv="X-UA-Compatible" content="IE=10"/>
#else
<meta http-equiv="X-UA-Compatible" content="IE=Edge"/>
#end
<meta content="text/html; charset=UTF-8" http-equiv="content-type"/>
##
#set($percPageID = $perc.getPage().getId())
#if($perc.linkContext.getSite().isCanonical() && "$!{percPageID}" != "")##
#set($renderCanonical = true)##
#if($perc.linkContext.getSite().isCanonicalReplace())##
#set($stripCustomCanonical = true)##
#elseif($rx.pageutils.checkLinkCanonical($!perc.page.additionalHeadContent) || $rx.pageutils.checkLinkCanonical($!perc.template.additionalHeadContent))##
#set($renderCanonical = false)##
#end##
#if($renderCanonical)##
#set($canLink = $rx.pageutils.itemLink($perc.linkContext, $perc.getPage()).toString())##
#if($perc.linkContext.getMode().name() == "PREVIEW")##
#set($linkPref = "/Sites/${perc.linkContext.getSite().getName()}")##
#set($canLink = $canLink.substring($linkPref.length()))##
#end##
<link rel="canonical" href="${perc.linkContext.getSite().getSiteProtocol()}://${perc.linkContext.getSite().getName()}${canLink}" />
#end##
#end##
<title>$tools.esc.html($!perc.page.title)</title>
<script type="text/javascript">
var percGlobalErrors = [];         
window.onerror=function(msg, url, linenumber){
var jsErrorMsg = 'JavaScript error -- ' + msg;  
percGlobalErrors.push(jsErrorMsg);
return false;
}
</script>
#foreach($css_link in $rx.pageutils.cssLinks($perc.linkContext, $sys.assemblyItem))##
<link rel="stylesheet" href="$css_link" type="text/css" media="all" />
#end##
<link rel="stylesheet" href="$rx.pageutils.themeLink($perc.linkContext, $perc.template.theme)" type="text/css" media="all" />
#set($__perc_region_css = $rx.pageutils.themeRegionCssLink($perc.linkContext, $perc.template.theme, $perc.editMode, $perc.editType).toString().trim())##
#if("$!__perc_region_css" != "")##
<link rel="stylesheet" href="${__perc_region_css}" type="text/css" media="all" />
#end##
#set($__style = $!perc.template.cssRegion.replaceAll("$tools.esc.q", "").trim())##
#if("$!__style" != "")##
<style type="text/css">
$__style
</style>
#end##
#set($__style = $!perc.template.cssOverride.trim())##
#if("$!{__style}" != "")
<style type="text/css">
$__style
</style>
#end##
#foreach($js_link in $rx.pageutils.javascriptLinks($perc.linkContext, $sys.assemblyItem))##
<script type="text/javascript" src="$js_link"></script>
#end##
#if("$!perc.page.noindex" == "true")##
<meta name="robots" content="noindex" />
#end##
#if("$!perc.page.description" != "")##
<meta name="description" content="$tools.esc.html($!perc.page.description)" />
#end##
#if($sys.item.hasProperty('rx:page_authorname') && $sys.item.getProperty('rx:page_authorname').getString() != "")##
<meta property="dcterms:author" content="$tools.esc.html($sys.item.getProperty('rx:page_authorname').getString())" />
## Use the correct dcterms property
<meta property="dcterms:creator" content="$tools.esc.html($sys.item.getProperty('rx:page_authorname').getString())" />
#end##
## I don't know how to FIXME without potentially breaking customers.  This is not a valid Dublin Core type. 
<meta property="dcterms:type" content="page"/>
<meta property="dcterms:source" content="$tools.esc.html($!perc.template.name)"/>
<meta property="dcterms:created" content="#perc_displayXdsDateTime('rx:sys_contentpostdate')"/>
<meta property="dcterms:modified" content="#perc_displayXdsDateTime('rx:sys_contentlastmodifieddate')"/> 
<meta property="dcterms:alternative" content="$tools.esc.html($!perc.page.linkTitle)"/>
#if($sys.item.hasProperty("page_tags"))##
#set( $tagValues = $sys.item.getProperty('page_tags').getValues() )##
#foreach($tag in $tagValues)##
<meta property="perc:tags" content="$tools.esc.html($tag.String)"/>
#end##
#end##
#if($sys.item.hasProperty("page_categories_tree"))##
#set( $categoryValues = $sys.item.getProperty('page_categories_tree').getValues() )##
#foreach($category in $categoryValues)##
#set($categoryLabel = $rx.pageutils.getCategoryLabel($category.String))##
<meta property="perc:category" content="$tools.esc.html($!{categoryLabel})"/>
#end##
#end##
#if($sys.item.hasProperty("page_calendar"))##
#set( $calendarValues = $sys.item.getProperty('page_calendar').getValues())##
#foreach($calendar in $calendarValues)##
<meta property="perc:calendar" content="$tools.esc.html($calendar.String)"/>
#end##
#end##
#if($sys.item.hasProperty("page_start_date"))##
<meta property="perc:start_date" content="#perc_displayXdsDateTime('page_start_date')"/> 
#end##
#if($sys.item.hasProperty("page_end_date"))##
<meta property="perc:end_date" content="#perc_displayXdsDateTime('page_end_date')"/> 
#end##
##
#if ($stripCustomCanonical)##
#set($tmplAdditionalHeadContent = $rx.pageutils.stripLinkCanonical($!perc.template.additionalHeadContent))##
#set($pageAdditionalHeadContent = $rx.pageutils.stripLinkCanonical($!perc.page.additionalHeadContent))##
#perc_displayText("$!tmplAdditionalHeadContent")##
#perc_displayText("$!pageAdditionalHeadContent")##
#else##
#perc_displayText("$!perc.template.additionalHeadContent")##
#perc_displayText("$!perc.page.additionalHeadContent")##
#end##
##
#perc_addDeliveryJSFunctions()##
## The analytics script additions must be the last thing added to the header
##perc_addGoogleAnalyticsScript()##
#suppressCatalogedLinks()##
#addMobilePreviewToolbar()##
#addResponsiveResources()##
</head>
<body>
<input type="hidden" id="perc_linkback_id" value="$perc.page.id"/>
#if ($perc.isPreviewMode() && !$perc.isEditMode() && $sys.isHummingbirdEnabled)##
<div data-perc-title="$sys.item.getProperty('sys_title').String" data-perc-contentid="$sys.assemblyItem.getId().getUUID()"></div>##
#end##
#if($perc.linkContext.mode == "PUBLISH" && $perc.template.protectedRegion && $perc.template.protectedRegion.String != "")##
#set($loginPage = $rx.pageutils.getSiteLoginPage($perc.linkContext.site.name))##
<div id="protectedRegionInformation" data="{&quot;protectedRegion&quot;:&quot;$!perc.template.protectedRegion&quot;,&quot;protectedRegionText&quot;:&quot;$!perc.template.protectedRegionText&quot;,&quot;siteLoginPage&quot;:&quot;$!loginPage&quot;}" style="display:none"></div>
#if("" != "$!{perc.template.protectedRegion}"))##
<style>
#$!perc.template.protectedRegion{
display:none;
}
</style>
#end##
#end##
#if($perc.page.name && $sys.item.getProperty("rx:page_summary").String != "")##
#set($summaryField="#displayfield('rx:page_summary')")##
#set($summaryField2=$summaryField.replace("<!-- morelink -->", '&nbsp;<a class="perc-more-link"></a>'))##
<div property="dcterms:abstract" style="display:none" datatype="rdf:XMLLiteral">$summaryField2</div>
#end##
#perc_displayText("$!perc.template.afterBodyStartContent")##
#perc_displayText("$!perc.page.afterBodyStartContent")##
#end##

Notes: This is the jackpot macro for Page Speed. All CM1 Page Templates include all of the code in this macro on every Published CM1 Page. We will break down over-riding this macro in a following section.

Macro: perc_template_footer
Purpose: Common footer for all base templates
Source:

#macro(perc_templateFooter)##
#perc_displayText("$!perc.page.beforeBodyCloseContent")##
#perc_displayText("$!perc.template.beforeBodyCloseContent")##
#if ($perc.isPreviewMode() && !$perc.isEditMode() && $sys.isHummingbirdEnabled)##
<script src="//cdn.percussion.com/hbirdEditor-js/hummingbird-editor.min.js"></script>
#end##
</body>
 </html>
#end##

Notes: This macro is the other end of our jackpot when it comes to optimizing Page Speed. This represents the important Before Body Close portion of every Page published by the product. We will breakdown and cover updating this macro in a later section.

Macro: perc_displayText
Purpose: Display the supplied text as is if it is not in edit mode; otherwise, strip javascript from the source, then display it.
Source:

#macro(perc_displayText $source)##
#if($perc.scriptsOff)##
$rx.pageutils.stripJavascripts("$source")##
#else##
$!{source}
#end##
#end##

Notes: Used by the Disable JavaScript feature of the Template and Page Editor. Not a good candidate for overriding. Interesting in that it exposes a system variable that could be used within Widgets: $perc.scriptsOff indicating that JavaScript is currently disabled in the editor.

Macro: perc_displayXdsDateTimeWithFormat
Purpose: Display date field in xsd:dateTime with given format;
Source:
#macro(perc_displayXdsDateTimeWithFormat $format $field)##

#set($validFormat = $rx.pageutils.parseDateFormat($format,"EEE MMM d, yyyy 'at' hh:mm a"))##
#if($sys.item.hasProperty("$field") && $sys.item.getProperty("$field").String != "")##
$tools.date.format($validFormat, $sys.item.getProperty("$field").Date.Time)##
#else##
$tools.date.format($validFormat, $tools.date.getDate())##
#end##
#end##

Notes: May be useful to override if you have a specific date format that you need in the rendered dates (typically rendered in the head as metadata). Not helpful for Page Speed.

Macro: perc_displayXdsDateTime
Purpose: Display date field in xsd:dateTime;
Source:
> #macro(perc_displayXdsDateTime $field)##
> #perc_displayXdsDateTimeWithFormat(“yyyy-MM-dd’T’HH:mm:ss”, $field)##
> #end##

Notes: Another date macro. Maybe useful to override if the default format provided does not meet your needs.

Macro: perc_addGoogleAnalyticsScript
Purpose: Add Google Analytics javascript Async tracking tags if setup via the Google Setup gadget in the dashboard. Only adds this script if the current mode is ‘PUBLISH’.
Source:

#macro(perc_addGoogleAnalyticsScript)##
#if($perc.linkContext.mode == "PUBLISH")##
#set($wpId = $rx.pageutils.getWebPropertyId($perc.linkContext.site.name))##
#set($apiKey = $rx.pageutils.getGoogleApiKey($perc.linkContext.site.name))##
#if($wpId != "")##
<script type="text/javascript"> 
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', '$wpId', 'auto'); 
ga('send', 'pageview');
</script>
#end##
#end##
#end##

Notes: This macro is interesting in that it defines the format of the Google Analytics tag injected to Page templates. This would be a good candidate to override if you want to use a different Google Analytics snippet, or if you wanted to add the missing async parameter to the script tag.

Macro: suppressCatalogedLinks
Purpose: Preview / Editor only, detect links that may not have been imported yet by Live First and display a warning message.
Source:

#macro(suppressCatalogedLinks)##
#if ($perc.isPreviewMode())##
#if($perc.isEditMode())##
#else##
<script type="text/javascript">(function($){$(document).ready(function(){$('a[href*="/.system/PageCatalog"]').attr('cataloged', 'true' );$('a[href*="/.system/PageCatalog"]').click(function(e) {alert('This page has not yet been imported. It will be available for preview after import is complete.');e.preventDefault();});})    })(jQuery);</script>    
#end##
#end##
#end##

Notes: Used by the LiveFirst importer to indicate that links on a Page have not yet been imported. Preview only so no really useful for Page Speed.

Macro: addMobilePreviewToolbar
Purpose: Add the mobile Preview toolbar JavaScript to the page on Preview.
Source:
> #macro(addMobilePreviewToolbar)##
> #if ($perc.isPreviewMode())##
> #if($perc.isEditMode())##
> #else##
>
> #end##
> #end##
> #end##

Notes: Preview only so not useful for Page Speed. If you wanted to get rid of Mobile Preview, you could override this with an empty macro.

Macro: addResponsiveResources
Purpose: Adds responsive template resources, if the source template name starts with perc.resp. String.
Source:

#macro(addResponsiveResources)##
#if ($perc.template.sourceTemplateName.startsWith("perc.resp"))##
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
#if($perc.linkContext.mode == "PUBLISH")##
<script type="text/javascript" src="/web_resources/cm/foundation/js/foundation.min.js"></script>
<link rel="stylesheet" href="/web_resources/cm/foundation/css/foundation.min.css" type="text/css" media="all" />
<link rel="stylesheet" href="/web_resources/cm/foundation/css/normalize.css" type="text/css" media="all" />
#else##
<script type="text/javascript" src="/Rhythmyx/web_resources/cm/foundation/js/foundation.min.js"></script>
<link rel="stylesheet" href="/Rhythmyx/web_resources/cm/foundation/css/foundation.min.css" type="text/css" media="all" />
<link rel="stylesheet" href="/Rhythmyx/web_resources/cm/foundation/css/normalize.css" type="text/css" media="all" />
#end##
#end##  
#end##

Notes: This macro is interesting because it is responsible for pulling in the Foundation framework onto the built-in Responsive Templates. It is also hardwired to be included in the head of the document (note the meta viewport tag). This would be a very good candidate macro for overriding for Page Speed as well as if you want to include a different version of Foundation than the system currently ships. This macro will be deprecated with the Foundation widget to be included in the CM1 5.4 release.

Macro: perc_addDeliveryJSFunctions
Purpose: Add the delivery service base function to the page
Source:

#macro(perc_addDeliveryJSFunctions)##
#set($dServiceBase = $rx.pageutils.getDeliveryServer($sys.assemblyItem.PubServerId))##
#set($cm1Version = $rx.pageutils.productVersion())##
#if(! $cm1Version )##
#set($cm1Version = "")##
#end
#set($cm1LicenseStatus = $rx.pageutils.licenseStatus())##
#if(! $cm1LicenseStatus)##
#set($cm1License = "")##
#else##
#set($cm1License = $cm1LicenseStatus.licenseId)##
#end##
<script type="text/javascript">(function() { jQuery.getDeliveryServiceBase = function(){ return '$dServiceBase';};jQuery.getCm1License = function(){ return '$!{cm1License}';};jQuery.getCm1Version = function(){ return '$!{cm1Version}';};})();</script>
#end##

Notes: This macro is a good candidate for a Page Speed override. The JavaScript is inline and is not deferred, and it depends on JQuery. The script is important to include as it sets the connectionURL that client browsers should use when connecting to the Percussion DTS from any DTS Percussion widgets. If you are absolutely not using any DTS related widgets on your Templates, you could override this macro (or not call it from the header and footer) to remove this script block from your published pages.

Macro: perc_processCategoryNodeExpanded
Purpose: Used by Category List widget, recursively renders a list of lists of categories for Expanded hierarchy option. categories: List of nodes maxRow: limit the rows of the tree. (0 = no limit) nRow: used to check the current count of row in the recursive call. nLevel: depth level of the tree.
Source:

#macro(perc_processCategoryNodeExpanded $categories $maxRow $nRow $nLevel)
#if($maxRow > $nRow || $maxRow == 0)##
<ul class="perc-category-elements perc-category-level$nLevel">
#set($nLevel = $nLevel + 1)##
#foreach ($category in $categories)##
#if($maxRow > $nRow || $maxRow == 0)##
#set($_countTotal = $category.getCount().getFirst() + $category.getCount().getSecond())##
<li class="perc-category-element">
<a title="$category.getCategory() ($_countTotal)" href="#">$category.getCategory() ($_countTotal)</a>
#if($category.getChildren().size() > 0)##   
#set($nRow = $nRow + 1)##
#perc_processCategoryNodeExpanded($category.getChildren(), $maxRow, $nRow, $nLevel)##
#end##
</li>
#end##        
#end##
#set($nLevel = $nLevel - 1)##
</ul>
#end##
#end##

Notes: Not really a Page Speed item. Only useful in context of a Category List widget.

Macro: perc_processCategoryNodeCollapsible
Purpose: Used by Category List widget, recursively renders a list of lists of categories for Collapsible option categories: List of nodes maxRow: limit the number of top levels under root (0 = no limit) nLevel: depth level of the tree.
Source:

#macro(perc_processCategoryNodeCollapsible $categories $maxRow $nLevel)
<ul class="perc-category-elements">
#set($nLevel = $nLevel + 1)##
#foreach ($category in $categories)##
#if($nLevel == 1)##
#set($nRow = $nRow + 1)##
#end##
#if(($nLevel != 1 || $nRow <= $maxRow) || $maxRow == 0)##
#set($_countTotal = $category.getCount().getFirst() + $category.getCount().getSecond())##
<li class="perc-category-element">
<a title="$category.getCategory() ($_countTotal)" href="#">$category.getCategory() ($_countTotal)</a>
#if($category.getChildren().size() > 0)##   
#perc_processCategoryNodeCollapsible($category.getChildren(), $maxRow, $nLevel)##
#end##
</li>
#end##
#end##
#set($nLevel = $nLevel - 1)##
</ul>
#end##

Notes: Not Page Speed relevant. Useful only in context of a Category List widget.

Macro: createEmptyWidgetContent
Purpose: Create sample content for empty widgets. class: determine the container class. text: determine the tooltip.
Source:

#macro(createEmptyWidgetContent $class $text $style)
##the tooltip will be generated if we have name or description.
#if((${perc.widget.item.name} && $perc.widget.item.name != "") || (${perc.widget.item.description} && $perc.widget.item.description != ""))##
#set($title = "")##
#else##
#set($title = $!{text})##
#end##
<div class="$!{class}" style="$!{style}" title="$!{title}"></div>
#end##

Notes: Not useful for Page Speed. Useful for building Widgets as it provides a framework for a placeholder widget when a widget is empty on a Page or Template.

Macro: generateXMLContent
Purpose: Render the Page Content in XML
Source:

#macro(generateXMLContent)
<content>
#foreach($regionId in $perc.regions.keySet())##
<region name="$regionId">
#foreach($result in $perc.regions.get($regionId))##
<widget name="$!result.widget.item.name" id="$result.widget.item.id" type="$result.widget.definition.Id">
<![CDATA[#if($result.getType() == "WIDGET")<div class="perc-widget">#end##
#perc_displayText("$result")##
#if($result.getType() == "WIDGET")</div>#end]]></widget>
#end##
</region>
#end##
</content>
#end##

Notes: Used in XML publishing. Not useful for Page Speed but could be overridden to customize the output when publish Pages as XML.

Macro: generateXMLMetadata
Purpose: Render the Page Metadata in XML
Source:

#macro(generateXMLMetadata)
<metadata>
<sys_title type="string">$!perc.page.name</sys_title>
<resource_link_title type="string">$tools.esc.html($!perc.page.linkTitle)</resource_link_title>
<page_summary type="string">#displayCDataField($!perc.page.summary)</page_summary>
<sys_contentpostdate type="dateTime">#perc_displayXdsDateTime("rx:sys_contentpostdate")</sys_contentpostdate>
<page_title type="string">$tools.esc.html($!perc.page.title)</page_title>
<page_description type="string">#displayCDataField($!sys.item.getProperty("page_description").String)</page_description>
<page_noindex type="boolean">$!sys.item.getProperty("page_noindex").Boolean</page_noindex>
#if($sys.item.hasProperty("page_tags"))##
#set( $tagValues = $sys.item.getProperty('page_tags').getValues() )##
#if ($tagValues.size() > 0)##
<tags>
#end##
#foreach($tag in $tagValues)##
<page_tags type="string">$tools.esc.html($tag.String)</page_tags>
#end##
#if ($tagValues.size() > 0)##
</tags>
#end##
#end##
#if($sys.item.hasProperty("page_categories_tree"))##
#set( $categoryValues = $sys.item.getProperty('page_categories_tree').getValues() )##
#if ($categoryValues.size() > 0)##
<categories>
#end##
#foreach($category in $categoryValues)##
#set($categoryLabel = $rx.pageutils.getCategoryLabel($category.String))##
<page_categories type="string">$tools.esc.html($!{categoryLabel})</page_categories>
#end##
#if ($categoryValues.size() > 0)##
</categories>
#end##
#end##      
#if($sys.item.hasProperty("page_calendar"))##
#set( $calendarValues = $sys.item.getProperty('page_calendar').getValues())##
#if ($calendarValues.size() > 0)##
<calendars>
#end##
#foreach($calendar in $calendarValues)##
<page_calendar type="string">$tools.esc.html($calendar.String)</page_calendar>
#end##
#if ($calendarValues.size() > 0)##
</calendars>
#end##
#end##
<page_start_date type="dateTime">#perc_displayXdsDateTime("page_start_date")</page_start_date>
<page_end_date type="dateTime">#perc_displayXdsDateTime("page_end_date")</page_end_date>
<additional_head_content type="string">#displayCDataField($!perc.page.additionalHeadContent)</additional_head_content>
<code_insert_after_body_start type="string">#displayCDataField($!perc.page.afterBodyStartContent)</code_insert_after_body_start>
<code_insert_before_body_close type="string">#displayCDataField($!perc.page.beforeBodyCloseContent)</code_insert_before_body_close>
<sys_workflow type="string">$rx.pageutils.findWorkflow($perc.page.workflowId).getName()</sys_workflow>
<template_name type="string">$tools.esc.html($!perc.template.name)</template_name>
</metadata>
#end##

Notes: Used by XML publishing. Not useful for Page Speed. Could be overridden to customize XML output of metadata.

Macro: displayCDataField
Purpose: Utility macro to wrap fields in CDATA when performing XML publishing.
Source:

#macro(displayCDataField $value)
#if($value)##
<![CDATA[$value]]>
#end##
#end##

Notes: XML utility macro, not really useful for Page Speed.

Macro: cleanClass
Purpose: Utility macro for outputting css class attributes it will insure that class attributes are single spaced with no extra spaces.
Source:

#macro(cleanClass $value)
$value.trim().replaceAll(" +"," ")##
#end##

Notes: Not useful for Page Speed. Useful when building Widgets.