Archive for the ‘Data Content Records’ Category

in a component, you can link to a specific page by using $PAGE_LINK[name of page].
for example $PAGE_LINK[help/howto] will link to the current site help/howto.page.

Using $URL_PREFIX works in a similar way so that area relative paths can be used,e.g. $URL_PREFIX/images/icons/favicon.png.

Advertisements

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.

/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.acme;

import com.interwoven.cssdk.access.CSAuthenticationException;
import com.interwoven.cssdk.access.CSAuthorizationException;
import com.interwoven.cssdk.access.CSExpiredSessionException;
import com.interwoven.cssdk.common.CSClient;
import com.interwoven.cssdk.common.CSException;
import com.interwoven.cssdk.common.CSRemoteException;
import com.interwoven.cssdk.factory.CSFactory;
import com.interwoven.cssdk.factory.CSLocalFactory;
import com.interwoven.cssdk.filesys.CSExtendedAttribute;
import com.interwoven.cssdk.filesys.CSSimpleFile;
import com.interwoven.cssdk.filesys.CSVPath;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.tidy.Tidy;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
*
* @author laurent
*
*/
public class MediaSurfaceArticleDownloader {

private DocumentBuilder builder;
private XPathExpression xPathExpressionDate;
private XPathExpression xPathExpressionTitle;
private SimpleDateFormat dateFormat = new SimpleDateFormat(“dd MMMM yyyy”);
private SimpleDateFormat reverseDateFormat = new SimpleDateFormat(“yyyyMMdd”);
private Transformer transformer;
private CSClient client;
private CSExtendedAttribute[] extendedAttributes = new CSExtendedAttribute[1];

public MediaSurfaceArticleDownloader() {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
this.builder = factory.newDocumentBuilder();
System.out.print(“Preparing XPath pattern…”);
XPathFactory xPathfactory = XPathFactory.newInstance();
XPath xPath = xPathfactory.newXPath();
this.xPathExpressionDate = xPath.compile(“/article/date”);
this.xPathExpressionTitle = xPath.compile(“/article/title”);
System.out.println(“Done”);
TransformerFactory tFactory = TransformerFactory.newInstance();
this.transformer = tFactory.newTransformer();
System.out.print(“Connecting to localhost…”);
Properties localProperties = new Properties();
localProperties.setProperty(“cssdk.cfg.path”, “/apps/interwoven/teamsite/cssdk/cssdk.cfg”);
CSFactory csFactory = CSLocalFactory.getFactory(localProperties);
this.client = csFactory.getClientForCurrentUser(new Locale(“en”, “uk”), “MediaSurfaceArticleDownloader”, “localhost”);
System.out.println(“Done”);
System.out.print(“Preparing extended attributes…”);
extendedAttributes[0] = new CSExtendedAttribute(“TeamSite/Templating/DCR/Type”, “intranet/article”);
System.out.println(“Done”);
} catch (CSAuthenticationException ex) {
Logger.getLogger(MediaSurfaceArticleDownloader.class.getName()).log(Level.SEVERE, null, ex);
} catch (CSRemoteException ex) {
Logger.getLogger(MediaSurfaceArticleDownloader.class.getName()).log(Level.SEVERE, null, ex);
} catch (CSException ex) {
Logger.getLogger(MediaSurfaceArticleDownloader.class.getName()).log(Level.SEVERE, null, ex);
} catch (ParserConfigurationException ex) {
Logger.getLogger(MediaSurfaceArticleDownloader.class.getName()).log(Level.SEVERE, null, ex);
} catch (TransformerConfigurationException ex) {
Logger.getLogger(MediaSurfaceArticleDownloader.class.getName()).log(Level.SEVERE, null, ex);
} catch (XPathExpressionException ex) {
Logger.getLogger(MediaSurfaceArticleDownloader.class.getName()).log(Level.SEVERE, null, ex);
}
}

private void download(URL targetURL) {
Document document;
String targetDirectoryName = “/default/main/intranet/WORKAREA/Compliance/templatedata/intranet/article/data/compliance/news”;
byte[] fileBytes = new byte[255];
try {

// read the targetURL and create an XML file with it
InputStream inputStream = targetURL.openStream();
String outputFileName = targetDirectoryName + “/” + targetURL.getFile().replaceAll(“/compliance/whats-new/(.*)\\?.*$”, “$1”);
System.out.println(“output file name: ” + outputFileName);
OutputStream outputStream = new FileOutputStream(outputFileName);
int bytesRead;
while ((bytesRead=inputStream.read(fileBytes))!= -1) {
outputStream.write(fileBytes, 0, bytesRead);
}

// set the metadata on the file
CSSimpleFile inputFile = (CSSimpleFile) client.getFile(new CSVPath(outputFileName));
inputFile.setExtendedAttributes(extendedAttributes);

// rename the file with the article date and the title
// we need to re-open the stream that the document builder has closed for us.
InputSource inputSource = new InputSource(new FileInputStream(inputFile.getVPath().getPathNoServer().toString()));
String dateAsString = xPathExpressionDate.evaluate(inputSource);
String reverseDateString;
Date newsDate = new Date();
try {
newsDate = dateFormat.parse(dateAsString);
} catch (Exception e) {
newsDate = new Date();
}
try {
reverseDateString = reverseDateFormat.format(newsDate);
} catch (Exception e) {
reverseDateString = “19700101”; /* use the epoch */
}

// we need to re-open the stream that the date XPath evaluation has closed for us.
inputSource = new InputSource(new FileInputStream(inputFile.getVPath().getPathNoServer().toString()));
String title = xPathExpressionTitle.evaluate(inputSource);
String titleNoSpace = title.replaceAll(“\\s“, “-“);

// come up with the file name that will contain this DCR
outputFileName = reverseDateString + “-” + titleNoSpace;
outputFileName = outputFileName.replaceAll(“(<B>|</B>|<I>|</I>)”,””);
outputFileName = outputFileName.replaceAll(“[‘\\\\/:*?\”<>|,&;’`]”,””);
System.out.println(“renaming file to ” + outputFileName);
inputFile.rename(outputFileName, true);

} catch (CSAuthorizationException ex) {
Logger.getLogger(MediaSurfaceArticleDownloader.class.getName()).log(Level.SEVERE, null, ex);
} catch (CSExpiredSessionException ex) {
Logger.getLogger(MediaSurfaceArticleDownloader.class.getName()).log(Level.SEVERE, null, ex);
} catch (CSRemoteException ex) {
Logger.getLogger(MediaSurfaceArticleDownloader.class.getName()).log(Level.SEVERE, null, ex);
} catch (CSException ex) {
Logger.getLogger(MediaSurfaceArticleDownloader.class.getName()).log(Level.SEVERE, null, ex);
} catch (XPathExpressionException ex) {
Logger.getLogger(MediaSurfaceArticleDownloader.class.getName()).log(Level.SEVERE, null, ex);
//        } catch (TransformerConfigurationException ex) {
//            Logger.getLogger(MediaSurfaceArticleDownloader.class.getName()).log(Level.SEVERE, null, ex);
//        } catch (TransformerException ex) {
//            Logger.getLogger(MediaSurfaceArticleDownloader.class.getName()).log(Level.SEVERE, null, ex);
//        } catch (SAXException ex) {
//            Logger.getLogger(MediaSurfaceArticleDownloader.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(MediaSurfaceArticleDownloader.class.getName()).log(Level.SEVERE, null, ex);
}

}

public void seek(boolean download) {
InputStream inputStream = null;
try {

//URL articlesURL = new URL(“file:///home/piquetl/WIM-1734/mediasurface.source.articles.xml“);
//URL articlesURL = new URL(“http://webcontent/whatsnew/compliance/?view=toTeamSite“);
URL articlesURL = new URL(“http://webcontent/compliance/whats-new/?view=toTeamSite“);
inputStream = articlesURL.openStream();
InputSource inputSource = new InputSource(inputStream);
Document document = builder.parse(inputSource);
NodeList articleNodes = document.getElementsByTagName(“article”);

for (int i = 0; i < articleNodes.getLength(); i++) {
URL articleURL = new URL(articleNodes.item(i).getFirstChild().getNodeValue());
//System.err.println(articleURL.getPath());

if (!articleURL.getFile().contains(“.pdf”) &&
!articleURL.getFile().contains(“.doc”) &&
!articleURL.getFile().contains(“.xls”) &&
!articleURL.getFile().contains(“.ppt”)) {
System.out.println(articleNodes.item(i).getFirstChild().getNodeValue());
if (download) {
download(articleURL);
}
}
}

} catch (SAXException ex) {
Logger.getLogger(MediaSurfaceArticleDownloader.class.getName()).log(Level.SEVERE, null, ex);
} catch (MalformedURLException ex) {
Logger.getLogger(MediaSurfaceArticleDownloader.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(MediaSurfaceArticleDownloader.class.getName()).log(Level.SEVERE, null, ex);
} finally {
try {
inputStream.close();
} catch (IOException ex) {
Logger.getLogger(MediaSurfaceArticleDownloader.class.getName()).log(Level.SEVERE, null, ex);
}
}

}

/**
* @param args the command line arguments
*/
public static void main(String[] args) {
MediaSurfaceArticleDownloader urlDownloader = new MediaSurfaceArticleDownloader();
urlDownloader.seek(true); // true for download, false otherwise
}
}

Generally speaking, when users enter data into a page, they think the page and the content are synonymous. This is why the components on the page have datum elements in them to allow the user to do just that. The page also contains elements of the presentation through for example the positioning of the components. This does go against the MVC principles of separating data from presentation.

Teamsite does offer its authors the chance to remediate all that through the use of DCRs, or Data Content Records. By having a datum of type DCR, this enables to point the page to the data, thus separating the concerns. Your content can be re-used as many times as you want (for example to generate a PDF document or import it into a 3rd party application) without changing your site no more.

Whenever you see people enter the data directly into the page, fight it. it pays off in the long run – always.