Search code examples
freemarker

FO ordered list item labels roman numerals


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>

Solution

  • 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)}