Generate HTML documentation for a FreeMarker FTL library - documentation-generation

I've a FreeMarker library that I want to ship with my product, and I'm looking for a way to generate a HTML documentation for it based on the comments in the FTL file (in a Javadoc fashion).
For example, a typical function in my library is written like:
<#--
MyMacro: Does stuff with param1 and param2.
- param1: The first param, mandatory.
- param2: The second param, 42 if not specified.
-->
<#macro MyMacro param1 param2=42>
...
</#macro>
I didn't find anything on that subject, probably because there is no standard way of writing comments in FreeMarker (Such as #param or #returns in Javadoc).
I don't mind rolling my own solution for that, but I'm keen on using an existing system like Doxia (since I'm using Maven to build the project) or Doxygen maybe, instead of writing something from scratch.
Ideally I'd like to write the comment parsing code only, and rely on something else to detect the macros and generate the doc structure.
I'm open to changing the format of my comments if that helps.

In case you decide to write your own doc generator or to write a FTL-specific front-end for an existing document generator, you can reuse some of FreeMarker's parsing infrastructure:
You can use Template.getRootTreeNode() in order to retrieve the template's top level AST node. Because macros and the responding comments should be direct children of the this top level node (IIRC), iterating over its children and casting them to the right AST node subclass should give you almost everything you need with respect to FTL syntax. To illustrate the approach I hacked together a little "demo" (cfg is a normal FreeMarker Configuration object):
Template t = cfg.getTemplate("foo.ftl");
TemplateElement te = t.getRootTreeNode();
Enumeration e = te.children();
while(e.hasMoreElements()) {
Object child = e.nextElement();
if(child instanceof Comment) {
Comment comment = (Comment)child;
System.out.println("COMMENT: " + comment.getText());
} else if(child instanceof Macro) {
Macro macro = (Macro)child;
System.out.println("MACRO: " + macro.getName());
for(String argumentName : macro.getArgumentNames()) {
System.out.println("- PARAM: " + argumentName);
}
}
}
produces for your given example macro:
COMMENT:
MyMacro: Does stuff with param1 and param2.
- param1: The first param, mandatory.
- param2: The second param, 42 if not specified.
MACRO: MyMacro
- PARAM: param1
- PARAM: param2
How you parse the comment is then up to you ;-)
Update: Found something called ftldoc in my backups and uploaded it to GitHub. Maybe this is what you are looking for...

Related

Using one variable for multiple items data in descriptive programming

I know that with Descriptive programming you can do something like this:
Browser("StackOverflow").Page("StackOverflow").Link("text:=Go To Next Page ", "html tag:=A").Click
But is it possible to create some kind of string so I can assign more than one data value and pass it as single variable? I've tried many combinations using escape characters and I always get error.
For example in the case above, let's say I have more properties in the Page object, so I'd normally have to do something like this:
Browser("StackOverflow").Page("name:=StackOverflow", "html id:=PageID")...etc...
But I'd like to pass "name:=StackOverflow", "html id:=PageID" as a single variable, so when writing many objects I'd only have to write:
Browser(BrowserString).Page(PageString).WebEdit("name:=asdfgh")
And the first part would remain static, so if the parents' data needs to be modified I'd only have to modify two variables and not all the objects created in all libraries.
Is it possible?
If I was not clear enough please let me know.
Thank you in advance!
I think what you're looking for is UFT's Description object
This allows you finer grained control on the description since in descriptive programming all values are regular expressions but with Description you can turn the regular expression functionality off for a specific property.
Set desc = Description.Create()
desc("html tag").Value = "A"
desc("innertext").Value = "More information..."
desc("innertext").RegularExpression = False
Browser("Example Domain").Navigate "www.example.com"
Browser("Example Domain").Page("Example Domain").WebElement(desc).Click
If you want to represent this with plain string then it's a bit more of a problem, you can write a helper function but I'm not sure I would recommend it.
Function Desc(descString)
Set ret = Description.Create()
values = Split(descString, "::")
For Each value In values
keyVal = Split(value, ":=")
ret(keyVal(0)).Value = keyVal(1)
Next
Set Desc = ret
End Function
' Usage
Browser("StackOverflow").Page("StackOverflow").WebElement(Desc("html tag:=H2::innertext:=some text")).Click
Further reading about descriptive programming.
As an alternative to Motti's excellent answer, you could also Set a variable to match your initial descriptive object and then extend it as required:
Set myPage = Browser("StackOverflow").Page("name:=StackOverflow", "html id:=PageID")
after which you can then use
myPage.WebEdit("name:=asdfgh")
throughout the rest of the code, so long as the myPage object stays in scope...

Removing Class Name of an element using element.className.replace method

var divfoo=document.getElementById("foo");
divfoo.className=" css-class css-class2 ";
divfoo.className=divfoo.className.replace(" css-class2 ", "");
The above code works. but I would like to make changes to last line of above code which is using replace method. Instead of writing the code like above, I would like to know why doesn't it work when written like below.
var divfoo=document.getElementById("foo");
divfoo.className=" css-class css-class2 ";
divfoo.className.replace(" css-class2 ", "");
Why should one assign to "divfoo.className" when applying replace method to the same "divfoo.className", why can't we just apply method directly like above code did?
Because of this should I hate javascript for not being logical?
enter code here
Element.className is a plain string representation of class HTML attribute.
String.replace method does not change the source string that is called on, just returns the result of replacement procedure.
If you want more "logical / functional" approach, look at Element.classList interface, namely the remove method.

Freemarker - replace & with &

If i have the & symbol in some field (from a db, cannot be changed), and i want to display this via freemarker... but have the display (from freemarker) read &, what is the way to do so?
To reiterate, I cannot change the value before hand (or at least, I don't want to), i'd like freemarker to "unmark" &.
To double re-iterate, this is a value that is being placed with a lot of other xml. The value itself is displayed on its own, surrouded by tags... so something like
<someTag>${wheeeMyValueWithAnAmpersand}<someTag>
As a result, i don't want all ampersands escaped, or the xml will look funny... just that one in the interpolation.
Oh goodness.
I see the problem: the code was written like this:
<#escape x as x?xml>
<#import "small.ftl" as my>
<#my.macro1/>
</#escape>
and at which i'd assumed that the excape would excape all the calls within it - it is certainly what the documentation sort of implies
http://freemarker.org/docs/ref_directive_escape.html
<#assign x = "<test>"> m1>
m1: ${x}
</#macro>
<#escape x as x?html>
<#macro m2>m2: ${x}</#macro>
${x}
<#m1/>
</#escape>
${x}
<#m2/>
the output will be:
<test>
m1: <test>
<test>
m2: <test>
However it appears that when you import the file, then this isn't the case, and the escape... escapes!
SOLUTION:
http://watchitlater.com/blog/2011/10/default-html-escape-using-freemarker/
the above link details how to solve the problem. In effect, it comes down to loading a different FreemakerLoader, one that wraps all templates with an escape tag.
class SomeCoolClass implements TemplateLoader {
//other functions here
#Override
public Reader getReader(Object templateSource, String encoding) throws IOException {
Reader reader = delegate.getReader(templateSource, encoding);
try {
String templateText = IOUtils.toString(reader);
return new StringReader(ESCAPE_PREFIX + templateText + ESCAPE_SUFFIX);
} finally {
IOUtils.closeQuietly(reader);
}
}
which is a snippet from the link above. You create the class with the existing templateLoader, and just defer all the required methods to that.
Starting from FreeMarker 2.3.24 no TemplateLoader "hack" is needed anymore. There's a setting called output_format, which specifies if and what escaping is needed. This can be configured both globally, and/or per-template-name-pattern utilizing the template_configurations setting. The recommend way of doing this is even simpler (from the manual):
[...] if the
recognize_standard_file_extensions setting is true (which is the
default with the incompatible_improvements setting set to 2.3.24 or
higher), templates whose source name ends with ".ftlh" gets "HTML"
output format, and those with ".ftlx" get "XML" output format

mustache/hogan i18n and taking word-order into account

Found a couple of questions (and answers) on this: How is internationalization configured for Hogan.js?
,etc.
but non in particular that take word order into account. I need the ability to:
step 1. given a key -> lookup a sentence in a particular language.
step 2. this sentence may contain {{var}} , which need to be
substituted by json-values.
step 2. alone is general mustache-templating.
step 1. alone could be done with several techniques, but I prefer techniques that don't involve any specialized code outside of the Mustache/Hogan engine (in combination with a i18n-resource bundle of course) . Hogan seems to support this with something like: (from url above)
var template = "{{#i18n}}Name{{/i18n}}: {{username}}",
context = {
username: "Jean Luc",
i18n: function (i18nKey) {return translatedStrings[i18nKey];}
};
However to combine 1. and 2. in this example I would want translatedStrings[i18nKey] to return a string which potentially contains {{<some expansion>}} as well.
Someone knows of an elegant way to do this?
Rationale:
Often languages differ a lot in word order, etc. which makes for complex templates without this ability.
The latest version of Hogan.js will handle Mustache tags inside the result returned from a lambda. One minor change to the code in your question however, is that the result of the lambda should be a function in order to modify the string:
var translatedStrings = { name: "Nom {{rank}}" };
var template = "{{#i18n}}name{{/i18n}}: {{username}}",
context = {
username: "Jean Luc",
rank: 'Captain',
i18n: function() {
return function (i18nKey) {return translatedStrings[i18nKey];};
}
};
document.write(Hogan.compile(template).render(context));​ // Nom Captain: Jean Luc
I created a jsfiddle that demonstrates this with the latest version.

How to make client side I18n with mustache.js

i have some static html files and want to change the static text inside with client side modification through mustache.js.
it seems that this was possible Twitter's mustache extension on github: https://github.com/bcherry/mustache.js
But lately the specific I18n extension has been removed or changed.
I imagine a solution where http:/server/static.html?lang=en loads mustache.js and a language JSON file based on the lang param data_en.json.
Then mustache replaces the {{tags}} with the data sent.
Can someone give me an example how to do this?
You can use lambdas along with some library like i18next or something else.
{{#i18n}}greeting{{/i18n}} {{name}}
And the data passed:
{
name: 'Mike',
i18n: function() {
return function(text, render) {
return render(i18n.t(text));
};
}
}
This solved the problem for me
I don't think Silent's answer really solves/explains the problem.
The real issue is you need to run Mustache twice (or use something else and then Mustache).
That is most i18n works as two step process like the following:
Render the i18n text with the given variables.
Render the HTML with the post rendered i18n text.
Option 1: Use Mustache partials
<p>{{> i18n.title}}</p>
{{#somelist}}{{> i18n.item}}{{/somelist}}
The data given to this mustache template might be:
{
"amount" : 10,
"somelist" : [ "description" : "poop" ]
}
Then you would store all your i18n templates/messages as a massive JSON object of mustache templates on the server:
Below is the "en" translations:
{
"title" : "You have {{amount}} fart(s) left",
"item" : "Smells like {{description}}"
}
Now there is a rather big problem with this approach in that Mustache has no logic so handling things like pluralization gets messy.
The other issue is that performance might be bad doing so many partial loads (maybe not).
Option 2: Let the Server's i18n do the work.
Another option is to let the server do the first pass of expansion (step 1).
Java does have lots of options for i18n expansion I assume other languages do as well.
Whats rather annoying about this solution is that you will have to load your model twice. Once with the regular model and second time with the expanded i18n templates. This is rather annoying as you will have to know exactly which i18n expansions/templates to expand and put in the model (otherwise you would have to expand all the i18n templates). In other words your going to get some nice violations of DRY.
One way around the previous problem is pre-processing the mustache templates.
My answer is based on developingo's. He's answer is very great I'll just add the possibility to use mustache tags in the message keycode. It is really needed if you want to be able the get messages according to the current mustache state or in loops
It's base on a simple double rendering
info.i18n = function(){
return function(text, render){
var code = render(text); //Render first to get all variable name codes set
var value = i18n.t(code)
return render(value); //then render the messages
}
}
Thus performances aren't hit because of mustache operating on a very small string.
Here a little example :
Json data :
array :
[
{ name : "banana"},
{ name : "cucomber" }
]
Mustache template :
{{#array}}
{{#i18n}}description_{{name}}{{/i18n}}
{{/array}}
Messages
description_banana = "{{name}} is yellow"
description_cucomber = "{{name}} is green"
The result is :
banana is yellow
cucomber is green
Plurals
[Edit] : As asked in the comment follows an example with pseudo-code of plural handling for english and french language. Its a very simple and not tested example but it gives you a hint.
description_banana = "{{#plurable}}a {{name}} is{{/plurable}} green" (Adjectives not getting "s" in plurals)
description_banana = "{{#plurable}}Une {{name}} est verte{{/plurable}}" (Adjectives getting an "s" in plural, so englobing the adjective as well)
info.plurable = function()
{
//Check if needs plural
//Parse each word with a space separation
//Add an s at the end of each word except ones from a map of common exceptions such as "a"=>"/*nothing*/", "is"=>"are" and for french "est"=>"sont", "une" => "des"
//This map/function is specific to each language and should be expanded at need.
}
This is quite simple and pretty straightforward.
First, you will need to add code to determine the Query String lang. For this, I use snippet taken from answer here.
function getParameterByName(name) {
var match = RegExp('[?&]' + name + '=([^&]*)')
.exec(window.location.search);
return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
}
And then, I use jQuery to handle ajax and onReady state processing:
$(document).ready(function(){
var possibleLang = ['en', 'id'];
var currentLang = getParameterByName("lang");
console.log("parameter lang: " + currentLang);
console.log("possible lang: " + (jQuery.inArray(currentLang, possibleLang)));
if(jQuery.inArray(currentLang, possibleLang) > -1){
console.log("fetching AJAX");
var request = jQuery.ajax({
processData: false,
cache: false,
url: "data_" + currentLang + ".json"
});
console.log("done AJAX");
request.done(function(data){
console.log("got data: " + data);
var output = Mustache.render("<h1>{{title}}</h1><div id='content'>{{content}}</div>", data);
console.log("output: " + output);
$("#output").append(output);
});
request.fail(function(xhr, textStatus){
console.log("error: " + textStatus);
});
}
});
For this answer, I try to use simple JSON data:
{"title": "this is title", "content": "this is english content"}
Get this GIST for complete HTML answer.
Make sure to remember that other languages are significantly different from EN.
In FR and ES, adjectives come after the noun. "green beans" becomes "haricots verts" (beans green) in FR, so if you're plugging in variables, your translated templates must have the variables in reverse order. So for instance, printf won't work cuz the arguments can't change order. This is why you use named variables as in Option 1 above, and translated templates in whole sentences and paragraphs, rather than concatenating phrases.
Your data needs to also be translated, so the word 'poop', which came from data - somehow that has to be translated. Different languages do plurals differently, as does english, as in tooth/teeth, foot/feet, etc. EN also has glasses and pants that are always plural. Other languages similarly have exceptions and strange idoms. In the UK, IBM 'are' at the trade show whereas in in the US, IBM 'is' at the trade show. Russian has several different rules for plurals depending on if they are people, animals, long narrow objects, etc. In other countries, thousands separators are spaces, dots, or apostrophes, and in some cases don't work by 3 digits: 4 in Japan, inconsistently in India.
Be content with mediocre language support; it's just too much work.
And don't confuse changing language with changing country - Switzerland, Belgium and Canada also have FR speakers, not to mention Tahiti, Haiti and Chad. Austria speaks DE, Aruba speaks NL, and Macao speaks PT.

Resources