How do I use templates inside a T4 ClassBlock method? - t4

I'm starting to investigate T4 for Code Generation.
I get that you have a basic template in which you can embed little chunks of c#/vb which can perform clever stuff...
<## template language="VB" debug="True" hostspecific="True" #>
<## output extension=".vb" debug="True" hostspecific="True" #>
Imports System
<#For Each Table as String in New String(0 {"Table1","Table2"}#>
Public Class <#=Table#>DA
Public Sub New
<#= WriteConstructorBody() #>
End Sub
End Class
<#Next#>
<#+
Public Function WriteConstructorBody() as String
return "' Some comment"
End function
#>
This is great.. However I would like to be able to write my main block thus...
<## template language="VB" debug="True" hostspecific="True" #>
<## output extension=".vb" debug="True" hostspecific="True" #>
Imports System
<#
For Each BaseTableName as String in New String(){"Table1","Table2"}
WriteRecDataInterface(BaseTableName)
WriteRecDataClass(BaseTableName)
WriteDAInterface(BaseTableName)
WriteDAClass(BaseTableName)
Next
#>
Then I would like to be able to have the WriteX methods exist in a Class Block but themselves be writable using code by example ie escaped Code blocks.
How can I achieve this?

You can write.....
<## template language="VB" debug="True" hostspecific="True" #>
<## output extension=".vb" debug="True" hostspecific="True" #>
Imports System
<#
For Each BaseTableName as String in New String(){"Table1","Table2"}
WriteRecDataInterface(BaseTableName)
' WriteRecDataClass(BaseTableName)
' WriteDAInterface(BaseTableName)
' WriteDAClass(BaseTableName)
Next
#>
<#+ Public Sub WriteRecDataInterface(BaseTableName as String)#>
Some Templated unescaped code might go here
<#+ For SomeLoopVar as Integer = 1 to 10 #>
Some Templated unescaped code might go here
<#+ Next #>
Some Templated unescaped code might go here
<#+ End Sub #>
'...
'...
' Other Subs left out for brevity
'...

It seems that you can mix static output with template code in Class Blocks. Here is an example with C#:
<## template language="C#" #>
<# HelloWorld(); #>
<#+
private string _field = "classy";
private void HelloWorld()
{
for(int i = 1; i <= 3; i++)
{
#>
Hello <#=_field#> World <#= i #>!
<#+
}
}
#>

Related

T4, how to use EnvDTE get static class

how to use EnvDTE get static class
<## template debug="false" hostspecific="true" language="C#" #>
<## assembly name="System.Core" #>
<## assembly name="EnvDTE" #>
<## import namespace="System.Linq" #>
<## import namespace="System.Text" #>
<## import namespace="System.Collections.Generic" #>
<## import namespace="EnvDTE" #>
<## output extension=".txt" #>
<#
IServiceProvider hostServiceProvider = Host as IServiceProvider;
EnvDTE.DTE dte = hostServiceProvider.GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
EnvDTE.ProjectItem containingProjectItem = dte.Solution.FindProjectItem(Host.TemplateFile);
Project project = containingProjectItem.ContainingProject;
var codes= project.CodeModel.CodeElements;
foreach (CodeElement code in codes) {
if (code.Name=="MS") continue;
if (code.Name=="System") continue;
if (code.Name=="Microsoft") continue;
if (code.Name.StartsWith("EnvDTE")) continue;
try
{
foreach (CodeElement item in ((CodeNamespace)code).Members) {
if (item.Kind== vsCMElement.vsCMElementClass) {
CodeClass cc = (CodeClass)item;
#>
<#=cc.Name #>
<#
}
}
} catch {}
} #>
this is T4 codes;
It can output the class name,
But it can not determine whether the class is static.
I want to output static class name.
Add references:
<## assembly name="EnvDTE80" #>
<## import namespace="EnvDTE80" #>
Use CodeClass2 interface, that has IsShared property

Avoiding namespace prefixes with Saxon XPath against XHTML

Using Saxon HE 9.6 as a JAXP implementation
Have an HTML document with the XHTML namespace
//*:title returns the expected value, but //title doesn't
I'd really like to just use //title. How can this be done?
Alternatively, can I just remove a namespace from an already constructed Document?
See https://saxonica.plan.io/boards/3/topics/1649, you can cast the JAXP XPath object you have created from a Saxon XPathFactory implementation to a net.sf.saxon.xpath.XPathEvaluator and then set the default XPath namespace for XPath evaluation with e.g.
((XPathEvaluator)xpath).getStaticContext().setDefaultElementNamespace("http://www.w3.org/1999/xhtml");
Then a path //title will select title elements in the XHTML namespace. I tested that to work in a sample
XPathFactory xpathFactory = new XPathFactoryImpl();
XPath xpath = xpathFactory.newXPath();
((XPathEvaluator)xpath).getStaticContext().setDefaultElementNamespace("http://www.w3.org/1999/xhtml");
String xhtmlSample = "<html xmlns='http://www.w3.org/1999/xhtml'><head><title>This is a test</title></head><body><h1>Test</h1></body></html>";
InputSource source = new InputSource(new StringReader(xhtmlSample));
System.out.println("Found: " + xpath.evaluate("//title", source));

freemarker error: expected hash. evaluated instead to freemarker.template.SimpleScalar

My template looks like this:
<#assign senti = "${scmr.results[model]}">
<#if senti??>
<td>${senti} ---- ${senti.sentimentType}</td>
<td>${senti.score?html}</td>
</#if>
The output looks like this:
POSITIVE(1.0/1) ---- Expected hash. senti evaluated instead to freemarker.template.SimpleScalar on line 5, column 27 in com/addthis/sentiment/sentidemo.ftl.
the output text before "----" indicates that senti is, indeed, a valid java Sentiment object. Methods getSentimentType and getScore are present and working.
So, why am I getting the error?
With <#assign senti = "${scmr.results[model]}"> you have converted scmr.results[model] to a String (a scalar), that's why. Just write <#assign senti = scmr.results[model]>. In FreeMarker expressions you can inject value into a string literal, like "Hello ${name}!" (same as "Hello " + name + "!"), and "${someExpression}" is just a case of that. It's not like in JSP.
had the same error when using swagger generated models with ninjaframework , fixed by adding below class in conf package
package conf;
import com.google.inject.Inject;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.MethodAppearanceFineTuner;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapperBuilder;
import ninja.NinjaDefault;
import ninja.template.TemplateEngineFreemarker;
/**
* Created by varya on 07/12/17.
*/
public class Ninja extends NinjaDefault {
#Inject
protected TemplateEngineFreemarker templateEngineFreemarker;
#Override
public void onFrameworkStart() {
super.onFrameworkStart();
Configuration freemarkerConfiguration = templateEngineFreemarker.getConfiguration();
DefaultObjectWrapperBuilder owb = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_23);
owb.setMethodAppearanceFineTuner(new MethodAppearanceFineTuner() {
#Override
public void process(BeansWrapper.MethodAppearanceDecisionInput in, BeansWrapper.MethodAppearanceDecision out) {
out.setMethodShadowsProperty(false);
}
});
freemarkerConfiguration.setObjectWrapper(owb.build());
}
}

T4 trouble compiling transformation

I can't figure this one out. Why doesn't T4 locate the IEnumerable type? I'm using Visual Studio 2010. And I just hope someone knows why?
<## template debug="true" hostspecific="false" language="C#" #>
<## assembly name="System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" #>
<## import namespace="System" #>
<## import namespace="System.Data" #>
<## import namespace="System.Data.SqlClient" #>
<## output extension=".cs" #>
public static class Tables
{
<#
var q = #"
SELECT
tbl.name 'table',
col.name 'column'
FROM
sys.tables tbl
INNER JOIN
sys.columns col ON col.object_id = tbl.object_id
";
// var source = Execute(q);
#>
}
<#+
static IEnumerable Execute(string cmdText)
{
using (var conn = new SqlConnection(#"Data Source=.\SQLEXPRESS;Initial Catalog=t4build;Integrated Security=True;"))
{
conn.Open();
var cmd = new SqlCommand(cmdText, conn);
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
}
}
}
}
#>
Error 2 Compiling transformation: The type or namespace name 'IEnumerable' could not be found (are you missing a using directive or an assembly reference?) c:\Projects\T4BuildApp\T4BuildApp\TextTemplate1.tt 26 9
I would also recommend to referece #assembly name="System.Core" and #import "System.Linq" so you get more power when doing something with IEnumerable
Probably because IEnumerable is in System.Collections.

In freemarker is it possible to check to see if a file exists before including it?

We are trying to build a system in freemarker where extension files can be optionally added to replace blocks of the standard template.
We have gotten to this point
<#attempt>
<#include "extension.ftl">
<#recover>
Standard output
</#attempt>
So - if the extension.ftl file exists it will be used otherwise the part inside of the recover block is output.
The problem with this is that freemarker always logs the error that caused the recover block to trigger.
So we need one of two things:
Don't call the include if the file doesn't exist - thus the need to check for file existence.
-OR-
A way to prevent the logging of the error inside the recover block without changing the logging to prevent ALL freemarker errors from showing up.
easier solution would be:
<#attempt>
<#import xyz.ftl>
your_code_here
<#recover>
</#attempt>
We've written a custom macro which solves this for us. In early testing, it works well. To include it, add something like this (where mm is a Spring ModelMap):
mm.addAttribute(IncludeIfExistsMacro.MACRO_NAME, new IncludeIfExistsMacro());
import java.io.IOException;
import java.util.Map;
import org.apache.commons.io.FilenameUtils;
import freemarker.cache.TemplateCache;
import freemarker.cache.TemplateLoader;
import freemarker.core.Environment;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
/**
* This macro optionally includes the template given by path. If the template isn't found, it doesn't
* throw an exception; instead it renders the nested content (if any).
*
* For example:
* <#include_if_exists path="module/${behavior}.ftl">
* <#include "default.ftl">
* </#include_if_exists>
*
* #param path the path of the template to be included if it exists
* #param nested (optional) body could be include directive or any other block of code
*/
public class IncludeIfExistsMacro implements TemplateDirectiveModel {
private static final String PATH_PARAM = "path";
public static final String MACRO_NAME = "include_if_exists";
#Override
public void execute(Environment environment, Map map, TemplateModel[] templateModel,
TemplateDirectiveBody directiveBody) throws TemplateException, IOException {
if (! map.containsKey(PATH_PARAM)) {
throw new RuntimeException("missing required parameter '" + PATH_PARAM + "' for macro " + MACRO_NAME);
}
// get the current template's parent directory to use when searching for relative paths
final String currentTemplateName = environment.getTemplate().getName();
final String currentTemplateDir = FilenameUtils.getPath(currentTemplateName);
// look up the path relative to the current working directory (this also works for absolute paths)
final String path = map.get(PATH_PARAM).toString();
final String fullTemplatePath = TemplateCache.getFullTemplatePath(environment, currentTemplateDir, path);
TemplateLoader templateLoader = environment.getConfiguration().getTemplateLoader();
if (templateLoader.findTemplateSource(fullTemplatePath) != null) {
// include the template for the path, if it's found
environment.include(environment.getTemplateForInclusion(fullTemplatePath, null, true));
} else {
// otherwise render the nested content, if there is any
if (directiveBody != null) {
directiveBody.render(environment.getOut());
}
}
}
}
I had this exact need as well but I didn't want to use FreeMarker's ObjectConstructor (it felt too much like a scriptlet for my taste).
I wrote a custom FileTemplateLoader:
public class CustomFileTemplateLoader
extends FileTemplateLoader {
private static final String STUB_FTL = "/tools/empty.ftl";
public CustomFileTemplateLoader(File baseDir) throws IOException {
super(baseDir);
}
#Override
public Object findTemplateSource(String name) throws IOException {
Object result = null;
if (name.startsWith("optional:")) {
result = super.findTemplateSource(name.replace("optional:", ""));
if (result == null) {
result = super.findTemplateSource(STUB_FTL);
}
}
if (result == null) {
result = super.findTemplateSource(name);
}
return result;
}
}
And my corresponding FreeMarker macro:
<#macro optional_include name>
<#include "/optional:" + name>
</#macro>
An empty FTL file was required (/tools/empty.ftl) which just contains a comment explaining its existence.
The result is that an "optional" include will just include this empty FTL if the requested FTL cannot be found.
You can use also use Java method to check file exist or not.
Java Method-
public static boolean checkFileExistance(String filePath){
File tmpDir = new File(filePath);
boolean exists = tmpDir.exists();
return exists;
}
Freemarker Code-
<#assign fileExists = (Static["ClassName"].checkFileExistance("Filename"))?c/>
<#if fileExists = "true">
<#include "/home/demo.ftl"/>
<#else>
<#include "/home/index.ftl">
</#if>
Try this to get the base path:
<#assign objectConstructor = "freemarker.template.utility.ObjectConstructor"?new()>
<#assign file = objectConstructor("java.io.File","")>
<#assign path = file.getAbsolutePath()>
<script type="text/javascript">
alert("${path?string}");
</script>
Then this to walk the directory structure:
<#assign objectConstructor = "freemarker.template.utility.ObjectConstructor"?new()>
<#assign file = objectConstructor("java.io.File","target/test.ftl")>
<#assign exist = file.exists()>
<script type="text/javascript">
alert("${exist?string}");
</script>

Resources