?right_pad applied on captureGroup inside a ?replace won't consider captureGroup length - freemarker

I'm trying to convert a HTML table to plain text. To have the "columns" aligned correctly I'd like to insert as many whitespaces to every cell content to match the max length of all cell contents.
The cell content is extracted from the HTML using a RegEx Replace using a captureGroup. When I'm applying the ?right_pad on the captureGroup the actual length of the captureGroup isn't considered but just 2 characters ($1), thus the columns of the plain text aren't aligned but shifted.
Any other approaches? Or if a Freemarker Contributor/Dev is reading - could you register this as a bug or invite me to the project's Jira so I can register it myself?
Template:
<#-- DETERMINE MAX TABLE CELL CHARACTER LENGTH -->
<#assign tableCells = htmlTable?matches("<td>([\\w\\d\\s]*)</td>") >
<#assign cellSizes = []>
<#list tableCells as t>
<#assign cellSizes += [t?groups[1]?length]>
</#list>
<#assign maxCellSize = cellSizes?max>
Max Cell Character length: ${maxCellSize}
${htmlTable
<#-- REPLACE HTML TABLE WITH PLAINTEXT -->
<#-- REMOVE OUTER TABLE ELEMENTS -->
?replace("<table.*<tbody>(.*)</tbody></table>", "$1", "rgi")
<#-- REPLACE TABLE HEADERS -->
?replace("<th[\\w\\d\\s=\\\"]*>(<p>)*(<strong>)*([\\w\\d\\s=\\\"]*)(</strong>)*(</p>)*", "<b>" + "$3"?right_pad(maxCellSize, "-") + "</b>", "rgi")
<#-- ADD SPACERS BETWEEN TABLE HEADERS -->
?replace("</th>(?!</tr>)", " ", "rgi")
<#-- REPLACE TABLE CELLS-->
?replace("<td[\\w\\d\\s=\\\"]*>(<p>)*(<strong>)*([\\w\\d\\s=\\\"]*)(</strong>)*", "$3"?right_pad(maxCellSize, "-"), "rgi")
<#-- ADD SPACERS BETWEEN TABLE CELLS -->
?replace("</td>(?!</tr>)", " ", "rgi")
<#-- REPLACE "TABLE LINE BREAKS" (END OF ROW) WITH REGULAR LINE BREAKS-->
?replace("</tr>", "<br>")
<#-- REMOVE REMAINING <tr>|</th>|</td> ELEMENTS -->
?replace("<tr>|</th>|</td>", "", "rgi")
}
Data model
htmlTable = "<table><tbody><tr><th>col1</th><th>column 2</th><th>very long col header 3</th></tr><tr><td>text</td><td>some text</td><td>last col text</td></tr><tr><td>longer text</td><td>text</td><td>last col text 2</td></tr><tr><td>even longer text</td><td>yet another fairly long text</td><td>last col text 3</td></tr></tbody></table>"
Result
Max Cell Character length: 28
<b>col1--------------------------</b> <b>column 2--------------------------</b> <b>very long col header 3--------------------------</b><br>text-------------------------- some text-------------------------- last col text--------------------------<br>longer text-------------------------- text-------------------------- last col text 2--------------------------<br>even longer text-------------------------- yet another fairly long text-------------------------- last col text 3--------------------------<br>

So I found a solution to my problem, here it is if someone else can use it:
TL;DR: "flag" the table headers and contents, copy those without the flags and padding into an array and later replace flagged stuff with right padded stuff.
Template:
<#-- DETERMINE MAX TABLE CELL CHARACTER LENGTH -->
<#assign tableHeaders = htmlTable?matches("<th[\\w\\d\\s=\\\"]*>(<p>)*(<strong>)*([\\w\\d\\s=\\\"]*)(</strong>)*(</p>)*")>
<#assign tableCells = htmlTable?matches("<td[\\w\\d\\s=\\\"]*>(<p>)*(<strong>)*([\\w\\d\\s=\\\"]*)(</strong>)*") >
<#assign cellSizes = []>
<#assign cellContents = []>
<#list tableCells as t>
<#assign cellSizes += [t?groups[3]?length]>
<#assign cellContents += [t?groups[3]]>
</#list>
<#assign headerContents = []>
<#list tableHeaders as h>
<#assign cellSizes += [h?groups[3]?length]>
<#assign headerContents += [h?groups[3]]>
</#list>
<#assign maxCellSize = cellSizes?max>
<#assign flaggedCellContents = [] paddedCellContents = []>
<#list cellContents as c>
<#assign flaggedCellContents += ["###"+c+"###"]>
<#assign paddedCellContents += [c?right_pad(maxCellSize+3, "-")]>
</#list>
<#assign flaggedHeaderContents = [] paddedHeaderContents = []>
<#list headerContents as h>
<#assign flaggedHeaderContents += ["§§§"+h+"§§§"]>
<#assign paddedHeaderContents += [h?right_pad(maxCellSize+3, "-")]>
</#list>
Max Cell Character length: ${maxCellSize}
<#assign convertedTable = htmlTable
<#-- REPLACE HTML TABLE WITH PLAINTEXT -->
<#-- REMOVE OUTER TABLE ELEMENTS -->
?replace("<table.*<tbody>(.*)</tbody></table>", "$1", "rgi")
<#-- REPLACE TABLE HEADERS -->
?replace("<th[\\w\\d\\s=\\\"]*>(<p>)*(<strong>)*([\\w\\d\\s=\\\"]*)(</strong>)*(</p>)*", "§§§$3§§§", "rgi")
<#-- REPLACE TABLE CELLS-->
?replace("<td[\\w\\d\\s=\\\"]*>(<p>)*(<strong>)*([\\w\\d\\s=\\\"]*)(</strong>)*", "###$3###", "rgi")
<#-- REPLACE "TABLE LINE BREAKS" (END OF ROW) WITH REGULAR LINE BREAKS-->
?replace("</tr>", "\n")
<#-- REMOVE REMAINING <tr>|</th>|</td> ELEMENTS -->
?replace("<tr>|</th>|</td>", "", "rgi")
>
<#list 1..cellContents?size as i>
<#assign convertedTable = convertedTable?replace(flaggedCellContents[i?index], paddedCellContents[i?index])>
</#list>
<#list 1..headerContents?size as i>
<#assign convertedTable = convertedTable?replace(flaggedHeaderContents[i?index], paddedHeaderContents[i?index])>
</#list>
${convertedTable}
Data Model:
htmlTable = "<table><tbody><tr><th>col1</th><th>column 2</th><th>very long col header 3</th></tr><tr><td>text</td><td>some text</td><td>last col text</td></tr><tr><td>longer text</td><td>text</td><td>last col text 2</td></tr><tr><td>even longer text</td><td>yet another fairly long text</td><td>last col text 3</td></tr></tbody></table>"
Result:
Max Cell Character length: 28
col1---------------------------column 2-----------------------very long col header 3---------
text---------------------------some text----------------------last col text------------------
longer text--------------------text---------------------------last col text 2----------------
even longer text---------------yet another fairly long text---last col text 3----------------

Related

Print only 5 line of each having 32 chars using Freemarker

How to print only 5 line of each having 32 chars using Freemarker. Currently i have the below solution. Is there any better way of doing using split or substring
<#assign msg="Tell FreeMarker to convert string to real date-time value Convert date-time value to date-only value Let FreeMarker format it according the date_format setting">
<#assign len=msg?length>
<#list 1..5 as i>
<#assign start=(i-1)*32>
<#assign end=i*32>
<#if (end <len)>
${msg[start..end]}
<#else>
${msg[start..len-1]}
</#if>
</#list>
result is
Tell FreeMarker to convert string
g to real date-time value Convert
t date-time value to date-only va
alue Let FreeMarker format it acc
cording the date_format setting
Like this:
<#list msg?matches(".{1,32}")[0..*5] as row>
${row}
</#list>
Note that the "length limited range" operator, ..*, doesn't give error if the length is less than what you asked for. So even with your approach, you can remove the end assignment and the #if/#else, and just use ${msg[start..*32]}.

How to Write Series in FreeMarker?

I am trying to write a Numeric and Alphabetic Series in Free-marker. However I am not able to implement it.
I have tried various portal and Freemarker website itself, but was not able to find a proper solution.
<#assign count = 0>
<#assign seq = ['a','b','c','d','e','f',]>
<#list params_list as test_param>
${count} ${seq[count]}
<#assign count = count + 1>
</#list>
It will print data in
1 a
2 b
3 c
You can use ?lower_abc (or ?upper_abc) to convert a number to a letter, where 1 corresponds to letter "a". If this is inside #list, then you can get the 1-based item counter with itemVariable?counter. For example:
<#list items as item>
${item?counter} ${item?counter?lower_abc}
</#list>

freemarker if statement sequencing

I'm trying to write an #if statement with a sequence of numbers. Basically, if a certain field matches any of a subset of numbers (shown below with || or operators) then assign it as "bayarea", elseif a different subset, then a different name, etc. Is this possible without a bunch of nested "or" statements?
I'm getting a syntax error saying that it's expecting a boolean yes/no statement.
<#if TEST_CONTACTS_LIST.PREFERRED_STORE ==
{12||21||22||38||46||67||71||74||76||77||83||86||104||113||119||143>
{bayarea}
<#elseif TEST_CONTACTS_LIST.PREFERRED_STORE ==
{34||62||84||91||137||144||152||169}>
{blueridge}
<#elseif TEST_CONTACTS_LIST.PREFERRED_STORE ==
{18||44||49||50||61||68||121||182}>
{frontrange}
<#else>
</#if>
You don't need nesting:
<#if TEST_CONTACTS_LIST.PREFERRED_STORE == 12
|| TEST_CONTACTS_LIST.PREFERRED_STORE == 21 || ...>
Though that's surely too verbose, but you can do this:
<#assign store = TEST_CONTACTS_LIST.PREFERRED_STORE>
<#if store == 12 || store == 21 || ...>
But I think what you are looking for is this (or this combined with the #assign, if you have several #elseif-s):
<#if [12, 21, ...]?seq_contains(TEST_CONTACTS_LIST.PREFERRED_STORE)>
This is a possibility too (just don't forget the #break-s):
<#switch TEST_CONTACTS_LIST.PREFERRED_STORE>
<#case 12><#case 21>...
{bayarea}
<#break>
<#case 34><#case 62>...
{bluebridge}
<#break>
...
</#switch>
Those are four good answers above. To expand on it, I'd do something like this:
<#assign pref_store = TEST_CONTACTS_LIST.PREFERRED_STORE>
<#assign area = "">
<#assign group1 = [12,21,22,38,46,67,71,74,76,77,83,86,104,113,119,143]>
<#assign group2 = [34,62,84,91,137,144,152,169]>
<#assign group3 = [18,44,49,50,61,68,121,182]>
<#if group1?seq_contains(pref_store)>
<#assign area = "bayarea">
<#elseif group2?seq_contains(pref_store)>
<#assign area = "blueridge">
<#elseif group3?seq_contains(pref_store)>
<#assign area = "frontrange">
<#else>
<#assign area = "whatever">
</#if>
Your Preferred Store Area is ${area}.
Alternatively, instead of Sequences / Arrays, you could also set these values as a string.
For example:
<#assign pref_store = "${TEST_CONTACTS_LIST.PREFERRED_STORE}">
<#assign group1 = "12,21,22,38,46,67,71,74,76,77,83,86,104,113,119,143">
then the if statement would be the following:
<#if group1?contains(pref_store)>
(Notice the ?contains instead of ?seq_contains)
etc..

FreeMarker error: For "-" left-hand operand: Expected a number, but this has evaluated to a string

I am trying to compute sum of values in map and print line of "-"s (the length of this line should be sum-1).
This is the code of my function:
<#function getSeparatorLine map>
<#if !map?has_content>
<#return "">
</#if>
<#local borderLength = 0>
<#list map?keys as item>
<#local borderLength = borderLength + item + 1>
</#list>
<#return ""?right_pad(borderLength - 1, "-")>
</#function>
I get a freemarker error saying:
ERROR freemarker.runtime - Error executing FreeMarker template
freemarker.core.NonNumericalException: For "-" left-hand operand: Expected a number, but this has evaluated to a string (wrapper: f.t.SimpleScalar):
==> borderLength [in template "negative_events_report.template" at line 38, column 27]
Any suggestions?
item is a string, so borderLength + item + 1 actually does string concatenation instead of arithmetical addition. Assuming map contains numerical values, you should write map?values, not map?keys.

How to convert from currency apply addition then use save currency for new sum?

How can I add the following two strings that represent currency (these could be different than en_us currency)?
<#assign op1 = '$5.50'>
<#assign op2 = '$1.00'>
<#assign sum = op1 + op2>
where sum prints out: '$6.50'
Maybe I don't understand the question, but how about:
<#-- Calculate the sum: -->
<#assign op1 = 5.50>
<#assign op2 = 1.00>
<#assign sum = op1 + op2>
...
<#-- Later print out the sum: -->
$${sum?string('0.00')}
Or if you want to build on Java's currency formatter:
${sum?string.currency}
BTW, a template that calculates such business data stinks... it's not the duty of the template. The template is to deal with the formatting/visual-design aspects.

Resources