Archive for the ‘External’ Category

When you have an external in a SitePublisher component and the Datum parameter has a replicatable attribute set to yes, the parameter that gets passed to the external is the ReplicantSourceID made of comma separated list of the values of the replicants.

<External>
 <Parameters>
 <Datum Type="String" ID="dcrDir" Name="dcrDir" Replicatable="true" ReplicantSourceID="dcrDir">/templatedata/acme/article/data/</Datum>
 <Datum Type="String" ID="dcrDir-0" Name="dcrDir" Replicatable="true" ReplicantSourceID="dcrDir">/templatedata/acme/author/data/</Datum>
 </Parameters>
 <Object Scope="local">com.acme.SomeClass</Object>
 <Method>someMethod</Method>
</External>

Only thing, there’s a space before the comma and [] around the lot, so make sure to trim() the string after the split and remove the []!

String[] dcrDirs = context.getParameterString("dcrDir").split(",");
for (String dcrDir : dcrDirs) {
  String fullDcrDir = fileDal.getRoot() + dcrDir.trim();
  fullDcrDir = fullDcrDir.replace("[","");
  fullDcrDir = fullDcrDir.replace("]","");
  ...
}

Localisation is always a bit tricky. Here’s an easy solution.

First, we’re going to create dictionaries for each of our supported languages. They are going to be content managed by the user in DCRs. In our workarea, we add a “common/dictionary” data type. For this, we create the following structure:

templatedata/
    common/
        dictionary/
            data/
            presentation/
            datacapture.cfg

The content of datacapture.cfg would be like this:

<?xml version="1.0" encoding='UTF-8'?>
<!DOCTYPE data-capture-requirements SYSTEM "datacapture6.0.dtd">
<data-capture-requirements name="dictionary">
 <ruleset name="dictionary">
<root-container name="dictionary" location="dictionary">
<container name="entry" location="entry" min="0" max="unbounded" default="1" combination="and">
 <item name="key" pathid="key">
 <label>Key</label>
 <description>words to translate</description>
 <text required="t" size="100" />
 </item>
 <item name="translation" pathid="translation">
 <label>Translation</label>
 <description>translation of the words</description>
 <text required="t" size="100" />
 </item>
</container>
</root-container>
 </ruleset>
</data-capture-requirements>

Update templating.cfg and create a new dictionary per language to support. I call mine acme.fr and I place the sentences I want to translate and their translations in it. Such as “Hello World!” -> “Bonjour tout le monde!”

Now that we have the data somewhat sorted, we can do the business logic. First, we compile a class that will read the DCR containing our dictionary and the translations so that the component can call it and add the XML it returns to its data model. That data model will then be read by the XSL to display our translation.

package com.acme.components;

import com.interwoven.livesite.dom4j.Dom4jUtils;
import com.interwoven.livesite.file.FileDALIfc;
import com.interwoven.livesite.runtime.RequestContext;
import java.io.InputStream;
import org.dom4j.Document;
import org.dom4j.Element;

public class i18n {

 public Document getDictionary(RequestContext context) {

 Document doc = Dom4jUtils.newDocument();
 Element rootElement = doc.addElement(this.getClass().getCanonicalName() + ".getDictionary");

 // open and parse the dictionary as an XML document
 FileDALIfc fileDal = context.getFileDAL();                                  // gives us access to the files in Teamsite
 Pattern workareaPattern = Pattern.compile("(.*/)sites/.*");                 // creates a regular expression
 Matcher workareaMatcher = workareaPattern.matcher(context.getSite().getPath()); // evaluates the regular expression against the site's path /default/main/com/WORKAREA/com/sites/europe
 String dictionaryRelativePath = context.getParameterString("dictionary");
 if (workareaMatcher.find() && dictionaryRelativePath != null && !dictionaryRelativePath.equals("")) {
 String workareaPath = workareaMatcher.group(1); // grab the content of the first set of brackets
 String dictionaryFilePath = workareaPath + dictionaryRelativePath;          // computes the absolute path to the dictionary DCR file
 try {
 InputStream dictionaryFileInputStream = fileDal.getStream(dictionaryFilePath);    // opens the dictionary as a stream for reading
 Document dictionaryXMLDoc = Dom4jUtils.newDocument(dictionaryFileInputStream);    // and creates an XML document from it
 rootElement.add(dictionaryXMLDoc.getRootElement());
 } catch (Exception e) {
 // do nothing
 }
 }
 return (doc);
 }
}

We now can add the external call to our component in the content.xml box.
<External>
 <Parameters>
 <Datum ID="dictionary" Name="dictionary" Type="DCR">
 <DCR Category="common" Type="dictionary">templatedata/common/dictionary/data/acme.fr</DCR>
 </Datum>
 </Parameters>
 <Object Scope="local">com.acme.components.i18n</Object>
 <Method>getDictionary</Method>
 </External>

If you save and preview the component, you shall see the translations added to the content XML in the bottom left under the result node. All we have to do is use this in our XSL. The key to doing this is the key (pun intended!).

We add a translation template that will look for a translation for a given key, passed as a parameter:

<xsl:template name="i18n">
 <xsl:param name="key" />
 <xsl:variable name="translation"><xsl:value-of select="/Properties/Data/Result/com.acme.components.i18n.getDictionary/dictionary/entry/key[text()=$key]/../translation" /></xsl:variable>
 <xsl:choose>
 <xsl:when test="$translation != '' "><xsl:value-of select="$translation" /></xsl:when>
 <xsl:otherwise><xsl:value-of select="$key" /></xsl:otherwise>
 </xsl:choose>
</xsl:template>

If the translation is found, it returns it, otherwise it returns the original text. This is how the translation is called:

<xsl:call-template name="i18n">
 <xsl:with-param name="key">Hello World!</xsl:with-param>
 </xsl:call-template>

The main benefit of this solution is that the translations are content managed and re-usable. We’re not compiling anything every time we want to add new stuff and we can also create the dictionary as a DCR from other sources. Overall, I have found this solution to be quite rapid to implement and very elegant.

package com.acme.components;

import com.interwoven.livesite.dom4j.Dom4jUtils;
import com.interwoven.livesite.file.FileDALIfc;
import com.interwoven.livesite.runtime.RequestContext;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.dom4j.Document;
import org.dom4j.Element;

public class Skeleton {

public Document getSiteMap(RequestContext context) {

// open and parse the sitemap as an XML document
FileDALIfc fileDal = context.getFileDAL();                                  // gives us the root directory of the workarea
String siteMapFilePath = context.getSite().getPath() + “/default.sitemap”;  // computes the path to the default.sitemap file
InputStream siteMapFileInputStream = fileDal.getStream(siteMapFilePath);    // opens the siteMapFile as a stream for reading
Document siteMapXMLDoc = Dom4jUtils.newDocument(siteMapFileInputStream);    // and creates an XML document from it

Document doc = Dom4jUtils.newDocument();
Element rootElement = doc.addElement(this.getClass().getCanonicalName() + “.getSiteMap”);

rootElement.add(siteMapXMLDoc.getRootElement());
return(doc);
}

public Document readRSSFeed(RequestContext context) {
Document doc = Dom4jUtils.newDocument();
Element rootElement = doc.addElement(this.getClass().getCanonicalName() + “.readRSSFeed”);
try {
String feedURLAsString = context.getParameterString(“feedURL”);
String proxyServer = context.getParameterString(“proxyServer”);
int proxyServerPort = Integer.valueOf(context.getParameterString(“proxyServerPort”));
URL feedURL = null;
try {
feedURL = new URL(“http”, proxyServer, proxyServerPort, feedURLAsString);
} catch (MalformedURLException ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
}
Document feed = Dom4jUtils.newDocument(feedURL.openStream());
if (feed != null) {
rootElement.add(feed.getRootElement());
}
} catch (MalformedURLException ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
} catch (Exception ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
}
return (doc);
}
}

As part of your navigation, you probably will have to create your own component. Here’s a crude one that I made.

Content XML

<Data>
  <External>
    <Object Scope="local">com.interwoven.livesite.external.impl.LivesiteSiteMap</Object>
    <Method>getSiteMap</Method>
  </External>
</Data>

Appearance XSL

<xsl:template match="/">
    <div class="navigation"><xsl:apply-templates select="/site-map/segment/node" /></div>
</xsl:template>
<xsl:template match="node">
        <div>
            <xsl:attribute name="title"><xsl:value-of select="description" disable-output-escaping="yes" /></xsl:attribute>
            <xsl:choose>
                <xsl:when test="link/@type != ''">
                    <xsl:comment><!-- we have a query string to process --></xsl:comment>
                    <xsl:variable name="queryString"><xsl:for-each select="link/query-string/parameter">&amp;<xsl:value-of select="name" />=<xsl:value-of select="value" /></xsl:for-each></xsl:variable>
                    <a>
                        <xsl:attribute name="href"><xsl:choose>
                                <xsl:when test="link/@type = 'page'">/<xsl:value-of select="link/value" />.shtml</xsl:when>
                                <xsl:otherwise><xsl:value-of select="link/value" /></xsl:otherwise>
                            </xsl:choose><xsl:choose><xsl:when test="contains(link/value,'?')"><xsl:value-of select="$queryString" /></xsl:when><xsl:otherwise><xsl:value-of select="substring($queryString,2)" /></xsl:otherwise></xsl:choose>
                        </xsl:attribute>
                        <xsl:choose>
                            <xsl:when test="link/@target != ''">
                                <xsl:attribute name="target"><xsl:value-of select="link/@target" /></xsl:attribute>
                            </xsl:when>
                            <xsl:otherwise></xsl:otherwise>
                        </xsl:choose>
                        <xsl:value-of select="label" disable-output-escaping="yes" />
                        <xsl:comment><!-- unfortunately, we can't process the "preserve query string parameters" instruction in this stylesheet, as it will be used from the development side, not the delivery side --></xsl:comment>
                    </a>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="label" disable-output-escaping="yes" />
                </xsl:otherwise>
            </xsl:choose>
        </div>
        <div style="margin-left: 15px">
            <xsl:apply-templates select="node" />
        </div>
    </xsl:template>