This class has been posted first time on the Groovy-User Mailing List by Raffaele Castagno in this format:
class TableTemplateFactory
{
def columns = []; // contains columns names and theyr length
def header1 = ''; // contains columns names
def header2 = ''; // contains underscores
def body = ''; // the rows of the table
def footer = ''; // actually unused: can contain footer notes, totals, etc.
def addColumn(name, size)
{
columns << [name:name, size:size];
}
def getTemplate()
{
header1 = "\n";
columns.each{ header1 += ' <%print "'+it.name+'".center('+it.size+')%> ' };
header2 = "\n";
columns.each{ header2 += ' <%print "_"*'+it.size+' %> ' };
body = '\n<% rows.each {%>';
// If a value is longer than given column name, it will be trunked
columns.each{body += ' ${it.'+it.name+'.toString().padRight('+it.size+').substring(0,'+it.size+')} '};
body += '\n<% } %>';
return header1 + header2 + body + footer;
} }
|
and later "groovyfied" by Gavin Grover:
class TableTemplateFactory{
def columns = []
def addColumn(name, size) { columns << [name:name, size:size]; this }
def getTemplate() { """
${columns.collect{ " <%print \"$it.name\".center($it.size)%> " }.join()}
${columns.collect{ " <%print \"_\"*$it.size %> " }.join()}
<% rows.each {%>${columns.collect{ " \${it.${it.name}.toString().padRight($it.size).substring(0,$it.size)} " }.join()}
<% } %>"""
}
}
|
First version is here only as an example of the "groovify process". Of course, the Gavin's version is better.
This class emulate the output of most RDBMS consoles (ie. Oracle SQL*, MySql).
Here's an usage example (again, grooved up by Gavin):
import groovy.text.Template; import groovy.text.SimpleTemplateEngine
def ttf = new TableTemplateFactory().addColumn("name", 15).addColumn("age", 4)
def names = [] << [name:"Raffaele", age:"23"] << [name:"Griorgio", age:"30"]
def binding = ['rows': names]
println new SimpleTemplateEngine().createTemplate(ttf.template).make(binding).toString()
|
This is the output:
name age _______________ ____ Raffaele 23 Griorgio 30 |
Actually is really limited: column width must be declared, and strings are truncated to that given size.
The version below is part of a larger project that you are free to grab and use for you own. The main project wiki page is
here. We prefer static typing for our larger projects, so we have modified the original code to suit our style. Hope it is useful for you. Victor Vlasenko authored most of this enhancement under contract for Build Lackey Labs, and I helped clean up the code a bit for presentation on this site [].
This enhancement enables wrapping-to-multi-lines for column text
that exceeds the width of a given column. We break out word boundaries
(white space) where possible, The implementation we built on truncated
the overly long column value instead of wrapping it.
for this data
[col1: '1',
col2: 'A very long text messsage for you with a superLongWordThatCantBeBrokenAtAWordBoundaryWithoutwrapping']
Instead of this:
col1 col2
---- --------------------
1 A very long text mes
We get this:
col1 col2
---- --------------------
1 A very long text
message for you with
a superLongWordThatC
antBeBrokenOnWordBou
ndaryWithoutwrapping
|
package com.lackey.provis.cmdline.util
/*
* Reproduced from
* http://groovy.codehaus.org/Formatting+simple+tabular+text+data
* (with some code reformatting according to my readability preferences)
*/
class TableTemplateFactory {
def columns = []; // contains columns names and their length
def columnLen = [:]; // contains lengthes of the columns
def header1 = ''; // contains columns names
def header2 = ''; // contains underscores
def body = ''; // the rows of the table
def footer = ''; // actually unused: can contain footer notes, totals, etc.
/**
* Breaks up long line into multiline at the word boundary
*
* TODO move this method to some generic text utils class
*
* @param input long input line
* @param lineWidth maximum output lines width
*
* @return multiline as an array of strings
*/
protected static List<String> wrapLine(input, lineWidth) {
List<String> lines = []
def line = ""
def addWord;
addWord = {word ->
// Add new word if we have space in current line
if ((line.size() + word.size()) <= lineWidth) {
line <<= word
if (line.size() < lineWidth)
line <<= " "
// Our word is longer than line width, break it up
} else if (word.size() > lineWidth) {
def len = lineWidth - line.length()
line <<= word.substring(0, len)
word = word.substring(len)
lines += line.toString()
while (word.size() > lineWidth) {
lines += word.substring(0, lineWidth);
word = word.substring(lineWidth);
}
line = word
if (line.size() > 0 && line.size() < lineWidth)
line <<= " "
// No more space in line - wrap to another line
} else {
lines += line.toString()
line = ""
addWord(word)
}
}
input.split(" ").each() {
addWord(it)
}
lines += line.toString()
return lines
}
/**
* Wraps values in rows according to the column width. Value wrapping performed at
* the word boundary.
*
* @param rows input rows array
*
* @return rows array with multiline values
*/
public List<Map<String,String>> wrapRows(unwrappedRows) {
List<Map<String,List<String>>> multilineRows = []
List<Integer> rowHeights = []
// Preprare unwrappedRows with multiline values
unwrappedRows.each() {
unwrappedRow ->
def multiLineRow = [:]
int height = 1
unwrappedRow.each() {
column -> // column in unwrapped row
List<String> multilineValue = wrapLine(column.value, columnLen[column.key])
if (multilineValue.size() > height)
height = multilineValue.size()
multiLineRow[column.key] = multilineValue // multiLineValue is list of strings
}
multilineRows << multiLineRow
rowHeights << height
}
return foldMultiLineRowsIntoPlainRows(multilineRows, rowHeights)
}
// For each array of strings (wrapped lines) in multiLineRows we fold those in to
// a new array of rows (plain rows). For any given columnn that consists of an array
// of wrapped lines we either insert the appropriate wrapped line, or a blank if no
// more lines are left for that column.
//
private List<Map<String,String>> foldMultiLineRowsIntoPlainRows(
List<Map<String,List<String>>> multilineRows,
List<Integer> rowHeights) {
List<Map<String,String>> plainRows = []
multilineRows.eachWithIndex() {
Map<String,List<String>> mlRow, int idx ->
int height = rowHeights[idx]
for (i in 0..<height) {
Map<String,String> row = [:]
mlRow.each() {
Map.Entry<String, List<String>> col ->
List<String> listOfStringsForColumn = mlRow[col.key]
row[col.key] = listOfStringsForColumn[i] ?: ""
}
plainRows << row
}
}
return plainRows
}
public TableTemplateFactory addColumn(String name, int size) {
columns << [name: name, size: size];
columnLen[name] = size
return this
}
def getTemplate() {
header1 = "\n";
columns.each {
header1 += ' <%print "' + it.name + '".center(' + it.size + ')%> '
};
header2 = "\n";
columns.each {
header2 += ' <%print "_"*' + it.size + ' %> '
};
body = '\n<% rows.each {%>';
// If a value is longer than given column name, it will be trunked
columns.each {
body +=
' ${it.' +
it.name + '.toString().padRight(' +
it.size + ').substring(0,' +
it.size +
')} '
};
body += '\n<% } %>';
return header1 + header2 + body + footer;
}
}
package com.lackey.provis;
/*
*
* Copyright 2009. Build Lackey Labs. All Rights Reserved.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions
* and limitations under the License.
*
* Author: cbedford
* Date: Jan 18, 2009
* Time: 3:21:52 PM
*/
import org.testng.annotations.*
import org.testng.TestNG
import org.testng.TestListenerAdapter
import org.slf4j.*
import groovy.text.Template;
import groovy.text.SimpleTemplateEngine
import com.lackey.provis.cmdline.util.TableTemplateFactory
public class TestTableTemplates {
private static Logger logger = LoggerFactory.getLogger(TestTableTemplates.class.name)
@Test (enabled = true)
public void test_X() {
final TableTemplateFactory factory = new TableTemplateFactory()
def ttf = factory.addColumn("name", 15).addColumn("age", 4)
def names = [] << [name:"Raffaele Smoke BOON laforge calm nwe box ooex", age:"23"] << [name:"Griorgio", age:"30"]
def binding = ['rows': names]
println new SimpleTemplateEngine().createTemplate(ttf.template).make(binding).toString()
}
@Test (enabled = true)
public void testWrapLineSingleWord() {
TableTemplateFactory ttf = new TableTemplateFactory()
assert (ttf.wrapLine("aa bb cc", 2) == ["aa", "bb", "cc"])
}
@Test (enabled = true)
public void testWrapLineSeveralWords() {
TableTemplateFactory ttf = new TableTemplateFactory()
assert (ttf.wrapLine("This is some text that must be broken up to multiline" +
" at a word boundary", 9) == ["This is ", "some text", "that must", "be broken",
"up to ", "multiline", "at a word", "boundary "])
}
@Test (enabled = true)
public void testWrapLineWithLongWord() {
TableTemplateFactory ttf = new TableTemplateFactory()
assert (ttf.wrapLine("A very long text message for you with a" +
" superLongWordThatCantBeBrokenOnWordBoundaryWithoutwrapping", 20) ==
["A very long text ", "message for you with", "a superLongWordThatC",
"antBeBrokenOnWordBou", "ndaryWithoutwrapping"])
}
@Test (enabled = true)
public void testMultilineRowWrappingWithActualPrinting() {
def ttf = new TableTemplateFactory().addColumn("name", 15).addColumn("age", 4)
def names = [] << [name:"Raffaele Gudinio BanderasHulioLopezMakachino", age:"819 4096"] << [name:"Griorgio", age:"30"]
def wrappedNames = ttf.wrapRows(names)
assert wrappedNames ==
[["name":"Raffaele ", "age":"819 "],
["name":"Gudinio Bandera", "age":"4096"],
["name":"sHulioLopezMaka", "age":""],
["name":"chino ", "age":""],
["name":"Griorgio ", "age":"30 "]]
def binding = ['rows': wrappedNames ]
final String templateOutput = getTemplateOutput(binding, ttf)
System.out.println templateOutput
}
private String getTemplateOutput(Map<Object, List> binding, TableTemplateFactory ttf) {
return new SimpleTemplateEngine().createTemplate(ttf.template).make(binding).toString()
}
}
|