all repos — htmlformat @ db3ccbe063c5534525722ec8cc07c0ba7adb0601

Go package and CLI tool used to format HTML

remove newlines in elements with only text content

Alan Pearce
commit

db3ccbe063c5534525722ec8cc07c0ba7adb0601

parent

5bd994fe268e4d505a9793143fa85414c7d50887

3 files changed, 59 insertions(+), 30 deletions(-)

changed files
M README.mdREADME.md
@@ -23,14 +23,12 @@
### CLI ```bash -echo '<ol><li style="&">A</li><li>B</li></ol>' | htmlformat +echo '<ol><li style="&"><em>A</em></li><li>B</li></ol>' | htmlformat <ol> <li style="&"> - A - </li> - <li> - B + <em>A</em> </li> + <li>B</li> </ol> ```
M format.goformat.go
@@ -4,6 +4,8 @@ import (
"fmt" "io" "strings" + "unicode" + "unicode/utf8" "golang.org/x/net/html" "golang.org/x/net/html/atom"
@@ -109,17 +111,34 @@ }
return false } +func getFirstRune(s string) rune { + r, _ := utf8.DecodeRuneInString(s) + return r +} + +func hasSingleTextChild(n *html.Node) bool { + return n != nil && n.FirstChild != nil && n.FirstChild == n.LastChild && n.FirstChild.Type == html.TextNode +} + func printNode(w io.Writer, n *html.Node, level int) (err error) { switch n.Type { case html.TextNode: s := n.Data s = strings.TrimSpace(s) if s != "" { - if err = printIndent(w, level); err != nil { + if !hasSingleTextChild(n.Parent) && + (n.PrevSibling == nil || !unicode.IsPunct(getFirstRune(s))) { + if err = printIndent(w, level); err != nil { + return + } + } + if _, err = fmt.Fprint(w, s); err != nil { return } - if _, err = fmt.Fprintln(w, s); err != nil { - return + if !hasSingleTextChild(n.Parent) { + if _, err = fmt.Fprint(w, "\n"); err != nil { + return + } } } case html.ElementNode:
@@ -135,18 +154,32 @@ if _, err = fmt.Fprintf(w, ` %s="%s"`, a.Key, val); err != nil {
return } } - if _, err = fmt.Fprintln(w, ">"); err != nil { + if _, err = fmt.Fprint(w, ">"); err != nil { return } + if !hasSingleTextChild(n) { + if _, err = fmt.Fprint(w, "\n"); err != nil { + return + } + } if !isVoidElement(n) { if err = printChildren(w, n, level+1); err != nil { return } - if err = printIndent(w, level); err != nil { - return + if !hasSingleTextChild(n) { + if err = printIndent(w, level); err != nil { + return + } } - if _, err = fmt.Fprintf(w, "</%s>\n", n.Data); err != nil { + if _, err = fmt.Fprintf(w, "</%s>", n.Data); err != nil { return + } + + if n.NextSibling == nil || + (!unicode.IsPunct(getFirstRune(n.NextSibling.Data)) || n.NextSibling.Type == html.ElementNode) { + if _, err = fmt.Fprint(w, "\n"); err != nil { + return + } } } case html.CommentNode:
M format_test.goformat_test.go
@@ -24,12 +24,8 @@ {
name: "html attribute escaping is normalized", input: `<ol> <li style="&amp;&#38;"> A </li> <li> B </li> </ol> `, expected: `<ol> - <li style="&amp;&amp;"> - A - </li> - <li> - B - </li> + <li style="&amp;&amp;">A</li> + <li>B</li> </ol> `, },
@@ -37,12 +33,8 @@ {
name: "bare ampersands are escaped", input: `<ol> <li style="&"> A </li> <li> B </li> </ol> `, expected: `<ol> - <li style="&amp;"> - A - </li> - <li> - B - </li> + <li style="&amp;">A</li> + <li>B</li> </ol> `, },
@@ -50,12 +42,8 @@ {
name: "html elements are indented", input: `<ol> <li class="name"> A </li> <li> B </li> </ol> `, expected: `<ol> - <li class="name"> - A - </li> - <li> - B - </li> + <li class="name">A</li> + <li>B</li> </ol> `, },
@@ -63,6 +51,16 @@ {
name: "text fragments are supported", input: `test 123`, expected: `test 123` + "\n", + }, + { + name: "phrasing content element children are kept on the same line, including punctuation", + input: `<ul><li><a href="http://example.com">Test</a>.</li></ul>`, + expected: `<ul> + <li> + <a href="http://example.com">Test</a>. + </li> +</ul> +`, }, }