1

I am converting two XSLT files to freemarker. One is HTML and the other is FO. I need to be able to generate list item labels based on a variable typeordered which can be one of the values 1, a, A, i, I (as used in html ordered list type).

Original html.xsl

<ol type="{typeordered}">
    <li>...</li>
</ol>

Original fo.xsl

<fo:list-item>
    <fo:list-item-label end-indent="label-end()">
        <fo:block><xsl:number format="{typeordered}" /></fo:block>
    </fo:list-item-label>
    ...
</fo:list-item>

FO freemarker version. can do lower / upper case alphabet but how to do roman numerals? seems overly complicated?

<#macro listItemM listItem listElement n>
<fo:list-item>
    <fo:list-item-label end-indent="label-end()">
        <fo:block>
            <#if listElement.type == "ordered">
                <#if listElement.typeordered??>
                    <#if listElement.typeordered == "a">
                        ${n?lower_abc}
                    <#elseif listElement.typeordered == "A">
                        ${n?upper_abc}
                    <#else>
                        ${n}
                    </#if>
                <#else>
                    ${n}
                </#if>.
            <#else>
                &#x2022;
            </#if>
        </fo:block>
    </fo:list-item-label>
    ...
</fo:list-item>
MitchBroadhead
  • 811
  • 14
  • 23

2 Answers2

2

There's no roman number formatting built into FreeMarker as of 2.3.28 (nor into Java, last time I have checked). Probably there should be... but for now, you have to roll your own (write a TemplateMethodModelEx for it).

ddekany
  • 29,656
  • 4
  • 57
  • 64
1

Like ddekany mentioned you can create your own method. Here is an example of how you can do it:

Java Code

import freemarker.template.*;

import java.util.List;
import java.util.TreeMap;

public class RomanNumerals implements TemplateMethodModelEx {

    private final static TreeMap<Integer, String> map = new TreeMap<>();

    static {
        map.put(1000, "M");
        map.put(900, "CM");
        map.put(500, "D");
        map.put(400, "CD");
        map.put(100, "C");
        map.put(90, "XC");
        map.put(50, "L");
        map.put(40, "XL");
        map.put(10, "X");
        map.put(9, "IX");
        map.put(5, "V");
        map.put(4, "IV");
        map.put(1, "I");
    }

    // Copied from Stackoverflow https://stackoverflow.com/a/19759564/2735286
    private static String toRoman(int number) {
        int l = map.floorKey(number);
        if (number == l) {
            return map.get(number);
        }
        return map.get(l) + toRoman(number - l);
    }

    @Override
    public Object exec(List arguments) throws TemplateModelException {
        final boolean upperCase = ((TemplateBooleanModel) arguments.get(0)).getAsBoolean();
        final Integer number = ((SimpleNumber) arguments.get(1)).getAsNumber().intValue();
        String roman = toRoman(number);
        return new SimpleScalar(upperCase ? roman : roman.toLowerCase());
    }
}

You will have to insert it into your data model map, before processing the template:

HashMap<String, Object> dataModel = new HashMap<>();
dataModel.put("date", new Date());
dataModel.put("roman", new RomanNumerals());
testTemplate.process(dataModel, new PrintWriter(System.out));

This is how you use it in Freemarker:

${roman(true, 1234)}
gil.fernandes
  • 12,978
  • 5
  • 63
  • 76
  • marking as answer because it included full code sample – MitchBroadhead Aug 07 '18 at 10:15
  • @gil.fernandes Consider contributing this to Apache FreeMarker. (Of course then it needs some more work, like handling some edge cases, tests, ICLA at Apache, etc.) – ddekany Aug 07 '18 at 14:44
  • @ddekany I feel honoured that you are asking for that. Ok, I will dedicate some time to this in the coming days. Please note: I have not yet submitted the ICLA at Apache, but will do that. – gil.fernandes Aug 07 '18 at 15:08
  • @gil.fernandes Great, and see https://freemarker.apache.org/contribute.html for guidance. (It's more like we are honored when someone takes the time and contributes.) – ddekany Aug 07 '18 at 16:36
  • @ddekany this is the class i finished with https://gist.github.com/chongma/dbd781f0a38c517b5c289353e0b387e1 called by `${listItemLabel(listElement.typeordered, n)}` – MitchBroadhead Aug 07 '18 at 17:45
  • @MitchBroadhead Note that what `?upper_abc`/`?lower_abc` does is exposed as `freemarker.template.utility.StringUtil.toUpperABC`/`toLowerABC`. – ddekany Aug 07 '18 at 21:30