├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── print.go └── print_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTML Pretty Print for Go (golang) 2 | Package hpp (github.com/Joker/hpp) is a HTML formatter for Go. 3 | 4 | 5 | example: 6 | ```html 7 | Pug

Pug - template engine

11 |

You are amazing


First name:
Last name: 12 |

Pug is a terse and simple templating 13 | language with a strong focus on 14 | performance and powerful features.

15 | ``` 16 | becomes 17 | ```html 18 | 19 | 20 | 21 | Pug 22 | 27 | 28 | 29 |

Pug - template engine

30 |
31 |

You are amazing

32 |
33 |
First name: 34 | 35 |
Last name: 36 | 37 |
38 |

39 | Pug is a terse and simple templating 40 | language with a strong focus on 41 | performance and powerful features. 42 |

43 |
44 | 45 | 46 | ``` 47 | 48 | 49 | ### Installation 50 | 51 | ```sh 52 | $ go get github.com/Joker/hpp 53 | ``` -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Joker/hpp 2 | 3 | go 1.12 4 | 5 | require golang.org/x/net v0.0.0-20190327091125-710a502c58a2 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 2 | golang.org/x/net v0.0.0-20190327091125-710a502c58a2 h1:17UhVDrPb40BH5k6cyeb2V/7QlBNdo/a0+r0dtK+Utw= 3 | golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 4 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 5 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 6 | -------------------------------------------------------------------------------- /print.go: -------------------------------------------------------------------------------- 1 | package hpp 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "log" 8 | "regexp" 9 | "strings" 10 | "unicode/utf8" 11 | 12 | "golang.org/x/net/html" 13 | ) 14 | 15 | // TabStr - tab size 16 | var TabStr = []byte(" ") 17 | 18 | // NewStr should be "\n" or "\r\n" for windows 19 | var NewStr = []byte("\n") 20 | 21 | func isInline(tag []byte) bool { 22 | switch string(tag) { 23 | case "a_", "b", "i", "em", "strong", "code", "span", "ins", 24 | "big", "small", "tt", "abbr", "acronym", "cite", "dfn", 25 | "kbd", "samp", "var", "bdo", "map", "q", "sub", "sup": 26 | return true 27 | default: 28 | return false 29 | } 30 | } 31 | 32 | func isVoid(tag []byte) bool { 33 | switch string(tag) { 34 | case "input", "link", "meta", "hr", "img", "br", "area", "base", "col", 35 | "param", "command", "embed", "keygen", "source", "track", "wbr": 36 | return true 37 | default: 38 | return false 39 | } 40 | } 41 | 42 | func PrPrint(in string) string { 43 | out := new(bytes.Buffer) 44 | Format(strings.NewReader(in), out) 45 | return strings.TrimLeft(out.String(), "\n\r\t ") 46 | } 47 | 48 | func ByPrint(in []byte) []byte { 49 | out := new(bytes.Buffer) 50 | Format(bytes.NewReader(in), out) 51 | return bytes.TrimLeft(out.Bytes(), "\n\r\t ") 52 | } 53 | 54 | func Print(r io.Reader) []byte { 55 | out := new(bytes.Buffer) 56 | Format(r, out) 57 | return bytes.TrimLeft(out.Bytes(), "\n\r\t ") 58 | } 59 | 60 | func Format(r io.Reader, w io.Writer) { 61 | var ( 62 | tokenize = html.NewTokenizer(r) 63 | depth = 0 64 | LongText = false 65 | prevType html.TokenType 66 | tagName []byte 67 | prvName []byte 68 | rb = regexp.MustCompile(`^\s+\S`) 69 | re = regexp.MustCompile(`\S\s+$`) 70 | ) 71 | Loop: 72 | for { 73 | nowType := tokenize.Next() 74 | 75 | if nowType != html.TextToken { 76 | prvName = tagName 77 | tagName, _ = tokenize.TagName() 78 | } 79 | 80 | switch nowType { 81 | case html.StartTagToken: 82 | if !(isInline(tagName) && prevType == html.TextToken) { 83 | w.Write(NewStr) 84 | w.Write(bytes.Repeat(TabStr, depth)) 85 | } 86 | w.Write(tokenize.Raw()) 87 | if !isVoid(tagName) { 88 | depth += 1 89 | } 90 | 91 | case html.SelfClosingTagToken, html.CommentToken, html.DoctypeToken: 92 | w.Write(NewStr) 93 | w.Write(bytes.Repeat(TabStr, depth)) 94 | w.Write(tokenize.Raw()) 95 | 96 | case html.EndTagToken: 97 | if depth > 0 { 98 | depth -= 1 99 | } 100 | switch { 101 | case !bytes.Equal(prvName, tagName), 102 | prevType == html.SelfClosingTagToken, 103 | prevType == html.CommentToken, 104 | prevType == html.DoctypeToken, 105 | prevType == html.EndTagToken, 106 | prevType == html.TextToken && LongText: 107 | 108 | w.Write(NewStr) 109 | w.Write(bytes.Repeat(TabStr, depth)) 110 | } 111 | w.Write(tokenize.Raw()) 112 | 113 | case html.TextToken: 114 | t := bytes.Replace(tokenize.Raw(), []byte{'\t'}, TabStr, -1) 115 | text := bytes.Trim(t, "\n\r ") 116 | if re.Match(t) { 117 | text = append(text, ' ') 118 | } 119 | LongText = false 120 | if len(text) > 0 { 121 | if bytes.Contains(text, []byte{'\n'}) { 122 | if !(prevType == html.EndTagToken && isInline(tagName)) { 123 | w.Write(NewStr) 124 | w.Write(bytes.Repeat(TabStr, depth)) 125 | } else { 126 | if rb.Match(t) { 127 | text = append([]byte{' '}, text...) 128 | } 129 | } 130 | w.Write(txtFmt(text, depth)) 131 | LongText = true 132 | } else { 133 | switch { 134 | case utf8.RuneCount(text) > 80, prevType != html.StartTagToken: 135 | 136 | if !(prevType == html.EndTagToken && isInline(tagName)) { 137 | w.Write(NewStr) 138 | w.Write(bytes.Repeat(TabStr, depth)) 139 | LongText = true 140 | } else { 141 | if rb.Match(t) { 142 | text = append([]byte{' '}, text...) 143 | } 144 | } 145 | } 146 | w.Write(text) 147 | } 148 | } 149 | 150 | case html.ErrorToken: 151 | err := tokenize.Err() 152 | if err.Error() == "EOF" { 153 | break Loop 154 | } 155 | log.Panicln(err) 156 | 157 | } 158 | prevType = nowType 159 | } 160 | w.Write(NewStr) 161 | } 162 | 163 | func PHPformat(in []byte) []byte { 164 | in = bytes.Replace(in, []byte(""), []byte("}}}"), -1) 166 | 167 | wr := new(bytes.Buffer) 168 | Format(bytes.NewReader(in), wr) 169 | 170 | out := bytes.Replace(wr.Bytes(), []byte("{{{"), []byte(""), -1) 172 | return bytes.TrimLeft(out, "\n\r\t ") 173 | } 174 | 175 | func txtFmt(txt []byte, depth int) []byte { 176 | var ( 177 | min = 1000 178 | ln = 0 179 | f = func(c rune) bool { return '\n' != c && ' ' != c } 180 | ) 181 | for _, v := range bytes.FieldsFunc(txt, f) { 182 | ln = len(bytes.TrimLeft(v, " ")) 183 | if ln > 0 && ln < min { 184 | min = ln 185 | } 186 | } 187 | var re = regexp.MustCompile(fmt.Sprintf(`\n\s{%d}`, min-1)) 188 | return re.ReplaceAllLiteral(txt, append([]byte{'\n'}, bytes.Repeat(TabStr, depth)...)) 189 | } 190 | -------------------------------------------------------------------------------- /print_test.go: -------------------------------------------------------------------------------- 1 | package hpp 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | var input = `Pug

Pug - template engine

14 |

You are amazing


First name:
Last name: 15 |

Pug is a terse and simple templating 16 | language with a strong focus on 17 | performance and powerful features.

` 18 | 19 | var expect = ` 20 | 21 | 22 | Pug 23 | 28 | 29 | 30 |

Pug - template engine

31 |
32 |

You are amazing

33 |
34 |
First name: 35 | 36 |
Last name: 37 | 38 |
39 |

40 | Pug is a terse and simple templating 41 | language with a strong focus on 42 | performance and powerful features. 43 |

44 |
45 | 46 | 47 | ` 48 | 49 | func TestFormat(t *testing.T) { 50 | bf := new(bytes.Buffer) 51 | Format(strings.NewReader(input), bf) 52 | output := bf.String() 53 | if expect != strings.TrimLeft(output, "\n\r\t ") { 54 | t.Errorf("\n------ expect:\n%s\n------ output:\n%s", expect, output) 55 | } 56 | } 57 | 58 | func TestPrint(t *testing.T) { 59 | output := Print(strings.NewReader(input)) 60 | if expect != string(output) { 61 | t.Errorf("\n------ expect:\n%s\n------ output:\n%s", expect, output) 62 | } 63 | } 64 | 65 | func TestByPrint(t *testing.T) { 66 | output := ByPrint([]byte(input)) 67 | if expect != string(output) { 68 | t.Errorf("\n------ expect:\n%s\n------ output:\n%s", expect, output) 69 | } 70 | } 71 | 72 | func TestPrPrint(t *testing.T) { 73 | output := PrPrint(input) 74 | if expect != output { 75 | t.Errorf("\n------ expect:\n%s\n------ output:\n%s", expect, output) 76 | } else { 77 | fmt.Print(output) 78 | } 79 | } 80 | --------------------------------------------------------------------------------