109 lines
2.6 KiB
Go
109 lines
2.6 KiB
Go
|
// Copyright (c) 2021 Tulir Asokan
|
||
|
//
|
||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||
|
|
||
|
package binary
|
||
|
|
||
|
import (
|
||
|
"encoding/hex"
|
||
|
"fmt"
|
||
|
"sort"
|
||
|
"strings"
|
||
|
"unicode"
|
||
|
"unicode/utf8"
|
||
|
)
|
||
|
|
||
|
// Options to control how Node.XMLString behaves.
|
||
|
var (
|
||
|
IndentXML = false
|
||
|
MaxBytesToPrintAsHex = 128
|
||
|
)
|
||
|
|
||
|
// XMLString converts the Node to its XML representation
|
||
|
func (n *Node) XMLString() string {
|
||
|
content := n.contentString()
|
||
|
if len(content) == 0 {
|
||
|
return fmt.Sprintf("<%[1]s%[2]s/>", n.Tag, n.attributeString())
|
||
|
}
|
||
|
newline := "\n"
|
||
|
if len(content) == 1 || !IndentXML {
|
||
|
newline = ""
|
||
|
}
|
||
|
return fmt.Sprintf("<%[1]s%[2]s>%[4]s%[3]s%[4]s</%[1]s>", n.Tag, n.attributeString(), strings.Join(content, newline), newline)
|
||
|
}
|
||
|
|
||
|
func (n *Node) attributeString() string {
|
||
|
if len(n.Attrs) == 0 {
|
||
|
return ""
|
||
|
}
|
||
|
stringAttrs := make([]string, len(n.Attrs)+1)
|
||
|
i := 1
|
||
|
for key, value := range n.Attrs {
|
||
|
stringAttrs[i] = fmt.Sprintf(`%s="%v"`, key, value)
|
||
|
i++
|
||
|
}
|
||
|
sort.Strings(stringAttrs)
|
||
|
return strings.Join(stringAttrs, " ")
|
||
|
}
|
||
|
|
||
|
func printable(data []byte) string {
|
||
|
if !utf8.Valid(data) {
|
||
|
return ""
|
||
|
}
|
||
|
str := string(data)
|
||
|
for _, c := range str {
|
||
|
if !unicode.IsPrint(c) {
|
||
|
return ""
|
||
|
}
|
||
|
}
|
||
|
return str
|
||
|
}
|
||
|
|
||
|
func (n *Node) contentString() []string {
|
||
|
split := make([]string, 0)
|
||
|
switch content := n.Content.(type) {
|
||
|
case []Node:
|
||
|
for _, item := range content {
|
||
|
split = append(split, strings.Split(item.XMLString(), "\n")...)
|
||
|
}
|
||
|
case []byte:
|
||
|
if strContent := printable(content); len(strContent) > 0 {
|
||
|
if IndentXML {
|
||
|
split = append(split, strings.Split(string(content), "\n")...)
|
||
|
} else {
|
||
|
split = append(split, strings.ReplaceAll(string(content), "\n", "\\n"))
|
||
|
}
|
||
|
} else if len(content) > MaxBytesToPrintAsHex {
|
||
|
split = append(split, fmt.Sprintf("<!-- %d bytes -->", len(content)))
|
||
|
} else if !IndentXML {
|
||
|
split = append(split, hex.EncodeToString(content))
|
||
|
} else {
|
||
|
hexData := hex.EncodeToString(content)
|
||
|
for i := 0; i < len(hexData); i += 80 {
|
||
|
if len(hexData) < i+80 {
|
||
|
split = append(split, hexData[i:])
|
||
|
} else {
|
||
|
split = append(split, hexData[i:i+80])
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
case nil:
|
||
|
// don't append anything
|
||
|
default:
|
||
|
strContent := fmt.Sprintf("%s", content)
|
||
|
if IndentXML {
|
||
|
split = append(split, strings.Split(strContent, "\n")...)
|
||
|
} else {
|
||
|
split = append(split, strings.ReplaceAll(strContent, "\n", "\\n"))
|
||
|
}
|
||
|
}
|
||
|
if len(split) > 1 && IndentXML {
|
||
|
for i, line := range split {
|
||
|
split[i] = " " + line
|
||
|
}
|
||
|
}
|
||
|
return split
|
||
|
}
|