...
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 Lasenko 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 Chris/Lead Lackey at the Labs[].
Summary of Enhancement
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.
Example:
| Code Block |
|---|
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 |
| Code Block |
|---|
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()
}
}
|
...