META Tags

Version: 1.0.0
Date: 2006-01-16
note:This tutorial was tested with ePublisher 9.1.
Changes:Initial version.

Abstract

Demonstrates an implementation of META tags in generated output using a project format override.

1   Introduction

Project format overrides enable ePublisher users full control over generated output on a per target basis. When used in conjunction with ePublisher Stationary, these customizations can be packaged and distributed to other users.

This tutorial illustrates how to add both simple and custom META tags to generated output. The first part adds "ModeDate" and "Expires" META tags. These tags are simple in the sense that they do not reference the source documents in any way. The second part implements META tags whose values are pulled from the source documents themselves.

1.1   Changes

Initial version.

2   Dependencies

This tutorial depends on the user have the following files:

Also available as a ZIP:

3   Project Setup

Step-by-step instructions for creating a new project and configuring project format overrides.

  1. Create a new project.

    1. Use "Dynamic HTML" as the format.
    2. Add the document

      META Tags.doc.

  2. Setup to perform a project format override.

    1. Create a "Formats" directory in the project directory laid out as follows:

      project.wep
      Data
      Files
      **Formats**
        **Dynamic HTML**
          **Pages**
          **Transforms**
    2. Copy over the following files from the "Dynamic HTML" format into the project format override hierarchy:

      C:\Program Files\WebWorks\ePublisher Pro\Formats\Dynamic HTML\Pages\Page.asp --> <projectdir>\Formats\Dynamic HTML\Pages\Page.asp
      C:\Program Files\WebWorks\ePublisher Pro\Formats\Dynamic HTML\Transforms\pages.xsl --> <projectdir>\Formats\Dynamic HTML\Transforms\pages.xsl
      C:\Program Files\WebWorks\ePublisher Pro\Formats\Dynamic HTML\Transforms\wrappers.xsl --> <projectdir>\Formats\Dynamic HTML\Transforms\wrappers.xsl

4   Simple META Tags

HTML files commonly require "ModDate" and "Expires" META tags. These tags enable smart caching of files by web browsers and proxy servers. Implementing these tags do not require data from source documents.

Unfortunately, XSLT 1.0 and 1.1 do not provide a default method to access the current date and time. Nor does it provide methods for calculating relative dates. Therefore, to implement "ModDate" and "Expires" will require the use of an XSL script block.

4.1   XSL Script Blocks

ePublisher relies upon the Microsoft .NET 1.1 XSL transform engine. This transform engine supports custom extensions with <msxsl:script> blocks. This mechanism will be used to define some date and time utility routines.

4.1.1   Declare MSXSL Namespace

To use <msxsl:script> blocks, the MSXSL namespace must be declared.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                              xmlns:msxsl="urn:schemas-microsoft-com:xslt"
                              exclude-result-prefixes="xsl msxsl"
>
4.1.2   Declare Extension Namespace

Next, the script block must be identified with a unique namespace. It is recommended that URLs of the form urn:<company-name>-<intended-use> be used to avoid possible conflicts with other defined namespaces.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                              xmlns:msxsl="urn:schemas-microsoft-com:xslt"
                              xmlns:wwdatetime="urn:WebWorks-Date-Time-Extension"
                              exclude-result-prefixes="xsl msxsl wwdatetime"
>
4.1.3   Define Script Block

Now it is possible to define a script block containing any support .NET language such as C#, VB .NET, or JScript. C# is used for this example.

<msxsl:script language="C#" implements-prefix="wwdatetime">
 <![CDATA[
  public long  TicksPerSecond() { return 10000000; }
  public long  TicksPerMinute() { return 600000000; }
  public long  TicksPerHour()   { return 36000000000; }
  public long  TicksPerDay()    { return 864000000000; }

  public long  NowAsTicks()
  {
    long  result;

    result = DateTime.Now.Ticks;

    return result;
  }

  public string  TicksAsRFC1123(long  ticks)
  {
    string    result;
    DateTime  dateTime;

    dateTime = new DateTime(ticks);
    result = String.Format("{0:r}", dateTime);

    return result;
  }
 ]]>
</msxsl:script>

For more information on formatting dates and times with .NET classes, see C# String Formatting Documentation.

4.1.4   Use Script Block

Using the methods defined in the script block, it is possible to determine the correct values for "ModDate" and "Expires" META tags.

<xsl:variable name="VarNowAsTicks" select="wwdatetime:NowAsTicks()" />
<xsl:variable name="VarModDate" select="wwdatetime:TicksAsRFC1123($VarNowAsTicks)" />
<xsl:variable name="VarExpires" select="wwdatetime:TicksAsRFC1123($VarNowAsTicks + (365.25 * wwdatetime:TicksPerDay()))" />

4.2   Date/Time Stylesheet

To allow for reuse, the date and time script block is stored in a stand-alone stylesheet. Add datetime.xsl to the project format overrides hierarchy.

<example>\Formats\Dynamic HTML\Transforms\datetime.xsl --> <projectdir>\Formats\Dynamic HTML\Transforms\datetime.xsl

Next, include the stylesheet in the pages.xsl and wrappers.xsl stylesheets.

  1. Declare the namespace:

    xmlns:wwdatetime="urn:WebWorks-Date-Time-Extension"
  2. Add the namespace prefix to the exclude-result-prefixes attribute:

    exclude-result-prefixes="... wwdatetime"
  3. Include the datetime.xsl stylesheet:

    <xsl:include href="datetime.xsl" />
  4. To enable proper change tracking during development, add datetime.xsl to the GlobalActionChecksum:

    <xsl:value-of select="concat(',', wwuri:AsFilePath('datetime.xsl'), ':', wwfilesystem:GetChecksum(wwuri:AsFilePath('datetime.xsl')))" />

4.3   Use The Page Template Luke!

With the addition of the datetime.xsl stylesheet, it is now possible to determine the correct values for "ModDate" and "Expires" META tags. The next problem to solve is adding them to every generated page.

Page templates allow developers to define a set of conditions and replacement values which can be applied to an XML template file. Conditions are not required for "ModDate" and "Expires" META tags since they should always show up in the generated page. Therefore, all that remains to be done is to set define replacement values and add them to our Page.asp file.

4.3.1   Replacements Please!

In both pages.xsl and wrappers.xsl, search for:

<xsl:variable name="VarReplacements"

Just above this line, there are a series of page template replacement value definitions such as:

<wwpage:Replacement name="content">
 ...
</wwpage:Replacement>

Insert a new one for each META tag:

<wwpage:Replacement name="meta-moddate">
 <xsl:value-of select="wwdatetime:TicksAsRFC1123(wwdatetime:NowAsTicks())" />
</wwpage:Replacement>

<wwpage:Replacement name="meta-expires">
 <xsl:value-of select="wwdatetime:TicksAsRFC1123(wwdatetime:NowAsTicks() + (365.25 * wwdatetime:TicksPerDay()))" />
</wwpage:Replacement>

Note

For this exercise, "Expires" has been defined as one year from the generation date.

Messages are now available for the page template to use.

4.3.2   Page Template ASP

The page template file can now catch the messages made available by the pages.xsl and wrappers.xsl stylesheets. Using the same replacement names, it is now possible to define the "ModDate" and "Expires" META tags.

  1. Open Page.asp.
  2. Add the following two lines:

    <meta name="ModDate" content="I will be replaced!" wwpage:attribute-content="meta-moddate" />
    <meta name="Expires" content="I will be replaced!" wwpage:attribute-content="meta-expires" />

Note

wwpage:attribute-<name> is the method used to define XML element attribute replacement values.

5   Marker META Tags

Users require the ability to process custom markers with ePublisher. This exercise shows how one can define a "META" behavior for markers. Actually, two behaviors will be defined:

META per Page Split::
  Use markers only within the scope of the current page split's content.

META per Document::
  Use markers within the scope of the current source document's content.

The META tag name will come from the marker itself and the value will be used as the content. If more than one marker is found, then their values will be combined with a comma (',') between each entry.

Note

Two additional behaviors could be defined at this point:

META per Group::
  Use markers within the scope of the top-level group's documents' content.

META per Project::
  Use markers within the scope of the project's documents' content.

These behaviors are not defined in this exercise in order to restrict the length and complexity of this exercise.

Using these defined behaviors, it is possible to add META tags such as "Product" to generate pages.

5.1   Why Behaviors

At this point, experienced WebWorks Publisher 2003 users are asking the question:

What the heck are behaviors?
What do they have to do with my markers?

One of the goals for ePublisher was to split the behavior of a style, say "Popup" or "DropDown" from the style name. Rather than mapping source document styles onto WebWorks style/behavior combos, ePublisher now allows users to treat style separate from behavior. So now users can define as many "Popup" and "DropDown" paragraph styles as they like by simply selecting a behavior from a drop down menu.

For markers, this seems a bit indirect and counter-intuitive for 2003 users:

I know what I want my "Product" markers to do.
Can't I just make them do it?

The fact is, you can still do this in ePublisher. But when you do so, you prevent yourself from reusing your work elsewhere.

Consider the following snippet:

<Marker id="123" name="Product">
 <TextRun id="456">
  <Text value="WebWorks ePublisher" />
 </TextRun>
</Marker>

One could code the following:

<xsl:template match="wwdoc:Marker" mode="wwmode:textrun">
 <xsl:param name="ParamSplits" />
 <xsl:param name="ParamCargo" />
 <xsl:param name="ParamLinks" />
 <xsl:param name="ParamSplit" />

 <xsl:variable name="VarMarker" select="." />

 <xsl:choose>
  <xsl:when test="$VarMarker/@name = 'Product'">
   <!-- Some really cool action! -->
   <!--                          -->
  </xsl:when>

  <xsl:otherwise>
   <!-- Ignore markers -->
   <!--                -->
  </xsl:otherwise>
 </xsl:choose>
</xsl:template>

Now what if you had three additional markers, "Gizmo", "Gadget", and "Widget" that you would like to behave just like "Product". Then you would have to again modify the XSL to include these new names:

...
<xsl:choose>
 <xsl:when test="($VarMarker/@name = 'Product') or ($VarMarker/@name = 'Gizmo') or ($VarMarker/@name = 'Gadget') or ($VarMarker/@name = 'Widget')">
  <!-- Some really cool action! -->
  <!--                          -->
 </xsl:when>

 <xsl:otherwise>
  <!-- Ignore markers -->
  <!--                -->
 </xsl:otherwise>
</xsl:choose>
...

However, if one had defined a behavior for the marker, one could simply set the "product" behavior for all similar markers "Product", "Gizmo", "Gadget", and "Widget" via the ePublisher GUI. The XSL stylesheet to support this might look like:

<xsl:template match="wwdoc:Marker" mode="wwmode:textrun">
 <xsl:param name="ParamSplits" />
 <xsl:param name="ParamCargo" />
 <xsl:param name="ParamLinks" />
 <xsl:param name="ParamSplit" />

 <xsl:variable name="VarMarker" select="." />

 <!-- Retrieve marker behavior -->
 <!--                          -->
 <xsl:for-each select="$ParamCargo/wwbehaviors:Behaviors[1]">
  <xsl:variable name="VarMarkerBehavior" select="key('wwbehaviors-wwdocmarkers-by-id', $VarMarker/@id)[1]/../@behavior" />

  <xsl:choose>
   <xsl:when test="$VarMarkerBehavior = 'product'">
    <!-- Some really cool action! -->
    <!--                          -->
   </xsl:when>

   <xsl:otherwise>
    <!-- Ignore markers -->
    <!--                -->
   </xsl:otherwise>
  </xsl:choose>
 </xsl:for-each>
</xsl:template>

where wwbehaviors-wwdocmarkers-by-id is defined as:

<xsl:key name="wwbehaviors-wwdocmarkers-by-id" match="wwdoc:Marker" use="@id" />

While this level of indirection does complicate matters a bit for format developers, it enables "simple users" the ability to invoke these powerful actions with minimal effort (and understanding).

5.2   Defining Behaviors

Defining new marker behaviors requires modifying FTI (Format Trait Info) files. These files fully describe a format's possible values for settings, options, and properties. The ePublisher GUI can then process these FTI files to display the correct interface for users. FTI files give format developers a direct mechanism to extend and enhance the ePublisher GUI.

  1. To add a new behavior, copy the pages.fti from the "Dynamic HTML" format into the project format overrides hierarchy:

    C:\Program Files\WebWorks\ePublisher Pro\Formats\Dynamic HTML\Transforms\pages.fti --> <projectdir>\Formats\Dynamic HTML\Transforms\pages.fti
  2. Edit pages.fti and search for Class name="marker-type".
  3. Add the following items:

    <Item value="meta-split" />
    <Item value="meta-document" />
  4. Save and close pages.fti.

Note

To test changes to an FTI file, one must close the current project and reopen it.

After closing and reopening the test project, open the "Style Designer", click on "Markers", click on "[Default]", and then review the possible values for "Marker type". The list show now include:

meta-split
meta-document

Note

At this time, there is no method available for format designers to localize the string displayed in the menu. This feature will be addressed in future versions of ePublisher.

5.3   Tracking Behaviors

At this point, the standard XSL transforms included with ePublisher will track assigned marker behaviors and make them available via the $ParamBehaviors XSL parameter in the pages.xsl and wrappers.xsl stylesheets. These behaviors ultimately end up in a file named behaviors_finalize.xml in the Data directory hierarchy.

  1. Add the test document

    META Tags.doc to a test project.

  2. Open the "Style Designer" and map the following markers:

    Company --> Marker Type: meta-document
    Help    --> Marker Type: meta-split
    Product --> Marker Type: meta-document
    Topic   --> Marker Type: meta-split
  3. Map the following paragraph styles:

    Heading 1 --> Page break priority: 1
  4. Generate all.
  5. Navigate to the project Data folder.
  6. Search for behaviors_finalize.xml and open the file.

The assigned behaviors should show up in this file as expected.

5.4   Using Behaviors

Time to extract our META markers.

To begin, one must understand the structure of the behaviors_finalize.xml file. For every source document, there is a single corresponding behaviors_finalize.xml file. Inside that file, each page split is represented by a <Split> element. Using XPath queries, one can retrieve all meta-split and meta-document markers.

Assuming behaviors_finalize.xml is defined as:

<?xml version=1.0" encoding="utf-8"?>
<Behaviors version="1.0" xmlns="urn:WebWorks-Behaviors-Schema">
 <Split id="9000001" documentposition="1">
  <Paragraph id="9000001" documentposition="1" splitpriority="none">
   <Marker behavior="meta-document">
    <Marker id="6000001" name="Product" xmlns="urn:WebWorks-Document-Schema">
     <TextRun id="7000001">
      <Text value="ePublisher" />
     </TextRun>
    </Marker>
   </Marker>
  </Paragraph>
 </Split>

 <Split id="9000001" documentposition="2">
  <Paragraph id="9000002" documentposition="2" splitpriority="none">
   <Marker behavior="meta-split">
    <Marker id="6000002" name="Topic" xmlns="urn:WebWorks-Document-Schema">
     <TextRun id="7000002">
      <Text value="single source" />
     </TextRun>
    </Marker>
   </Marker>
   <Marker behavior="meta-document">
    <Marker id="6000001" name="Product" xmlns="urn:WebWorks-Document-Schema">
     <TextRun id="7000001">
      <Text value="AutoMap" />
     </TextRun>
    </Marker>
   </Marker>
  </Paragraph>
 </Split>
</Behaviors>

then we could expect to see the following output into our HTML files:

File 1::
  <meta name="Product" content="ePublisher,AutoMap" />

File 2::
  <meta name="Product" content="ePublisher,AutoMap" />
  <meta name="Topic" content="single source" />

Using XPath, it is possible to determine the list of possible META markers for the current context:

<xsl:variable name="VarMETADocumentMarkers" select="$ParamBehaviors//wwbehaviors:Marker[@behavior = 'meta-document']/wwdoc:Marker" />
<xsl:variable name="VarMETASplitMarkers" select="$ParamBehaviors/wwbehaviors:Behaviors/wwbehaviors:Split[@id = $ParamSplit/@id]//wwbehaviors:Marker[@behavior = 'meta-split']/wwdoc:Marker" />
<xsl:variable name="VarMETAMarkers" select="$VarMETADocumentMarkers | $VarMETASplitMarkers" />

Next, determine the list of all possible META tag names, including duplicates:

<!-- All META tag names -->
<!--                    -->
<xsl:variable name="VarMETATagNamesAsXML">
 <xsl:for-each select="$VarMETAMarkers">
  <xsl:variable name="VarMETAMarker" select="." />

  <wwbehaviors:META name="{$VarMETAMarker/@name}" />
 </xsl:for-each>
</xsl:variable>
<xsl:variable name="VarMETATagNames" select="msxsl:node-set($VarMETATagNamesAsXML)/*" />

Now determine the unique list of META tag names:

<xsl:key name="wwbehaviors-meta-by-name" match="wwbehaviors:META" use="@name" />

...

<!-- Unique META tag names -->
<!--                       -->
<xsl:variable name="VarUniqueMETATagNamesAsXML">
 <xsl:for-each select="$VarMETATagNames">
  <xsl:variable name="VarMETATagName" select="." />

  <!-- Take first unique entry -->
  <!--                         -->
  <xsl:if test="count($VarMETATagName | key('wwbehaviors-meta-by-name', $VarMETATagName/@name)[1]) = 1">
   <xsl:copy-of select="$VarMETATagName" />
  </xsl:if>
 </xsl:for-each>
</xsl:variable>
<xsl:variable name="VarUniqueMETATagNames" select="msxsl:node-set($VarUniqueMETATagNamesAsXML)/*" />

Define a replacement element to use in a page template:

<wwpage:Replacement name="meta-markers">
 <xsl:for-each select="$VarUniqueMETATagNames">
  <xsl:variable name="VarMETATagName" select="." />

  <!-- Define the META tag content -->
  <!--                             -->
  <xsl:variable name="VarMETATagContent">
   <xsl:for-each select="$VarMETAMarkers[@name = $VarMETATagName/@name]">
    <xsl:variable name="VarMETAMarker" select="." />

    <xsl:for-each select="$VarMETAMarker/wwdoc:TextRun/wwdoc:Text">
     <xsl:variable name="VarText" select="." />

     <xsl:value-of select="$VarText/@value" />
    </xsl:for-each>

    <xsl:if test="position() &lt; last()">
     <xsl:text>,</xsl:text>
    </xsl:if>
   </xsl:for-each>
  </xsl:variable>

  <html:meta name="{$VarMETATagName/@name}" content="{$VarMETATagContent}" />
 </xsl:for-each>
</wwpage:Replacement>

Finally, define a condition element to inform the page template that META markers exist:

<xsl:if test="count($VarMETAMarkers[1]) > 0">
 <wwpage:Condition name="meta-markers-exist" />
</xsl:if>

5.5   META Markers Stylesheet

To allow for reuse, META marker condition and replacement code is stored in a stand-alone stylesheet. Add metamarkers.xsl to the project format overrides hierarchy.

<example>\Formats\Dynamic HTML\Transforms\metamarkers.xsl --> <projectdir>\Formats\Dynamic HTML\Transforms\metamarkers.xsl

Next, include the stylesheet in the pages.xsl and wrappers.xsl stylesheets.

  1. Include the metamarkers.xsl stylesheet:

    <xsl:include href="metamarkers.xsl" />
  2. To enable proper change tracking during development, add metamarkers.xsl to the GlobalActionChecksum:

    <xsl:value-of select="concat(',', wwuri:AsFilePath('metamarkers.xsl'), ':', wwfilesystem:GetChecksum(wwuri:AsFilePath('metamarkers.xsl')))" />

5.6   Enable META Markers

For each stylesheet, pages.xsl and wrappers.xsl, add a call to the approriate XSL template to insert <wwpage:Condition> and <wwpage:Replacement> elements.

In pages.xsl:

<xsl:variable name="VarConditionsAsXML">
 ...
 <xsl:call-template name="METAMarkers-Conditions">
  <xsl:with-param name="ParamBehaviors" select="$ParamBehaviors" />
  <xsl:with-param name="ParamSplit" select="$ParamSplit" />
 </xsl:call-template>
</xsl:variable>
<xsl:variable name="VarConditions" select="msxsl:node-set($VarConditionsAsXML)" />

...

<xsl:variable name="VarReplacementsAsXML">
 ...
 <xsl:call-template name="METAMarkers-Replacements">
  <xsl:with-param name="ParamBehaviors" select="$ParamBehaviors" />
  <xsl:with-param name="ParamSplit" select="$ParamSplit" />
 </xsl:call-template>
</xsl:variable>
<xsl:variable name="VarReplacements" select="msxsl:node-set($VarReplacementsAsXML)" />

In wrappers.xsl, the addition is the same, except that $ParamSplit should be replaced with $ParamSplitsFrame/wwsplits:Wrapper:

<xsl:variable name="VarConditionsAsXML">
 ...
 <xsl:call-template name="METAMarkers-Conditions">
  <xsl:with-param name="ParamBehaviors" select="$ParamBehaviors" />
  <xsl:with-param name="ParamSplit" select="$ParamSplitsFrame/wwsplits:Wrapper" />
 </xsl:call-template>
</xsl:variable>
<xsl:variable name="VarConditions" select="msxsl:node-set($VarConditionsAsXML)" />

...

<xsl:variable name="VarReplacementsAsXML">
 ...
 <xsl:call-template name="METAMarkers-Replacements">
  <xsl:with-param name="ParamBehaviors" select="$ParamBehaviors" />
  <xsl:with-param name="ParamSplit" select="$ParamSplitsFrame/wwsplits:Wrapper" />
 </xsl:call-template>
</xsl:variable>
<xsl:variable name="VarReplacements" select="msxsl:node-set($VarReplacementsAsXML)" />

META tags are now ready for processing by a page template.

5.7   Update Page.asp

The conditions are set, the replacements are defined, so now it is time to modify Page.asp to use these new settings.

  1. Open Page.asp.
  2. Under the existing META tags, add:

    <meta name="META Markers" content="I will be replaced!" wwpage:condition="meta-markers-exist" wwpage:replace="meta-markers" />
  3. Save and close.
  4. Generate all.
  5. Confirm results in output HTML files.

6   Conclusion

ePublisher continues the tradition established by Publisher 2003 for end-user customization and extension. While the implementation has shifted from a custom macro language to XSL, ePublisher has not forgone the idea that users should be able to modify any and all aspects of their custom formats.

Where ePublisher and Publisher 2003 drastically differ lies in the design of those customizations. Publisher 2003 lent itself well to quick and dirty fixes to accomplish a set task. Unfortunately, those "quick fixes" became long lived hacks that were difficult to maintain and impossible to use without intimate knowledge of the source template.

ePublisher enables end-users to smoothly extend formats with custom features that appear right along side Quadralay defined features. Quadralay uses the same mechanisms that are available to end-users. If Quadralay can do it, so can any format developer.

DevCenter/Projects/MetaTags (last edited 2009-06-03 18:53:05 by MarshaLofthouse)