Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

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

...