...
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 \---- \ col1 col2 ---- -------------------\- 1 1 A very long text mes You now We get this: col1 col2 \---- \ col1 col2 ---- -------------------\- 1 1 A very long text message for you with a superLongWordThatC antBeBrokenOnWordBou ndaryWithoutwrapping 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()
}
}
|
...