076cd77469
Signed-off-by: Stephan Renatus <srenatus@chef.io>
277 lines
5.6 KiB
Go
277 lines
5.6 KiB
Go
// Copyright 2015-2019 Brett Vickers.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package etree
|
|
|
|
import (
|
|
"bufio"
|
|
"io"
|
|
"strings"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// A simple stack
|
|
type stack struct {
|
|
data []interface{}
|
|
}
|
|
|
|
func (s *stack) empty() bool {
|
|
return len(s.data) == 0
|
|
}
|
|
|
|
func (s *stack) push(value interface{}) {
|
|
s.data = append(s.data, value)
|
|
}
|
|
|
|
func (s *stack) pop() interface{} {
|
|
value := s.data[len(s.data)-1]
|
|
s.data[len(s.data)-1] = nil
|
|
s.data = s.data[:len(s.data)-1]
|
|
return value
|
|
}
|
|
|
|
func (s *stack) peek() interface{} {
|
|
return s.data[len(s.data)-1]
|
|
}
|
|
|
|
// A fifo is a simple first-in-first-out queue.
|
|
type fifo struct {
|
|
data []interface{}
|
|
head, tail int
|
|
}
|
|
|
|
func (f *fifo) add(value interface{}) {
|
|
if f.len()+1 >= len(f.data) {
|
|
f.grow()
|
|
}
|
|
f.data[f.tail] = value
|
|
if f.tail++; f.tail == len(f.data) {
|
|
f.tail = 0
|
|
}
|
|
}
|
|
|
|
func (f *fifo) remove() interface{} {
|
|
value := f.data[f.head]
|
|
f.data[f.head] = nil
|
|
if f.head++; f.head == len(f.data) {
|
|
f.head = 0
|
|
}
|
|
return value
|
|
}
|
|
|
|
func (f *fifo) len() int {
|
|
if f.tail >= f.head {
|
|
return f.tail - f.head
|
|
}
|
|
return len(f.data) - f.head + f.tail
|
|
}
|
|
|
|
func (f *fifo) grow() {
|
|
c := len(f.data) * 2
|
|
if c == 0 {
|
|
c = 4
|
|
}
|
|
buf, count := make([]interface{}, c), f.len()
|
|
if f.tail >= f.head {
|
|
copy(buf[0:count], f.data[f.head:f.tail])
|
|
} else {
|
|
hindex := len(f.data) - f.head
|
|
copy(buf[0:hindex], f.data[f.head:])
|
|
copy(buf[hindex:count], f.data[:f.tail])
|
|
}
|
|
f.data, f.head, f.tail = buf, 0, count
|
|
}
|
|
|
|
// countReader implements a proxy reader that counts the number of
|
|
// bytes read from its encapsulated reader.
|
|
type countReader struct {
|
|
r io.Reader
|
|
bytes int64
|
|
}
|
|
|
|
func newCountReader(r io.Reader) *countReader {
|
|
return &countReader{r: r}
|
|
}
|
|
|
|
func (cr *countReader) Read(p []byte) (n int, err error) {
|
|
b, err := cr.r.Read(p)
|
|
cr.bytes += int64(b)
|
|
return b, err
|
|
}
|
|
|
|
// countWriter implements a proxy writer that counts the number of
|
|
// bytes written by its encapsulated writer.
|
|
type countWriter struct {
|
|
w io.Writer
|
|
bytes int64
|
|
}
|
|
|
|
func newCountWriter(w io.Writer) *countWriter {
|
|
return &countWriter{w: w}
|
|
}
|
|
|
|
func (cw *countWriter) Write(p []byte) (n int, err error) {
|
|
b, err := cw.w.Write(p)
|
|
cw.bytes += int64(b)
|
|
return b, err
|
|
}
|
|
|
|
// isWhitespace returns true if the byte slice contains only
|
|
// whitespace characters.
|
|
func isWhitespace(s string) bool {
|
|
for i := 0; i < len(s); i++ {
|
|
if c := s[i]; c != ' ' && c != '\t' && c != '\n' && c != '\r' {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// spaceMatch returns true if namespace a is the empty string
|
|
// or if namespace a equals namespace b.
|
|
func spaceMatch(a, b string) bool {
|
|
switch {
|
|
case a == "":
|
|
return true
|
|
default:
|
|
return a == b
|
|
}
|
|
}
|
|
|
|
// spaceDecompose breaks a namespace:tag identifier at the ':'
|
|
// and returns the two parts.
|
|
func spaceDecompose(str string) (space, key string) {
|
|
colon := strings.IndexByte(str, ':')
|
|
if colon == -1 {
|
|
return "", str
|
|
}
|
|
return str[:colon], str[colon+1:]
|
|
}
|
|
|
|
// Strings used by indentCRLF and indentLF
|
|
const (
|
|
indentSpaces = "\r\n "
|
|
indentTabs = "\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"
|
|
)
|
|
|
|
// indentCRLF returns a CRLF newline followed by n copies of the first
|
|
// non-CRLF character in the source string.
|
|
func indentCRLF(n int, source string) string {
|
|
switch {
|
|
case n < 0:
|
|
return source[:2]
|
|
case n < len(source)-1:
|
|
return source[:n+2]
|
|
default:
|
|
return source + strings.Repeat(source[2:3], n-len(source)+2)
|
|
}
|
|
}
|
|
|
|
// indentLF returns a LF newline followed by n copies of the first non-LF
|
|
// character in the source string.
|
|
func indentLF(n int, source string) string {
|
|
switch {
|
|
case n < 0:
|
|
return source[1:2]
|
|
case n < len(source)-1:
|
|
return source[1 : n+2]
|
|
default:
|
|
return source[1:] + strings.Repeat(source[2:3], n-len(source)+2)
|
|
}
|
|
}
|
|
|
|
// nextIndex returns the index of the next occurrence of sep in s,
|
|
// starting from offset. It returns -1 if the sep string is not found.
|
|
func nextIndex(s, sep string, offset int) int {
|
|
switch i := strings.Index(s[offset:], sep); i {
|
|
case -1:
|
|
return -1
|
|
default:
|
|
return offset + i
|
|
}
|
|
}
|
|
|
|
// isInteger returns true if the string s contains an integer.
|
|
func isInteger(s string) bool {
|
|
for i := 0; i < len(s); i++ {
|
|
if (s[i] < '0' || s[i] > '9') && !(i == 0 && s[i] == '-') {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
type escapeMode byte
|
|
|
|
const (
|
|
escapeNormal escapeMode = iota
|
|
escapeCanonicalText
|
|
escapeCanonicalAttr
|
|
)
|
|
|
|
// escapeString writes an escaped version of a string to the writer.
|
|
func escapeString(w *bufio.Writer, s string, m escapeMode) {
|
|
var esc []byte
|
|
last := 0
|
|
for i := 0; i < len(s); {
|
|
r, width := utf8.DecodeRuneInString(s[i:])
|
|
i += width
|
|
switch r {
|
|
case '&':
|
|
esc = []byte("&")
|
|
case '<':
|
|
esc = []byte("<")
|
|
case '>':
|
|
if m == escapeCanonicalAttr {
|
|
continue
|
|
}
|
|
esc = []byte(">")
|
|
case '\'':
|
|
if m != escapeNormal {
|
|
continue
|
|
}
|
|
esc = []byte("'")
|
|
case '"':
|
|
if m == escapeCanonicalText {
|
|
continue
|
|
}
|
|
esc = []byte(""")
|
|
case '\t':
|
|
if m != escapeCanonicalAttr {
|
|
continue
|
|
}
|
|
esc = []byte("	")
|
|
case '\n':
|
|
if m != escapeCanonicalAttr {
|
|
continue
|
|
}
|
|
esc = []byte("
")
|
|
case '\r':
|
|
if m == escapeNormal {
|
|
continue
|
|
}
|
|
esc = []byte("
")
|
|
default:
|
|
if !isInCharacterRange(r) || (r == 0xFFFD && width == 1) {
|
|
esc = []byte("\uFFFD")
|
|
break
|
|
}
|
|
continue
|
|
}
|
|
w.WriteString(s[last : i-width])
|
|
w.Write(esc)
|
|
last = i
|
|
}
|
|
w.WriteString(s[last:])
|
|
}
|
|
|
|
func isInCharacterRange(r rune) bool {
|
|
return r == 0x09 ||
|
|
r == 0x0A ||
|
|
r == 0x0D ||
|
|
r >= 0x20 && r <= 0xD7FF ||
|
|
r >= 0xE000 && r <= 0xFFFD ||
|
|
r >= 0x10000 && r <= 0x10FFFF
|
|
}
|