aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/jaytaylor
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/jaytaylor')
-rw-r--r--vendor/github.com/jaytaylor/html2text/.travis.yml12
-rw-r--r--vendor/github.com/jaytaylor/html2text/README.md94
-rw-r--r--vendor/github.com/jaytaylor/html2text/html2text.go565
3 files changed, 467 insertions, 204 deletions
diff --git a/vendor/github.com/jaytaylor/html2text/.travis.yml b/vendor/github.com/jaytaylor/html2text/.travis.yml
index 6f150c2dc8..2e8ae28604 100644
--- a/vendor/github.com/jaytaylor/html2text/.travis.yml
+++ b/vendor/github.com/jaytaylor/html2text/.travis.yml
@@ -1,12 +1,12 @@
language: go
go:
+ # n.b. For golang release history, see https://golang.org/doc/devel/release.html
- tip
- - 1.7
- - 1.6
- - 1.5
- - 1.4
- - 1.3
- - 1.2
+ - "1.13.8"
+ - "1.12.17"
+ - "1.11.13"
+ - "1.10.8"
+ - "1.9.7"
notifications:
email:
on_success: change
diff --git a/vendor/github.com/jaytaylor/html2text/README.md b/vendor/github.com/jaytaylor/html2text/README.md
index 6e67dbcc6a..90848faadb 100644
--- a/vendor/github.com/jaytaylor/html2text/README.md
+++ b/vendor/github.com/jaytaylor/html2text/README.md
@@ -4,11 +4,15 @@
[![Build Status](https://travis-ci.org/jaytaylor/html2text.svg?branch=master)](https://travis-ci.org/jaytaylor/html2text)
[![Report Card](https://goreportcard.com/badge/github.com/jaytaylor/html2text)](https://goreportcard.com/report/github.com/jaytaylor/html2text)
-### Converts HTML into text
+### Converts HTML into text of the markdown-flavored variety
## Introduction
+Ensure your emails are readable by all!
+
+Turns HTML into raw text, useful for sending fancy HTML emails with an equivalently nicely formatted TXT document as a fallback (e.g. for people who don't allow HTML emails or have other display issues).
+
html2text is a simple golang package for rendering HTML into plaintext.
There are still lots of improvements to be had, but FWIW this has worked fine for my [basic] HTML-2-text needs.
@@ -19,7 +23,7 @@ It requires go 1.x or newer ;)
## Download the package
```bash
-go get github.com/jaytaylor/html2text
+go get jaytaylor.com/html2text
```
## Example usage
@@ -30,39 +34,51 @@ package main
import (
"fmt"
- "github.com/jaytaylor/html2text"
+ "jaytaylor.com/html2text"
)
func main() {
- inputHtml := `
- <html>
- <head>
- <title>My Mega Service</title>
- <link rel=\"stylesheet\" href=\"main.css\">
- <style type=\"text/css\">body { color: #fff; }</style>
- </head>
-
- <body>
- <div class="logo">
- <a href="http://mymegaservice.com/"><img src="/logo-image.jpg" alt="Mega Service"/></a>
- </div>
-
- <h1>Welcome to your new account on my service!</h1>
-
- <p>
- Here is some more information:
-
- <ul>
- <li>Link 1: <a href="https://example.com">Example.com</a></li>
- <li>Link 2: <a href="https://example2.com">Example2.com</a></li>
- <li>Something else</li>
- </ul>
- </p>
- </body>
- </html>
- `
-
- text, err := html2text.FromString(inputHtml)
+ inputHTML := `
+<html>
+ <head>
+ <title>My Mega Service</title>
+ <link rel=\"stylesheet\" href=\"main.css\">
+ <style type=\"text/css\">body { color: #fff; }</style>
+ </head>
+
+ <body>
+ <div class="logo">
+ <a href="http://jaytaylor.com/"><img src="/logo-image.jpg" alt="Mega Service"/></a>
+ </div>
+
+ <h1>Welcome to your new account on my service!</h1>
+
+ <p>
+ Here is some more information:
+
+ <ul>
+ <li>Link 1: <a href="https://example.com">Example.com</a></li>
+ <li>Link 2: <a href="https://example2.com">Example2.com</a></li>
+ <li>Something else</li>
+ </ul>
+ </p>
+
+ <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>
+ </body>
+</html>`
+
+ text, err := html2text.FromString(inputHTML, html2text.Options{PrettyTables: true})
if err != nil {
panic(err)
}
@@ -72,7 +88,7 @@ func main() {
Output:
```
-Mega Service ( http://mymegaservice.com/ )
+Mega Service ( http://jaytaylor.com/ )
******************************************
Welcome to your new account on my service!
@@ -83,6 +99,15 @@ Here is some more information:
* Link 1: Example.com ( https://example.com )
* Link 2: Example2.com ( https://example2.com )
* Something else
+
++-------------+-------------+
+| HEADER 1 | HEADER 2 |
++-------------+-------------+
+| Row 1 Col 1 | Row 1 Col 2 |
+| Row 2 Col 1 | Row 2 Col 2 |
++-------------+-------------+
+| FOOTER 1 | FOOTER 2 |
++-------------+-------------+
```
@@ -110,3 +135,6 @@ Email: jay at (my github username).com
Twitter: [@jtaylor](https://twitter.com/jtaylor)
+# Alternatives
+
+https://github.com/k3a/html2text - Lightweight
diff --git a/vendor/github.com/jaytaylor/html2text/html2text.go b/vendor/github.com/jaytaylor/html2text/html2text.go
index 66454cfcac..43989092a5 100644
--- a/vendor/github.com/jaytaylor/html2text/html2text.go
+++ b/vendor/github.com/jaytaylor/html2text/html2text.go
@@ -7,174 +7,408 @@ import (
"strings"
"unicode"
+ "github.com/olekukonko/tablewriter"
+ "github.com/ssor/bom"
"golang.org/x/net/html"
"golang.org/x/net/html/atom"
)
+// Options provide toggles and overrides to control specific rendering behaviors.
+type Options struct {
+ PrettyTables bool // Turns on pretty ASCII rendering for table elements.
+ PrettyTablesOptions *PrettyTablesOptions // Configures pretty ASCII rendering for table elements.
+ OmitLinks bool // Turns on omitting links
+}
+
+// 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]
+ }
+
+ ctx := textifyTraverseContext{
+ buf: bytes.Buffer{},
+ options: options,
+ }
+ if err := ctx.traverse(doc); err != nil {
+ return "", err
+ }
+
+ text := strings.TrimSpace(newlineRe.ReplaceAllString(
+ strings.Replace(ctx.buf.String(), "\n ", "\n", -1), "\n\n"),
+ )
+ return text, nil
+}
+
+// FromReader renders text output after parsing HTML for the specified
+// io.Reader.
+func FromReader(reader io.Reader, options ...Options) (string, error) {
+ newReader, err := bom.NewReaderWithoutBom(reader)
+ if err != nil {
+ return "", err
+ }
+ doc, err := html.Parse(newReader)
+ if err != nil {
+ return "", err
+ }
+ return FromHTMLNode(doc, options...)
+}
+
+// FromString parses HTML from the input string, then renders the text form.
+func FromString(input string, options ...Options) (string, error) {
+ bs := bom.CleanBom([]byte(input))
+ text, err := FromReader(bytes.NewReader(bs), options...)
+ if err != nil {
+ return "", err
+ }
+ return text, nil
+}
+
var (
spacingRe = regexp.MustCompile(`[ \r\n\t]+`)
newlineRe = regexp.MustCompile(`\n\n+`)
)
-type textifyTraverseCtx struct {
- Buf bytes.Buffer
+// traverseTableCtx holds text-related context.
+type textifyTraverseContext struct {
+ buf bytes.Buffer
prefix string
- blockquoteLevel int
- lineLength int
+ tableCtx tableTraverseContext
+ options Options
endsWithSpace bool
- endsWithNewline bool
justClosedDiv bool
+ blockquoteLevel int
+ lineLength int
+ isPre bool
}
-func (ctx *textifyTraverseCtx) traverse(node *html.Node) error {
- switch node.Type {
-
- default:
- return ctx.traverseChildren(node)
+// tableTraverseContext holds table ASCII-form related context.
+type tableTraverseContext struct {
+ header []string
+ body [][]string
+ footer []string
+ tmpRow int
+ isInFooter bool
+}
- case html.TextNode:
- data := strings.Trim(spacingRe.ReplaceAllString(node.Data, " "), " ")
- return ctx.emit(data)
+func (tableCtx *tableTraverseContext) init() {
+ tableCtx.body = [][]string{}
+ tableCtx.header = []string{}
+ tableCtx.footer = []string{}
+ tableCtx.isInFooter = false
+ tableCtx.tmpRow = 0
+}
- case html.ElementNode:
+func (ctx *textifyTraverseContext) handleElement(node *html.Node) error {
+ ctx.justClosedDiv = false
- ctx.justClosedDiv = false
- switch node.DataAtom {
- case atom.Br:
- return ctx.emit("\n")
+ switch node.DataAtom {
+ case atom.Br:
+ return ctx.emit("\n")
- case atom.H1, atom.H2, atom.H3:
- subCtx := textifyTraverseCtx{}
- if err := subCtx.traverseChildren(node); err != nil {
- return err
- }
+ case atom.H1, atom.H2, atom.H3:
+ subCtx := textifyTraverseContext{}
+ if err := subCtx.traverseChildren(node); err != nil {
+ return err
+ }
- str := subCtx.Buf.String()
- dividerLen := 0
- for _, line := range strings.Split(str, "\n") {
- if lineLen := len([]rune(line)); lineLen-1 > dividerLen {
- dividerLen = lineLen - 1
- }
- }
- divider := ""
- if node.DataAtom == atom.H1 {
- divider = strings.Repeat("*", dividerLen)
- } else {
- divider = strings.Repeat("-", dividerLen)
+ str := subCtx.buf.String()
+ dividerLen := 0
+ for _, line := range strings.Split(str, "\n") {
+ if lineLen := len([]rune(line)); lineLen-1 > dividerLen {
+ dividerLen = lineLen - 1
}
+ }
+ var divider string
+ if node.DataAtom == atom.H1 {
+ divider = strings.Repeat("*", dividerLen)
+ } else {
+ divider = strings.Repeat("-", dividerLen)
+ }
- if node.DataAtom == atom.H3 {
- return ctx.emit("\n\n" + str + "\n" + divider + "\n\n")
- }
- return ctx.emit("\n\n" + divider + "\n" + str + "\n" + divider + "\n\n")
+ if node.DataAtom == atom.H3 {
+ return ctx.emit("\n\n" + str + "\n" + divider + "\n\n")
+ }
+ return ctx.emit("\n\n" + divider + "\n" + str + "\n" + divider + "\n\n")
- case atom.Blockquote:
- ctx.blockquoteLevel++
- ctx.prefix = strings.Repeat(">", ctx.blockquoteLevel) + " "
+ case atom.Blockquote:
+ ctx.blockquoteLevel++
+ ctx.prefix = strings.Repeat(">", ctx.blockquoteLevel) + " "
+ if err := ctx.emit("\n"); err != nil {
+ return err
+ }
+ if ctx.blockquoteLevel == 1 {
if err := ctx.emit("\n"); err != nil {
return err
}
- if ctx.blockquoteLevel == 1 {
- if err := ctx.emit("\n"); err != nil {
- return err
- }
- }
- if err := ctx.traverseChildren(node); err != nil {
+ }
+ if err := ctx.traverseChildren(node); err != nil {
+ return err
+ }
+ ctx.blockquoteLevel--
+ ctx.prefix = strings.Repeat(">", ctx.blockquoteLevel)
+ if ctx.blockquoteLevel > 0 {
+ ctx.prefix += " "
+ }
+ return ctx.emit("\n\n")
+
+ case atom.Div:
+ if ctx.lineLength > 0 {
+ if err := ctx.emit("\n"); err != nil {
return err
}
- ctx.blockquoteLevel--
- ctx.prefix = strings.Repeat(">", ctx.blockquoteLevel)
- if ctx.blockquoteLevel > 0 {
- ctx.prefix += " "
- }
- return ctx.emit("\n\n")
+ }
+ if err := ctx.traverseChildren(node); err != nil {
+ return err
+ }
+ var err error
+ if !ctx.justClosedDiv {
+ err = ctx.emit("\n")
+ }
+ ctx.justClosedDiv = true
+ return err
+
+ case atom.Li:
+ if err := ctx.emit("* "); err != nil {
+ return err
+ }
+
+ if err := ctx.traverseChildren(node); err != nil {
+ return err
+ }
+
+ return ctx.emit("\n")
+
+ case atom.B, atom.Strong:
+ subCtx := textifyTraverseContext{}
+ subCtx.endsWithSpace = true
+ if err := subCtx.traverseChildren(node); err != nil {
+ return err
+ }
+ str := subCtx.buf.String()
+ return ctx.emit("*" + str + "*")
+
+ case atom.A:
+ linkText := ""
+ // For simple link element content with single text node only, peek at the link text.
+ if node.FirstChild != nil && node.FirstChild.NextSibling == nil && node.FirstChild.Type == html.TextNode {
+ linkText = node.FirstChild.Data
+ }
- case atom.Div:
- if ctx.lineLength > 0 {
- if err := ctx.emit("\n"); err != nil {
+ // If image is the only child, take its alt text as the link text.
+ if img := node.FirstChild; img != nil && node.LastChild == img && img.DataAtom == atom.Img {
+ if altText := getAttrVal(img, "alt"); altText != "" {
+ if err := ctx.emit(altText); err != nil {
return err
}
}
- if err := ctx.traverseChildren(node); err != nil {
- return err
- }
- var err error
- if ctx.justClosedDiv == false {
- err = ctx.emit("\n")
- }
- ctx.justClosedDiv = true
+ } else if err := ctx.traverseChildren(node); err != nil {
return err
+ }
- case atom.Li:
- if err := ctx.emit("* "); err != nil {
- return err
+ hrefLink := ""
+ if attrVal := getAttrVal(node, "href"); attrVal != "" {
+ attrVal = ctx.normalizeHrefLink(attrVal)
+ // Don't print link href if it matches link element content or if the link is empty.
+ if !ctx.options.OmitLinks && attrVal != "" && linkText != attrVal {
+ hrefLink = "( " + attrVal + " )"
}
+ }
- if err := ctx.traverseChildren(node); err != nil {
- return err
- }
+ return ctx.emit(hrefLink)
- return ctx.emit("\n")
+ case atom.P, atom.Ul:
+ return ctx.paragraphHandler(node)
- case atom.B, atom.Strong:
- subCtx := textifyTraverseCtx{}
- subCtx.endsWithSpace = true
- if err := subCtx.traverseChildren(node); err != nil {
- return err
- }
- str := subCtx.Buf.String()
- return ctx.emit("*" + str + "*")
-
- case atom.A:
- // If image is the only child, take its alt text as the link text
- if img := node.FirstChild; img != nil && node.LastChild == img && img.DataAtom == atom.Img {
- if altText := getAttrVal(img, "alt"); altText != "" {
- ctx.emit(altText)
- }
- } else if err := ctx.traverseChildren(node); err != nil {
- return err
- }
+ case atom.Table, atom.Tfoot, atom.Th, atom.Tr, atom.Td:
+ if ctx.options.PrettyTables {
+ return ctx.handleTableElement(node)
+ } else if node.DataAtom == atom.Table {
+ return ctx.paragraphHandler(node)
+ }
+ return ctx.traverseChildren(node)
- hrefLink := ""
- if attrVal := getAttrVal(node, "href"); attrVal != "" {
- attrVal = ctx.normalizeHrefLink(attrVal)
- if attrVal != "" {
- hrefLink = "( " + attrVal + " )"
- }
- }
+ case atom.Pre:
+ ctx.isPre = true
+ err := ctx.traverseChildren(node)
+ ctx.isPre = false
+ return err
- return ctx.emit(hrefLink)
+ case atom.Style, atom.Script, atom.Head:
+ // Ignore the subtree.
+ return nil
- case atom.P, atom.Ul, atom.Table:
- if err := ctx.emit("\n\n"); err != nil {
- return err
- }
+ default:
+ return ctx.traverseChildren(node)
+ }
+}
- if err := ctx.traverseChildren(node); err != nil {
- return err
- }
+// paragraphHandler renders node children surrounded by double newlines.
+func (ctx *textifyTraverseContext) paragraphHandler(node *html.Node) error {
+ if err := ctx.emit("\n\n"); err != nil {
+ return err
+ }
+ if err := ctx.traverseChildren(node); err != nil {
+ return err
+ }
+ return ctx.emit("\n\n")
+}
- return ctx.emit("\n\n")
+// handleTableElement is only to be invoked when options.PrettyTables is active.
+func (ctx *textifyTraverseContext) handleTableElement(node *html.Node) error {
+ if !ctx.options.PrettyTables {
+ panic("handleTableElement invoked when PrettyTables not active")
+ }
- case atom.Tr:
- if err := ctx.traverseChildren(node); err != nil {
- return err
- }
+ switch node.DataAtom {
+ case atom.Table:
+ if err := ctx.emit("\n\n"); err != nil {
+ return err
+ }
+
+ // Re-intialize all table context.
+ ctx.tableCtx.init()
+
+ // Browse children, enriching context with table data.
+ if err := ctx.traverseChildren(node); err != nil {
+ return err
+ }
+
+ 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)
+
+ // Render the table using ASCII.
+ table.Render()
+ if err := ctx.emit(buf.String()); err != nil {
+ return err
+ }
- return ctx.emit("\n")
+ return ctx.emit("\n\n")
- case atom.Style, atom.Script, atom.Head:
- // Ignore the subtree
- return nil
+ case atom.Tfoot:
+ ctx.tableCtx.isInFooter = true
+ if err := ctx.traverseChildren(node); err != nil {
+ return err
+ }
+ ctx.tableCtx.isInFooter = false
+
+ case atom.Tr:
+ ctx.tableCtx.body = append(ctx.tableCtx.body, []string{})
+ if err := ctx.traverseChildren(node); err != nil {
+ return err
+ }
+ ctx.tableCtx.tmpRow++
- default:
- return ctx.traverseChildren(node)
+ case atom.Th:
+ res, err := ctx.renderEachChild(node)
+ if err != nil {
+ return err
}
+
+ ctx.tableCtx.header = append(ctx.tableCtx.header, res)
+
+ case atom.Td:
+ res, err := ctx.renderEachChild(node)
+ if err != nil {
+ return err
+ }
+
+ if ctx.tableCtx.isInFooter {
+ ctx.tableCtx.footer = append(ctx.tableCtx.footer, res)
+ } else {
+ ctx.tableCtx.body[ctx.tableCtx.tmpRow] = append(ctx.tableCtx.body[ctx.tableCtx.tmpRow], res)
+ }
+
+ }
+ return nil
+}
+
+func (ctx *textifyTraverseContext) traverse(node *html.Node) error {
+ switch node.Type {
+ default:
+ return ctx.traverseChildren(node)
+
+ case html.TextNode:
+ var data string
+ if ctx.isPre {
+ data = node.Data
+ } else {
+ data = strings.TrimSpace(spacingRe.ReplaceAllString(node.Data, " "))
+ }
+ return ctx.emit(data)
+
+ case html.ElementNode:
+ return ctx.handleElement(node)
}
}
-func (ctx *textifyTraverseCtx) traverseChildren(node *html.Node) error {
+func (ctx *textifyTraverseContext) traverseChildren(node *html.Node) error {
for c := node.FirstChild; c != nil; c = c.NextSibling {
if err := ctx.traverse(c); err != nil {
return err
@@ -184,31 +418,33 @@ func (ctx *textifyTraverseCtx) traverseChildren(node *html.Node) error {
return nil
}
-func (ctx *textifyTraverseCtx) emit(data string) error {
- if len(data) == 0 {
+func (ctx *textifyTraverseContext) emit(data string) error {
+ if data == "" {
return nil
}
- lines := ctx.breakLongLines(data)
- var err error
+ var (
+ lines = ctx.breakLongLines(data)
+ err error
+ )
for _, line := range lines {
runes := []rune(line)
startsWithSpace := unicode.IsSpace(runes[0])
- if !startsWithSpace && !ctx.endsWithSpace {
- ctx.Buf.WriteByte(' ')
+ if !startsWithSpace && !ctx.endsWithSpace && !strings.HasPrefix(data, ".") {
+ if err = ctx.buf.WriteByte(' '); err != nil {
+ return err
+ }
ctx.lineLength++
}
ctx.endsWithSpace = unicode.IsSpace(runes[len(runes)-1])
for _, c := range line {
- _, err = ctx.Buf.WriteString(string(c))
- if err != nil {
+ if _, err = ctx.buf.WriteString(string(c)); err != nil {
return err
}
ctx.lineLength++
if c == '\n' {
ctx.lineLength = 0
if ctx.prefix != "" {
- _, err = ctx.Buf.WriteString(ctx.prefix)
- if err != nil {
+ if _, err = ctx.buf.WriteString(ctx.prefix); err != nil {
return err
}
}
@@ -218,27 +454,31 @@ func (ctx *textifyTraverseCtx) emit(data string) error {
return nil
}
-func (ctx *textifyTraverseCtx) breakLongLines(data string) []string {
- // only break lines when we are in blockquotes
+const maxLineLen = 74
+
+func (ctx *textifyTraverseContext) breakLongLines(data string) []string {
+ // Only break lines when in blockquotes.
if ctx.blockquoteLevel == 0 {
return []string{data}
}
- var ret []string
- runes := []rune(data)
- l := len(runes)
- existing := ctx.lineLength
- if existing >= 74 {
+ var (
+ ret = []string{}
+ runes = []rune(data)
+ l = len(runes)
+ existing = ctx.lineLength
+ )
+ if existing >= maxLineLen {
ret = append(ret, "\n")
existing = 0
}
- for l+existing > 74 {
- i := 74 - existing
+ for l+existing > maxLineLen {
+ i := maxLineLen - existing
for i >= 0 && !unicode.IsSpace(runes[i]) {
i--
}
if i == -1 {
- // no spaces, so go the other way
- i = 74 - existing
+ // No spaces, so go the other way.
+ i = maxLineLen - existing
for i < l && !unicode.IsSpace(runes[i]) {
i++
}
@@ -257,12 +497,33 @@ func (ctx *textifyTraverseCtx) breakLongLines(data string) []string {
return ret
}
-func (ctx *textifyTraverseCtx) normalizeHrefLink(link string) string {
+func (ctx *textifyTraverseContext) normalizeHrefLink(link string) string {
link = strings.TrimSpace(link)
link = strings.TrimPrefix(link, "mailto:")
return link
}
+// renderEachChild visits each direct child of a node and collects the sequence of
+// textuual representaitons separated by a single newline.
+func (ctx *textifyTraverseContext) renderEachChild(node *html.Node) (string, error) {
+ buf := &bytes.Buffer{}
+ for c := node.FirstChild; c != nil; c = c.NextSibling {
+ s, err := FromHTMLNode(c, ctx.options)
+ if err != nil {
+ return "", err
+ }
+ if _, err = buf.WriteString(s); err != nil {
+ return "", err
+ }
+ if c.NextSibling != nil {
+ if err = buf.WriteByte('\n'); err != nil {
+ return "", err
+ }
+ }
+ }
+ return buf.String(), nil
+}
+
func getAttrVal(node *html.Node, attrName string) string {
for _, attr := range node.Attr {
if attr.Key == attrName {
@@ -272,29 +533,3 @@ func getAttrVal(node *html.Node, attrName string) string {
return ""
}
-
-func FromReader(reader io.Reader) (string, error) {
- doc, err := html.Parse(reader)
- if err != nil {
- return "", err
- }
-
- ctx := textifyTraverseCtx{
- Buf: bytes.Buffer{},
- }
- if err = ctx.traverse(doc); err != nil {
- return "", err
- }
-
- text := strings.TrimSpace(newlineRe.ReplaceAllString(
- strings.Replace(ctx.Buf.String(), "\n ", "\n", -1), "\n\n"))
- return text, nil
-}
-
-func FromString(input string) (string, error) {
- text, err := FromReader(strings.NewReader(input))
- if err != nil {
- return "", err
- }
- return text, nil
-}