Contents
- Introduction
- .NET XSLT Extension Objects
- Creating an Extension Object
- Using Extension Objects – The Old Way
- Using Extension Objects – The New Way
- XSLT Page Template
- Conclusion
- Source Code
- Resources
Introduction
In the previous article I’ve discussed the fundamentals of setting up the XSLT Mediator, configuring it and then showed how to create a simple compound component template which is making use of an XSLT TBB.
In this article I would like to concentrate mainly on extension objects; a little bit on what they are and how to make use of these in conjunction with the mediator.
.NET XSLT Extension Objects
The .NET Framework allows us to extend the style sheets we build by creating Extension Objects, these can be simple classes that contain helper or utility methods.
Some functionality is either extremely difficult or impossible to implement in pure XSLT code, for example we may want to retrieve a list of items from Tridion, doing this in just XSLT will be complicated and unmanageable to say the least.
The extension feature allows writing the logic for this kind of functionality in .NET managed code, we may even already written this code for a different part of our implementation and we simply want to reuse it.
If this concept is still unclear I am sure it will become more so further in the article, I will also add some links to other resources on the web that cover this area more thoroughly.
Creating an Extension Object
We’ll begin by creating a simple class with some useful methods that we can reuse in multiple templates:
using System.Text;
using System.Xml;
using Tridion.ContentManager;
using Tridion.ContentManager.ContentManagement;
using Tridion.ContentManager.Publishing.Rendering;
using Tridion.ContentManager.Templating;
namespace Tridion.Extensions.Templating.XsltHelpers
{
public class SimpleXsltHelper
{
private TemplatingLogger m_Logger = null;
private Engine m_Engine = null;
public SimpleXsltHelper(Engine engine)
{
m_Engine = engine;
m_Logger = TemplatingLogger.GetLogger(this.GetType());
}
///
/// Returns the result of rendering the provided component and compomnent tempalte in a xml document
///component = the TCM URI of the component
///componentTemplate = the TCM URI of the component template
public XmlDocument RenderComponentPresentation(string component, string componentTemplate)
{
StringBuilder sb = new StringBuilder();
using (XmlWriter xWriter = XmlWriter.Create(sb))
{
xWriter.WriteStartElement("result");
TcmUri compURI = new TcmUri(component);
TcmUri ctURI = new TcmUri(componentTemplate);
xWriter.WriteRaw(m_Engine.RenderComponentPresentation(compURI, ctURI));
xWriter.WriteEndElement(); //result
}
XmlDocument resultDoc = new XmlDocument();
resultDoc.LoadXml(sb.ToString());
return resultDoc;
}
/// Used to publish a binary to the presentation server
/// compURI = the TCM URI of the multimedia component to publish
/// Returns the publish path of the binary
public string PublishBinary(string compURI)
{
Component comp = m_Engine.GetObject(compURI) as Component;
string publishPath = comp.Id;
if (comp.BinaryContent != null)
{
Binary pubBinary = m_Engine.PublishingContext.RenderedItem.AddBinary(comp);
m_Logger.Debug("PublishBinary: published Binary: " + comp.Id + " to url: " + pubBinary.Url);
publishPath = pubBinary.Url;
}
else
m_Logger.Warning("PublishBinary: " + comp.Id + " is not a multimedia coomponent");
return publishPath;
}
}
}
Here’s an explanation of the code:
The constructor of the class accepts a single parameter, an instance of the Engine (Tridion.ContentManager.Templating.Engine) class.
RenderComponentPresentation(string component, string componentTemplate) – this method accepts the TCM URI of a component and the TCM URI of a component template, renders the two and returns the resulting content in an xml document.
PublishBinary(string compURI) – this method accepts the TCM URI of a multimedia component and using the AddBinary method makes sure the binary is published to presentation server. The method returns the relative path of the published binary file.
The reason for creating this class is to be able to call the methods defined in it from an XSLT TBB, the next couple of sections demonstrate ways of accomplishing this.
Using Extension Objects – The Old Way
When I released the XSLT Mediator there was only one way for adding extension objects. To add an object to the transformation we had to create the class in the Mediator’s assembly project and inject the line of code for adding the object into the mediator’s class.
I’ve later improved this a bit by making the method in charge of adding extension objects Virtual so a developer can create their own mediator inheriting from the Tridion.Extensions.Mediators.XsltMediator and overriding that method.
So a project with a custom extension object will look something like this:
In the image above you can see the helper class (which we’ll use as an extension object) and the custom mediator inheriting from the XsltMediator.
The code for our custom mediator will look like this:
using System.Xml.Xsl;
using Tridion.Extensions.Mediators;
namespace XSLTMediator.Tutorials
{
public class MyCustomMediator : XsltMediator
{
protected override void AddExtensionObjects(XsltArgumentList arglist)
{
SimpleXsltHelper helper = new SimpleXsltHelper(this.Engine);
arglist.AddExtensionObject("http://www.tridion.com/customhelper", helper);
base.AddExtensionObjects(arglist);
}
}
}
The code is very straightforward, We override the virtual method AddExtensionObjects which accepts a single parameter of type System.Xml.Xsl.XsltArgumentList.
To the arglist parameter we can add our extension objects using its method AddExtensionObject(string, object).
The method expects two parameters, the first, a String, which should contain a unique namespace within the transformation. This can be just about any string of characters you decide to use as long as its unique.
The second parameter is the actual extension object which is an instance of the SimpleXsltHelper class we created earlier.
The last line of code to mention is
base.AddExtensionObjects(arglist);
This makes sure that the helper extension object which is bundled with the mediator is also available in the transformation. If you don’t plan on using this helper object you can simply omit that line of code from your implementation.
To be able to start using our new extension object there is one last step which is needed. In the previous article I’ve showed how to configure the basic mediator in the Tridion configuration file.
First build the project and overwrite the existing Mediator DLL file with the new build.
Since now we have a new custom implementation of the mediator we need to configure this new class name instead.
So in the Tridion.ContentManager.config file change the type attribute of the XSLT mediator from: Tridion.Extensions.Mediators.XsltMediator to XSLTMediator.Tutorials.MyCustomMediator
(the namespace and class name will obviously be different in real implementations).
Make sure you restart all the necessary services as explained in the first article.
Now we can go ahead and use the new methods we created in our XSLT templates.
We will go on and extend the XSLT template used in the previous article:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:simple="uuid:3CC625ED-34E6-4B51-BDD8-72BBD0D71469"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:tcm="http://www.tridion.com/ContentManager/5.0"
xmlns:customHelper="http://www.tridion.com/customhelper"
xmlns:image="http://www.tridion.com/ContentManager/5.0/DefaultMultimediaSchema" exclude-result-prefixes="msxsl simple customHelper">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:element name="p">
<xsl:apply-templates select="tcm:Component/tcm:Data/tcm:Content/simple:Content"/>
</xsl:element>
</xsl:template>
<xsl:template match="simple:Content">
<xsl:element name="h3">
<xsl:value-of select="simple:title"/>
</xsl:element>
<xsl:element name="div">
<xsl:copy-of select="simple:body"/>
</xsl:element>
<xsl:element name="div">
Publish Date: <xsl:value-of select="simple:pubDate"/>
</xsl:element>
<xsl:element name="img">
<xsl:attribute name="alt">
<xsl:value-of select="document(simple:image/@xlink:href)/tcm:Component/tcm:Data/tcm:Metadata/image:Metadata/image:altText"/>
</xsl:attribute>
<xsl:attribute name="src">
<xsl:value-of select="customHelper:PublishBinary(simple:image/@xlink:href)"/>
</xsl:attribute>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
First thing to notice is the declaration of the helper class namespace at the top:
xmlns:customHelper=”http://www.tridion.com/customhelper”. The important part is the namespace itself, it should be exactly the same as the one used in the Mediator’s code.
The prefix we assign this namespace can be anything we decide on as long as its unique within the context of our template.
The second part to notice is the call to our PublishBinary() method, using the prefix defined previously we can call any public method defined in the extension object’s class (As long as we use valid parameter data types). Since this method returns the publish path of the binary being published in our example we can use the returned value to set the ‘src’ attribute of the ‘img’ HTML element.
Notice that this time when we execute the compound template there is no need for the default finish actions, the image is published by the XSLT Template and the extension object.
Hopefully this simple example shows how extension objects can be used to augment XSLT templates in such a way that provides a very robust and manageable model.
Using Extension Objects – The New Way
As can be seen from previous sections its quite simple to create XSLT templates for compound templates and even add custom logic in the form of extension objects.
One thing bothered me about this process and that is that this type of templating model basically forces managing code in two locations, two projects: one is the templates projects which is uploaded into Tridion using the assembly upload tool and the second is the Mediator project which during a project and/or implementation is updated with extension objects and helper methods.
These two projects have to be maintained separately even though they both contain functionality which is ultimately used for one purpose – templates.
Because of this issue I came up with a new and in my view better way of creating and maintaining extension objects. This way allows you to create the class(es) in the same project you develop your templates.
The Mediator then loads the extension object(s) from Tridion in a similar manner to how Tridion loads .NET templates.
Using this new way you no longer need to keep updating the Mediator project, you can simply use the provided mediator as is.
To create extension objects in the Templates project you need a few things, if you are using the base project (available on world) you’ll have an easier time because everything you need is provided there.
First, any class which is meant to be used as an EO (Extension Object) should implement an interface, this is an interface you can create yourself or you can use the one in the base project, IXsltHelper. it doesn’t really matter which interface it is actually as we will see in a minute.
The second thing needed is to decorate any class used as an EO with an attribute class, TridionXSLTHelperNSAttribute. This attribute is used to define the namespace the EO will be assigned with when inserted into the argument list.
If you’re not using the base project or using an old version of it, simply download the new project from SDLTridionWorld and copy the TridionXSLTHelperNSAttribute class file to your project.
Your project should have similar elements to this example:
Since we already created a sample extension object we can reuse it by adding it to our templates project.
We will need to modify it a bit to conform to our new style:
using System.Text;
using System.Xml;
using Tridion.ContentManager;
using Tridion.ContentManager.ContentManagement;
using Tridion.ContentManager.Publishing.Rendering;
using Tridion.ContentManager.Templating;
using Tridion.Extensions.ContentManager.Templating;
namespace Tridion.Extensions.Mediators
{
[TridionXSLTHelperNS("http://www.tridion.com/customhelper")]
public class SimpleXsltHelper : IXSLTHelper
{
private TemplatingLogger m_Logger = null;
private Engine m_Engine = null;
.
.
.
Notice that now the SimpleXsltHelper is implementing our IXSLTHelper interface and has the TridionXSLTHelperNS attribute. The value we pass the attribute is the same namespace we used earlier so that means there is no need to change the XSLT at all.
The mediator will take care of the rest, but to make sure it does we need to add a few configuration parameters to the Tridion.ContentManagaer.config file in this fashion:
<mediator matchMIMEType="text/xml" type="Tridion.Extensions.Mediators.XsltMediator" assemblyPath="C:\Program Files\Tridion\bin\Tridion.Extensions.Mediators.dll">
<parameters>
<parameter name="loadFromTridion" value="true"/>
<parameter name="interfaceName" value="IXSLTHelper"/>
<parameter name="templateID" value="tcm:42-15958-2048"/>
</parameters>
</mediator>
Let’s go over the parameters:
loadFromTridion: True or False telling the mediator whether to look for extension objects in Tridion.
interfaceName: The Mediator loads the assembly for Tridion and will look for classes that implement an interface with this name.
templateID: The TCM URI of the template building block containing the templates assembly.
As always make sure to restart all the necessary services after making the configuration changes.
From now on you can create and modify your extension objects as if they were any other .NET template you were working on. Updating the code in Tridion requires the same process as do normal templates and there are no extra steps needed.
It also means you have only one project containing all your templating functionality code which I think is a massive improvement.
Running our compound CT again gives us the exact same result as before:
XSLT Page Template
Up until now we have been working on a compound component template that was using a XSLT TBB but it wasn’t doing much more than what could be accomplished with normal XSLT CT.
Here is an example of a simple XSLT page template:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:tcm="http://www.tridion.com/ContentManager/5.0"
xmlns:customHelper="http://www.tridion.com/customhelper"
exclude-result-prefixes="msxsl customHelper">
<xsl:output method="html" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/">
<html>
<head>
<title>
<xsl:value-of select="tcm:Page/tcm:Data/tcm:Title/text()"/>
</title>
</head>
<body style="background-color:yellow;">
<xsl:for-each select="tcm:Page/tcm:Data/tcm:ComponentPresentations/tcm:ComponentPresentation">
<p>
<xsl:variable name="cp" select="customHelper:RenderComponentPresentation(tcm:Component/@xlink:href, tcm:ComponentTemplate/@xlink:href)"/>
<xsl:copy-of select="$cp/result/node()"/>
</p>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
As with the Component Template created in the first part of this tutorial, you will need to create a XSLT TBB and paste the above code into the source pane, then create a new compound page template in the Template Builder and add the new XSLT TBB to the list of selected templates.
Save and close the new PT and create a new page based on this page template.
Now we can preview our XSLT page template successfully:
Very easily we created a new page template in XSLT, to render the component presentations on the page we use a method we added to the extension object which returns the resulting content and layout in an XML document that is stored in a variable from which we can copy it onto the page.
Conclusion
In this article I focused mainly on creating extension objects and using them from XSLT templates.
I showed two ways of including extension objects in the transformation of templates, both are valid but the second is much more convenient in terms of deploying and managing the functionality of extension objects..
I’ve even added a simple example of a XSLT page template which is where the benefit of using the XSLT Mediator is really obvious since Tridion doesn’t support this type of template natively.
The XSLT Mediator comes with an extension object which is added to the transformation by default. The namespace for this object is: “http://www.sdltridion.com/ps/XSLTHelper”
This default object comes with some useful methods:
getFormatedDate – Generic XSLT helper function to format date strings using standard format strings.
getListItems – Generic XSLT helper function to retrieve items from within an OrganizationalItem
GetMultimediaInfo – Returns xml with information about the MM component such as the mime type or extension
Source Code
Can be downloaded here.
hi,
i am working with latest version of sdl tridion 2009. i want to change the component using TOM.net api.
Example: i have a schema that has field Name. & i created a component called Person. using tom.net i want to change the value of Name field.
can you help me out how can i do that.
also .
1. how can i connect to the Tridion system to generate a session object
so that i can do following
Component myComponent = (Component)mySession.GetObject(“tcm:1-8”);
ItemFields contentFields = new ItemFields(myComponent.Content, myComponent.Schema);
TextField myTextField = (TextField)contentFields[“MyTextFieldName”];
myTextField.Value = “New Value”‘;
HI Chandra,
Thanks for reading my blog.
To answer your questions:
1. Changing values with TOM.NET: Im afraid this is not possible still with the latest version of Tridion. the TOM.NET API is still read-only. This will be changed with the next release(2010).
2. To be able to modify items in Tridon from code you will need to use the interops for .NET. you can find these in the Tridion installation folder and then: Bin/Client/PIA.
You can then write something along these lines:
using Tridion.ContentManager.Interop.TDS;
using Tridion.ContentManager.Interop.TDSDefines;
.
.
.
TDSE tdse = new TDSE();
tdse.Initialize();
Component myComp = tdse.GetObject(“tcm:1-8”, 2, null, XMLReadFilter.XMLReadAll);
myComp.Fields[“fieldName”].value[1] = “New Value”;
myComp.Save(true);
.
.
.
The entire COM API (the one used by the interops) is documented in the Templating and Customization Manual TOM 2009.chm.
Let me know if i can be of more help.
Dont forget you can ask questions and get more information on our forum: https://forum.tridion.com and find great stuff on http://www.sdltridionworld.com
Hi yoavniran, chandra
I want to access Image using above mycomp.Fields[] way.
Can i get the System.Drawing.Image from the component using above.
if yes how
if no please tell what can be done.
how can i get the image. can you give some pointers.
Thanks a lot.
Hi there,
If you want to get the image binary data you can do the following:
using the .NET interops:
Image image = Image.FromStream(new MemoryStream(comp.Multimedia.GetBinaryContent()));
using the TOM.NET API:
Image image = image.FromStream(new MemoryStream(comp.BinaryContent.GetByteArray()));
If you need this functionality to for example publish thumbnails of your images on the fly, make sure you check out the documentation for using the AddBinary method and variants so Tridion will manage the publishing and un-publishing of the thumbnails in your templates.
Hey Yoavniran, a lot of thanks for your help.
i have another question if you can help,
I was trying using normal XSLT component template(CT). My component contains some multimedia object. I want the image path or video path in the XSLT. Is it possible with the normal XSLT without using Compound template,
Can you help me out.
Regards
Raj Mittal.
Yes, you can.
If you create a normal CT and change the type to XSLT.
Then you need to declare the Tridion script assistant namespace like so:
xmlns:tcmse=”http://www.tridioncom/ContentManager/5.1/TcmScriptAssistant”
Then in your templates you can use the assistant’s exposed methods, one of them is of course PublishBinary:
I believe you can also use the AddBinary method and even pass the SG URI like so:
We’re using the XSLT mediator in one of our projects to output asp.net pages. These pages contain asp.net tags and custom tags. When outputting an asp.net tag, the resulting tag always contains a namespace declaration, for instance: xmlns:asp=”remove”.
I’ve modified the XSLT Mediator code to remove all the namespaces containing the word “remove” using a regex. When previewing the page in the Tridion GUI or in template builder, these namespaces are indeed removed. However, the actual published page on the webserver still contains these namespaces.
Any ideas on how to get rid of these namespaces?
Hi yoavniran,
I followed the same steps that explain above.
I am trying to use
But it is throwing error “Unexpected XML declaration. The XML declaration must be the first node in the document, and no white space characters are allowed to appear before it.”
Can you please suggest where I am doing wrong?
Also I am expecting XHTML output from this method, the same way I am getting on TBB using DWT.
Thanks
Hi there Piyush,
Sorry to hear you’re having difficulties. Can you please post the stack trace that is outputted with the error.
Also try using a very simple XSLT, one that only outputs a single text field just for testing and see whether that makes a difference.
The default output type for the Mediator is XHTML and you can also control the output type using the parameters schema that I included in the downloadable zip file on SDLTridionWorld.
Yoav.
Hi Yoavniran,
The method that I am trying to use from the above code is RenderComponentPresentation which is returing XMLDocument as per the above code, But I am expecting XHTML output from this method.
Even when I am using the same method and call on XSLT it is throwing error as below:
(2147747185) Unexpected XML declaration. The XML declaration must be the first node in the document, and no white space characters are allowed to appear before it. Line 1, position 207.
Unable to get rendered content of Component Template (tcm:6-283-32).
Unable to retrieve rendered data from Component Presentation.
An error occurred during a call to extension
Please help me to resolve this problem.
Thanks.
Hi again,
The RenderComponentPresentation method is actually causing a render of your component template which can be written in XSLT as well or .NET or Dreamweaver.
I would suggest debugging your component template in the Template Builder and see whether its output is actually valid XML (XHTML) and also doesnt include the xml declaration node since its going to be embedded inside the page output.
Good luck.
Hi yoavniran,
Firstly, thank you for the XSLT Mediator and the accompanying documentation. It has helped make my templates simpler by reducing the number of custom .NET building blocks needed per compound template.
I was wondering if it is possible to remote debug the .NET extension functions called from within an XSLT template? Similarly to how I can attach Visual Studios to the server’s TcmTemplateDebugHost.exe and step through my .NET building block code. Attaching to the TcmTemplateDebugHost.exe does not seem to work, is there another process that I should use?
Thanks again,
Eric
Hi Eric,
Im glad to hear that the article has been helpful to you.
I never actually managed to get VS to debug my XSLT extensions when they are called from templates. The problem is I think is that the extension objects are loaded at run time from Tridion.
What you could try doing is if you are on the CM server, try to attach VS to the publisher service and actually publish the item, this might work but Im not sure.
Good luck 🙂
Hi Yoav,
Posted this comment before in part 1 of this series.
I’ve created my own custom XSLT helper class from the IXSLTHelper interface. The helper is loaded from the .NET assembly stored in Tridion. This works great in Template Builder and when publishing items. However, when I do a preview in the GUI I get the following error:
“Cannot find the script or external object that implements prefix ‘http://www.tridion.com/xslthelper’.”
The namespace here is the one I used for the ‘TridionXSLTHelperNS’ attribute.
It appears that the “GetHelpersFromTridion” method is not getting called (or fails) when using preview mode. Have you seen this error before? Any ideas on how to fix this?
As it turned out, a reset of IIS on the GUI server and restart COM+ did the trick!
Hi Sander, Glad its all working now.
Hi Yoav,
Can you please suggest what can be the reason for below error.
I am passing my outputted string xml to the attached xslt in my template builder. I am getting below error:
Cannot find the script or external object that implements prefix ‘http://www.tridion.com/ContentManager/5.1/TcmScriptAssistant’.
at System.Xml.Xsl.Runtime.XmlQueryContext.InvokeXsltLateBoundFunction(String name, String namespaceUri, IList`1[] args)
at (XmlQueryRuntime {urn:schemas-microsoft-com:xslt-debug}runtime, XPathNavigator {urn:schemas-microsoft-com:xslt-debug}current)
at (XmlQueryRuntime {urn:schemas-microsoft-com:xslt-debug}runtime, IList`1 navConfig)
at (XmlQueryRuntime {urn:schemas-microsoft-com:xslt-debug}runtime, XPathNavigator {urn:schemas-microsoft-com:xslt-debug}current)
at (XmlQueryRuntime {urn:schemas-microsoft-com:xslt-debug}runtime, XPathNavigator {urn:schemas-microsoft-com:xslt-debug}current)
at (XmlQueryRuntime {urn:schemas-microsoft-com:xslt-debug}runtime, XPathNavigator )
at Root(XmlQueryRuntime {urn:schemas-microsoft-com:xslt-debug}runtime)
at Execute(XmlQueryRuntime {urn:schemas-microsoft-com:xslt-debug}runtime)
at System.Xml.Xsl.XmlILCommand.Execute(Object defaultDocument, XmlResolver dataSources, XsltArgumentList argumentList, XmlSequenceWriter results)
at System.Xml.Xsl.XmlILCommand.Execute(Object defaultDocument, XmlResolver dataSources, XsltArgumentList argumentList, XmlWriter writer, Boolean closeWriter)
at System.Xml.Xsl.XmlILCommand.Execute(XmlReader contextDocument, XmlResolver dataSources, XsltArgumentList argumentList, XmlWriter results)
at System.Xml.Xsl.XslCompiledTransform.Transform(XmlReader input, XsltArgumentList arguments, XmlWriter results, XmlResolver documentResolver)
at Tridion.Extensions.Mediators.XsltMediator.Transform(Engine engine, Template template, Package package)
at Tridion.ContentManager.Templating.Engine.ExecuteTemplate(Template template, Package package)
at Tridion.ContentManager.Templating.Engine.InvokeTemplate(Package package, TemplateInvocation templateInvocation, Template template)
at Tridion.ContentManager.Templating.Compound.CompoundTemplateMediator.Transform(Engine engine, Template templateToTransform, Package package)
at Tridion.ContentManager.Templating.Engine.ExecuteTemplate(Template template, Package package)
at Tridion.ContentManager.Templating.Engine.InvokeTemplate(Package package, TemplateInvocation templateInvocation, Template template)
at Tridion.ContentManager.Templating.Engine.TransformPackage(Template template, Package package)
at Tridion.ContentManager.Templating.Engine.TransformItem(Template template, IdentifiableObject itemToRender)
at Tridion.ContentManager.Templating.Debugging.DebuggingEngine.Run()
at Tridion.ContentManager.Templating.Debugging.DebugSession.Run()
Hi Manoj,
Looks like you copied that XSLT from a old-school xslt TBB and it still has a reference to the script assistant which the XSLT Mediator has no notion of. You should remove that namespace: ‘http://www.tridion.com/ContentManager/5.1/TcmScriptAssistant’. and try again.
Yoav.