Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 15 additions & 65 deletions html2text.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,55 +21,18 @@ type Options struct {
TextOnly bool // Returns only plain text
}

// PrettyTablesOptions overrides tablewriter behaviors
type PrettyTablesOptions struct {
AutoFormatHeader bool
AutoWrapText bool
ReflowDuringAutoWrap bool
ColWidth int
ColumnSeparator string
RowSeparator string
CenterSeparator string
HeaderAlignment int
FooterAlignment int
Alignment int
ColumnAlignment []int
NewLine string
HeaderLine bool
RowLine bool
AutoMergeCells bool
Borders tablewriter.Border
}

// NewPrettyTablesOptions creates PrettyTablesOptions with default settings
func NewPrettyTablesOptions() *PrettyTablesOptions {
return &PrettyTablesOptions{
AutoFormatHeader: true,
AutoWrapText: true,
ReflowDuringAutoWrap: true,
ColWidth: tablewriter.MAX_ROW_WIDTH,
ColumnSeparator: tablewriter.COLUMN,
RowSeparator: tablewriter.ROW,
CenterSeparator: tablewriter.CENTER,
HeaderAlignment: tablewriter.ALIGN_DEFAULT,
FooterAlignment: tablewriter.ALIGN_DEFAULT,
Alignment: tablewriter.ALIGN_DEFAULT,
ColumnAlignment: []int{},
NewLine: tablewriter.NEWLINE,
HeaderLine: true,
RowLine: false,
AutoMergeCells: false,
Borders: tablewriter.Border{Left: true, Right: true, Bottom: true, Top: true},
}
}

// FromHTMLNode renders text output from a pre-parsed HTML document.
func FromHTMLNode(doc *html.Node, o ...Options) (string, error) {
var options Options
if len(o) > 0 {
options = o[0]
}

if options.PrettyTables && options.PrettyTablesOptions == nil {
// defaults need to make explicit as they are no longer identical with tablewriter
options.PrettyTablesOptions = NewPrettyTablesOptions()
}

ctx := textifyTraverseContext{
buf: bytes.Buffer{},
options: options,
Expand Down Expand Up @@ -333,31 +296,18 @@ func (ctx *textifyTraverseContext) handleTableElement(node *html.Node) error {

buf := &bytes.Buffer{}
table := tablewriter.NewWriter(buf)
if ctx.options.PrettyTablesOptions != nil {
options := ctx.options.PrettyTablesOptions
table.SetAutoFormatHeaders(options.AutoFormatHeader)
table.SetAutoWrapText(options.AutoWrapText)
table.SetReflowDuringAutoWrap(options.ReflowDuringAutoWrap)
table.SetColWidth(options.ColWidth)
table.SetColumnSeparator(options.ColumnSeparator)
table.SetRowSeparator(options.RowSeparator)
table.SetCenterSeparator(options.CenterSeparator)
table.SetHeaderAlignment(options.HeaderAlignment)
table.SetFooterAlignment(options.FooterAlignment)
table.SetAlignment(options.Alignment)
table.SetColumnAlignment(options.ColumnAlignment)
table.SetNewLine(options.NewLine)
table.SetHeaderLine(options.HeaderLine)
table.SetRowLine(options.RowLine)
table.SetAutoMergeCells(options.AutoMergeCells)
table.SetBorders(options.Borders)
}
table.SetHeader(ctx.tableCtx.header)
table.SetFooter(ctx.tableCtx.footer)
table.AppendBulk(ctx.tableCtx.body)
ctx.options.PrettyTablesOptions.configureTable(table)

table.Header(ctx.tableCtx.header)
table.Footer(ctx.tableCtx.footer)
if err := table.Bulk(ctx.tableCtx.body); err != nil {
return err
}

// Render the table using ASCII.
table.Render()
if err := table.Render(); err != nil {
return err
}
if err := ctx.emit(buf.String()); err != nil {
return err
}
Expand Down
51 changes: 51 additions & 0 deletions html2text_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import (
"regexp"
"strings"
"testing"

"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw"
)

const destPath = "testdata"
Expand Down Expand Up @@ -353,6 +356,54 @@ Table 2 Header 1 Table 2 Header 2 Table 2 Footer 1 Table 2 Footer 2 Table 2 Row
}
}

func TestTablesWithDirectConfiguration(t *testing.T) {
testCases := []struct {
input string
tabularOutput string
}{
{
`<table>
<thead>
<tr><th>Header 1</th><th>Header 2</th></tr>
</thead>
<tfoot>
<tr><td>Footer 1</td><td>Footer 2</td></tr>
</tfoot>
<tbody>
<tr><td>Row 1 Col 1</td><td>Row 1 Col 2</td></tr>
<tr><td>Row 2 Col 1</td><td>Row 2 Col 2</td></tr>
</tbody>
</table>`,
`┌─────────────┬─────────────┐
│ HEADER 1 │ HEADER 2 │
├─────────────┼─────────────┤
│ Row 1 Col 1 │ Row 1 Col 2 │
│ Row 2 Col 1 │ Row 2 Col 2 │
└─────────────┴─────────────┘`,
},
}

for _, testCase := range testCases {
options := Options{
PrettyTables: true,
PrettyTablesOptions: &PrettyTablesOptions{
Configuration: func(table *tablewriter.Table) {
table.Options(
tablewriter.WithHeaderAlignment(tw.AlignRight),
tablewriter.WithFooterControl(tw.Control{Hide: tw.On}),
)
},
},
}
// Check pretty tabular ASCII version.
if msg, err := wantString(testCase.input, testCase.tabularOutput, options); err != nil {
t.Error(err)
} else if len(msg) > 0 {
t.Log(msg)
}
}
}

func TestStrippingLists(t *testing.T) {
testCases := []struct {
input string
Expand Down
212 changes: 212 additions & 0 deletions prettytables.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
package html2text

import (
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw"
)

// PrettyTablesOptions overrides tablewriter behaviors
type PrettyTablesOptions struct {
AutoFormatHeader bool
AutoWrapText bool
// Deprecated. Tablewriter always assumes this to be `true`
ReflowDuringAutoWrap bool
ColWidth int
ColumnSeparator string
RowSeparator string
CenterSeparator string
HeaderAlignment tw.Align
FooterAlignment tw.Align
Alignment tw.Align
ColumnAlignment tw.Alignment
// Deprecated. Tablewriter always assumes this to be `\n`
NewLine string
HeaderLine bool
RowLine bool
AutoMergeCells bool
Borders Border
// Configuration allows to directly manipulate the `Table` with all what [tablewriter] offers.
// Setting this ignores all the rest of the settings of this struct.
Configuration func(table *tablewriter.Table)
}

// NewPrettyTablesOptions creates PrettyTablesOptions with default settings
func NewPrettyTablesOptions() *PrettyTablesOptions {
return &PrettyTablesOptions{
AutoFormatHeader: true,
AutoWrapText: true,
ColWidth: 32, // old tablewriter.MAX_ROW_WIDTH + borders now count into width
ColumnSeparator: defaultBorderStyle.ColumnSeparator,
RowSeparator: defaultBorderStyle.RowSeparator,
CenterSeparator: defaultBorderStyle.CenterSeparator,
HeaderAlignment: tw.AlignCenter,
FooterAlignment: tw.AlignCenter,
Alignment: tw.AlignDefault,
ColumnAlignment: make(tw.Alignment, 0),
HeaderLine: true,
RowLine: false,
AutoMergeCells: false,
Borders: Border{Left: true, Right: true, Bottom: true, Top: true},
}
}

func (p *PrettyTablesOptions) configureTable(table *tablewriter.Table) {
if p.Configuration != nil {
p.Configuration(table)
return
}

cfg := tablewriter.NewConfigBuilder()

cfg.WithHeaderAutoFormat(asState(p.AutoFormatHeader)).WithFooterAutoFormat(asState(p.AutoFormatHeader)).
WithRowAutoWrap(p.wrapMode()).WithHeaderAutoWrap(p.wrapMode()).WithFooterAutoWrap(p.wrapMode()).
WithRowMaxWidth(p.ColWidth).
WithHeaderAlignment(p.HeaderAlignment).
WithFooterAlignment(p.FooterAlignment).
WithRowAlignment(p.Alignment).
WithRowMergeMode(p.mergeMode())

if len(p.ColumnAlignment) > 0 {
cfg.Row().Alignment().WithPerColumn(p.ColumnAlignment)
}

rendition := tw.Rendition{
Borders: p.Borders.withStates(),
Symbols: p.borderStyle(),
Settings: p.renderSettings(),
}

table.Options(
tablewriter.WithConfig(cfg.Build()),
tablewriter.WithRendition(rendition))
}

func (p *PrettyTablesOptions) wrapMode() int {
if p.AutoWrapText {
return tw.WrapNormal
} else {
return tw.WrapNone
}
}

func (p *PrettyTablesOptions) mergeMode() int {
if p.AutoMergeCells {
return tw.MergeVertical
} else {
return tw.MergeNone
}
}

func (p *PrettyTablesOptions) renderSettings() tw.Settings {
return tw.Settings{
Lines: tw.Lines{
ShowHeaderLine: asState(p.HeaderLine),
},
Separators: tw.Separators{
BetweenRows: asState(p.RowLine),
},
}
}

// Border controls tablewriter borders. It uses simple bools instead of tablewriters `State`
type Border struct {
Left, Right, Bottom, Top bool
}

func (b Border) withStates() tw.Border {
return tw.Border{
Left: asState(b.Left),
Right: asState(b.Right),
Bottom: asState(b.Bottom),
Top: asState(b.Top),
}
}

type BorderStyle struct {
ColumnSeparator string
RowSeparator string
CenterSeparator string
}

func (b BorderStyle) Name() string {
return "html2text"
}

func (b BorderStyle) Center() string {
return b.CenterSeparator
}

func (b BorderStyle) Row() string {
return b.RowSeparator
}

func (b BorderStyle) Column() string {
return b.ColumnSeparator
}

func (b BorderStyle) TopLeft() string {
return b.CenterSeparator
}

func (b BorderStyle) TopMid() string {
return b.CenterSeparator
}

func (b BorderStyle) TopRight() string {
return b.CenterSeparator
}

func (b BorderStyle) MidLeft() string {
return b.CenterSeparator
}

func (b BorderStyle) MidRight() string {
return b.CenterSeparator
}

func (b BorderStyle) BottomLeft() string {
return b.CenterSeparator
}

func (b BorderStyle) BottomMid() string {
return b.CenterSeparator
}

func (b BorderStyle) BottomRight() string {
return b.CenterSeparator
}

func (b BorderStyle) HeaderLeft() string {
return b.CenterSeparator
}

func (b BorderStyle) HeaderMid() string {
return b.CenterSeparator
}

func (b BorderStyle) HeaderRight() string {
return b.CenterSeparator
}

var defaultBorderStyle = BorderStyle{
ColumnSeparator: "|",
RowSeparator: "-",
CenterSeparator: "+",
}

func (p *PrettyTablesOptions) borderStyle() BorderStyle {
return BorderStyle{
ColumnSeparator: p.ColumnSeparator,
RowSeparator: p.RowSeparator,
CenterSeparator: p.CenterSeparator,
}
}

func asState(b bool) tw.State {
// TableWriter does not provide this by default :(
if b {
return tw.On
} else {
return tw.Off
}
}