Tuesday, December 30, 2014

Adding razor dotnet style support to the TCDL Transformer

There are plenty of scripting options as well as coding styles involved in a Web Application development. SDL Tridion supports several scripting options that can be configured at 3 different levels, Component Template, Publication Target and Deployer. At the moment this article is being writing SDL Tridion current version is 2013 SP1. This version supports the following script options.

JSP
ASP .NET
VBScript
JScript
REL

This Article is not intended to describe each of the above scripting options but to show how to extend the existing ones and add new coding styles. In order to show it I will describe how ASP .NET scripts are configured and how it can be extended. However the same procedure can be used for other script options. Please notice that this procedure has been tested in Tridion 2013 SP1 and it may stop working on future versions of the product since it is not a documented or supported extension

Configuring ASP .NET


The first Step is to select ASP .NET as the script option in the Publication Target. Consider this configuration as a very high level one. ASP .NET holds different coding styles like “inline coding”, and “controls” that are configured in the cd_deployer_conf.xml

<TCDLEngine>
      <Properties>
            <Property Name="tcdl.dotnet.style" Value="controls"/>
      </Properties>
</TCDLEngine>

The above configuration is instructing to the TCDL Transformer to use the DotNet Language with Controls Coding Style. This will generate an output suitable for ASP .NET Web Forms Web Applications as shown in the following code snippet.

<html>
      <body>
            <nav>
                  <ul>
                        <li><tridion:ComponentLink PageURI="[PageURI]" ComponentURI="[ComponentURI]" TemplateURI="[TemplateURI]" LinkAttributes="[LinkAttributes]" AddAnchor="[AddAnchor]" /></li>
                        <li><tridion:ComponentLink PageURI="[PageURI]" ComponentURI="[ComponentURI]" TemplateURI="[TemplateURI]" LinkAttributes="[LinkAttributes]" AddAnchor="[AddAnchor]" /></li>
                        <li><tridion:ComponentLink PageURI="[PageURI]" ComponentURI="[ComponentURI]" TemplateURI="[TemplateURI]" LinkAttributes="[LinkAttributes]" AddAnchor="[AddAnchor]" /></li>
                        <li><tridion:ComponentLink PageURI="[PageURI]" ComponentURI="[ComponentURI]" TemplateURI="[TemplateURI]" LinkAttributes="[LinkAttributes]" AddAnchor="[AddAnchor]" /></li>
                  </ul>
            </nav>
            <div class="main">
                        <article>
                              <tridion:ComponentPresentation PageURI="[PageURI]" ComponentURI="[ComponentURI]" TemplateURI="[TemplateURI]" />
                        </article>
                        <article>
                              <tridion:ComponentPresentation PageURI="[PageURI]" ComponentURI="[ComponentURI]" TemplateURI="[TemplateURI]" />
                        </article>
                        <article>
                              <tridion:ComponentPresentation PageURI="[PageURI]" ComponentURI="[ComponentURI]" TemplateURI="[TemplateURI]" />
                        </article>
            </div>
      </body>
</html>

All the above transformation are very well known and there is no reason for further explanation. However the intention of this post is to show how to add another coding style so that we can write the same code but  using Razor coding style or any other coding style that may be available.

Create a Razor Code Generator class


SDL Tridion comes with a set of Code Generator classes that will generate code from TCDL to an specific script language like DotNet, JSP, REL, VBScript and JSScript. In this Article I will show how to write a Code Generator class that will transform TCDL into Razor HTML Helpers. This class is implementing the transformation of <tcdl:ComponentPresentation> and <tcdl:Link> tags

/*
* Created by Eric Huiza on 12/28/2014.
*/

package com.tridion.tcdl.codegen;

import com.tridion.tcdl.OutputDocument;
import com.tridion.tcdl.Tag;
import com.tridion.tcdl.TransformContext;
import com.tridion.tcdl.tags.TargetGroupTagHandler;

import java.io.IOException;
import java.io.Writer;
import java.util.List;

public class RazorCodeGenerator implements CodeGenerator {
    protected TransformContext context;
    protected OutputDocument target;

    public RazorCodeGenerator(TransformContext context, OutputDocument output) {
        this.context = context;
        target = output;
    }

    public void generateDynamicComponentPresentation(StringBuffer tagBody, String pageURI, String componentURI, String templateURI) {
        tagBody.append(String.format("@Html.ComponentPresentation(\"%s\", \"%s\", \"%s\")", pageURI, componentURI, templateURI));
    }

    public String generateBinaryLink(String binaryURI, String variantId, String addAnchor, String linkText, String linkAttributes, boolean textOnFail) {
        return String.format("@Html.BinaryLink(\"%s\", \"%s\", \"%s\", \"%s\", \"%s\", %s)", binaryURI, variantId, addAnchor, linkText, linkAttributes, Boolean.toString(textOnFail));
    }

    public String generateComponentLink(String pageURI, String componentURI, String templateURI, boolean addAnchor, String linkText, String linkAttributes, boolean textOnFail) {
        return String.format("@Html.ComponentLink(\"%s\", \"%s\", \"%s\", %s, \"%s\", \"%s\", %s)", pageURI, componentURI, templateURI, Boolean.toString(addAnchor), linkText, linkAttributes, Boolean.toString(textOnFail));
    }

    public String generatePageLink(String pageURI, String addAnchor, String linkText, String linkAttributes, boolean textOnFail, String parameters) {
        return String.format("@Html.PageLink(\"%s\", \"%s\", \"%s\", \"%s\", %s, \"%s\")", pageURI, addAnchor, linkText, linkAttributes, Boolean.toString(textOnFail), parameters);
    }

    public void generatePageHeader() {
    }

    public void generateWAIPageHeader(StringBuffer stringBuffer, String s) {
    }

    public void generateTargetGroupCondition(StringBuffer stringBuffer, String s, String s2, boolean b, String s3, String s4) {
    }

    public String convertTargetGroupOperator(String s) {
        return null;
    }

    public String generateTargetGroupIf(TargetGroupTagHandler.Conditions conditions, String s) {
        return null;
    }

    public String generateIncrementTrackingKey(String s, String s2) {
        return null;
    }

    public void generateTaxonomy(StringBuffer stringBuffer, String s, String s2, boolean b, boolean b2, boolean b3, String s3, String s4) {

    }

    public String getNegationSymbol() {
        return null;
    }

    public String getAlwaysFalseExpression() {
        return null;
    }

    public void generateImport(Writer writer, String s) throws IOException {

    }

    public void generatorPageDirectives(Writer writer, List list) throws IOException {

    }

    public void generateCodeStatements(Writer writer, String s, List list, boolean b) throws IOException {

    }

    public boolean requiresCodeBlocks(Tag tag) {
        return false;
    }
}

Create a New Code Generator Factory


A new Code Generator Factory class needs to be developed in order to recognize the Razor coding style and instantiate the Razor Code Generator we already defined above.

/**
* Created by Eric Huiza on 12/28/2014.
*/

package com.tridion.tcdl.codegen;

import com.tridion.tcdl.OutputDocument;
import com.tridion.tcdl.TransformContext;

public class CodeGeneratorFactory {
    private static final String CODE_GENERATOR_CONTEXT_NAME = "tcdl.internal.codegenerator";
    private static final String TARGET_LANGUAGE_NAME = "tcdl.target.language";
    private static final String JSP_STYLE_NAME = "tcdl.jsp.style";
    private static final String DONET_STYLE_NAME = "tcdl.dotnet.style";

    public CodeGeneratorFactory() {
    }

    public static CodeGenerator getGenerator(TransformContext context, OutputDocument target) {
        CodeGenerator result = (CodeGenerator)context.getProperty(CODE_GENERATOR_CONTEXT_NAME, null);
        if(result == null) {
            String language = (String)context.getProperty(TARGET_LANGUAGE_NAME, "");
            if(language.equalsIgnoreCase("jscript")) {
                result = new JScriptCodeGenerator(context, target);
            }
            else if(language.equalsIgnoreCase("vbscript")) {
                result = new VBSCodeGenerator(context, target);
            }
            else if(language.equalsIgnoreCase("jsp"))
            {
                if(((String)context.getProperty(JSP_STYLE_NAME, "")).equalsIgnoreCase("tags")) {
                    result = new JSPTagCodeGenerator(context, target);
                }
                else {
                    result = new JSPCodeGenerator(context, target);
                }
            }
            else if(language.equalsIgnoreCase("none")) {
                result = new NullCodeGenerator();
            }
            else if(language.equalsIgnoreCase("dotnet")) {
                if(((String)context.getProperty(DONET_STYLE_NAME, "")).equalsIgnoreCase("controls")) {
                    result = new ASPNETCodeGenerator(context, target);
                }
                else if(((String)context.getProperty(DONET_STYLE_NAME, "")).equalsIgnoreCase("native_interop")) {
                    result = new JuggerNETCodeGenerator(context, target);
                }
                else if (((String)context.getProperty(DONET_STYLE_NAME, "")).equalsIgnoreCase("razor")) {
                    result = new RazorCodeGenerator(context, target);
                }
                else {
                    result = new DotNETCodeGenerator(context, target);
                }
            }
            else {
                result = new DotNETCodeGenerator(context, target);
            }
            context.setGlobalProperty(CODE_GENERATOR_CONTEXT_NAME, result);
        }
        return result;
    }
}

Configure Razor Coding Style


Set Razor to the tcdl.donet.style property

<TCDLEngine>
      <Properties>
            <Property Name="tcdl.dotnet.style" Value="razor"/>
      </Properties>
</TCDLEngine>


Override the JVM Class Loading process


Since I created a new CodeGeneratorFactory class, it is necessary to instruct the JVM to load the overridden CodeGeneratorFactory instead of the out of the box one. In order to do so, there are plenty of options like using the Boot Class Path option to force a class to be loaded first. In this scenario I opted for an easier one which is to copy the class files in the “config” folder. Classes living inside the “config” folder will be loaded before the ones in the “lib” folder. It needs to be done the Deployer Application.

C:\INETPUB\WWWROOT\DEPLOYERS\REFERENCE_STAGING\BIN\CONFIG
|   cd_deployer_conf.xml
|   cd_storage_conf.xml
|   logback.xml
|  
\---com
    \---tridion
        \---tcdl
            \---codegen
                    CodeGeneratorFactory.class
                    RazorCodeGenerator.class


Publishing using Razor coding style


The result of this operation is shown as following

<html>
      <body>
            <nav>
                  <ul>
                        <li>
                              @Html.ComponentLink("[PageURI]", "[ComponentURI]", "[TemplateURI]", "[AddAnchor]", "[LinkText]", "LinkAttributes", "TextOnFail")
                        </li>
                        <li>
                              @Html.ComponentLink("[PageURI]", "[ComponentURI]", "[TemplateURI]", "[AddAnchor]", "[LinkText]", "LinkAttributes", "TextOnFail")
                        </li>
                        <li>
                              @Html.ComponentLink("[PageURI]", "[ComponentURI]", "[TemplateURI]", "[AddAnchor]", "[LinkText]", "LinkAttributes", "TextOnFail")
                        </li>
                  </ul>
            </nav>
            <div class="main">
                        <article>
                              @Html.ComponentPresentation("[PageURI]", "[ComponentURI]", "[TemplateURI]")
                        </article>
                        <article>
                              @Html.ComponentPresentation("[PageURI]", "[ComponentURI]", "[TemplateURI]")
                        </article>
                        <article>
                              @Html.ComponentPresentation("[PageURI]", "[ComponentURI]", "[TemplateURI]")
                        </article>
            </div>
      </body>
</html>

1 comment:

  1. Bluehost is definitely one of the best hosting provider for any hosting services you require.

    ReplyDelete