You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
381 lines
8.9 KiB
381 lines
8.9 KiB
//
|
|
// DISCLAIMER
|
|
//
|
|
// Copyright 2017 ArangoDB GmbH, Cologne, Germany
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
|
//
|
|
// Author Ewout Prangsma
|
|
//
|
|
|
|
package velocypack
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
)
|
|
|
|
type DumperOptions struct {
|
|
// EscapeUnicode turns on escapping multi-byte Unicode characters when dumping them to JSON (creates \uxxxx sequences).
|
|
EscapeUnicode bool
|
|
// EscapeForwardSlashes turns on escapping forward slashes when serializing VPack values into JSON.
|
|
EscapeForwardSlashes bool
|
|
UnsupportedTypeBehavior UnsupportedTypeBehavior
|
|
}
|
|
|
|
type UnsupportedTypeBehavior int
|
|
|
|
const (
|
|
NullifyUnsupportedType UnsupportedTypeBehavior = iota
|
|
ConvertUnsupportedType
|
|
FailOnUnsupportedType
|
|
)
|
|
|
|
type Dumper struct {
|
|
w io.Writer
|
|
indentation uint
|
|
options DumperOptions
|
|
}
|
|
|
|
// NewDumper creates a new dumper around the given writer, with an optional options.
|
|
func NewDumper(w io.Writer, options *DumperOptions) *Dumper {
|
|
d := &Dumper{
|
|
w: w,
|
|
}
|
|
if options != nil {
|
|
d.options = *options
|
|
}
|
|
return d
|
|
}
|
|
|
|
func (d *Dumper) Append(s Slice) error {
|
|
w := d.w
|
|
switch s.Type() {
|
|
case Null:
|
|
if _, err := w.Write([]byte("null")); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
return nil
|
|
case Bool:
|
|
if v, err := s.GetBool(); err != nil {
|
|
return WithStack(err)
|
|
} else if v {
|
|
if _, err := w.Write([]byte("true")); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
} else {
|
|
if _, err := w.Write([]byte("false")); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
}
|
|
return nil
|
|
case Double:
|
|
if v, err := s.GetDouble(); err != nil {
|
|
return WithStack(err)
|
|
} else if err := d.appendDouble(v); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
return nil
|
|
case Int, SmallInt:
|
|
if v, err := s.GetInt(); err != nil {
|
|
return WithStack(err)
|
|
} else if err := d.appendInt(v); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
return nil
|
|
case UInt:
|
|
if v, err := s.GetUInt(); err != nil {
|
|
return WithStack(err)
|
|
} else if err := d.appendUInt(v); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
return nil
|
|
case String:
|
|
if v, err := s.GetString(); err != nil {
|
|
return WithStack(err)
|
|
} else if err := d.appendString(v); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
return nil
|
|
case Array:
|
|
if err := d.appendArray(s); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
return nil
|
|
case Object:
|
|
if err := d.appendObject(s); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
return nil
|
|
default:
|
|
switch d.options.UnsupportedTypeBehavior {
|
|
case NullifyUnsupportedType:
|
|
if _, err := w.Write([]byte("null")); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
case ConvertUnsupportedType:
|
|
msg := fmt.Sprintf("(non-representable type %s)", s.Type().String())
|
|
if err := d.appendString(msg); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
default:
|
|
return WithStack(NoJSONEquivalentError)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
doubleQuoteSeq = []byte{'"'}
|
|
escapeTable = [256]byte{
|
|
// 0 1 2 3 4 5 6 7 8 9 A B C D E
|
|
// F
|
|
'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'b', 't', 'n', 'u', 'f', 'r',
|
|
'u',
|
|
'u', // 00
|
|
'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u',
|
|
'u',
|
|
'u', // 10
|
|
0, 0, '"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0,
|
|
'/', // 20
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0,
|
|
0, // 30~4F
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
'\\', 0, 0, 0, // 50
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0,
|
|
0, // 60~FF
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0}
|
|
)
|
|
|
|
func (d *Dumper) appendUInt(v uint64) error {
|
|
s := strconv.FormatUint(v, 10)
|
|
if _, err := d.w.Write([]byte(s)); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *Dumper) appendInt(v int64) error {
|
|
s := strconv.FormatInt(v, 10)
|
|
if _, err := d.w.Write([]byte(s)); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func formatDouble(v float64) string {
|
|
return strconv.FormatFloat(v, 'g', -1, 64)
|
|
}
|
|
|
|
func (d *Dumper) appendDouble(v float64) error {
|
|
s := formatDouble(v)
|
|
if _, err := d.w.Write([]byte(s)); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *Dumper) appendString(v string) error {
|
|
p := []byte(v)
|
|
e := len(p)
|
|
buf := make([]byte, 0, 16)
|
|
if _, err := d.w.Write(doubleQuoteSeq); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
for i := 0; i < e; i++ {
|
|
buf = buf[0:0]
|
|
c := p[i]
|
|
if (c & 0x80) == 0 {
|
|
// check for control characters
|
|
esc := escapeTable[c]
|
|
|
|
if esc != 0 {
|
|
if c != '/' || d.options.EscapeForwardSlashes {
|
|
// escape forward slashes only when requested
|
|
buf = append(buf, '\\')
|
|
}
|
|
buf = append(buf, esc)
|
|
|
|
if esc == 'u' {
|
|
i1 := ((uint(c)) & 0xf0) >> 4
|
|
i2 := ((uint(c)) & 0x0f)
|
|
|
|
buf = append(buf, '0', '0', hexChar(i1), hexChar(i2))
|
|
}
|
|
} else {
|
|
buf = append(buf, c)
|
|
}
|
|
} else if (c & 0xe0) == 0xc0 {
|
|
// two-byte sequence
|
|
if i+1 >= e {
|
|
return WithStack(InvalidUtf8SequenceError)
|
|
}
|
|
|
|
if d.options.EscapeUnicode {
|
|
value := ((uint(p[i]) & 0x1f) << 6) | (uint(p[i+1]) & 0x3f)
|
|
buf = dumpUnicodeCharacter(buf, value)
|
|
} else {
|
|
buf = append(buf, p[i:i+2]...)
|
|
}
|
|
i++
|
|
} else if (c & 0xf0) == 0xe0 {
|
|
// three-byte sequence
|
|
if i+2 >= e {
|
|
return WithStack(InvalidUtf8SequenceError)
|
|
}
|
|
|
|
if d.options.EscapeUnicode {
|
|
value := (((uint(p[i]) & 0x0f) << 12) | ((uint(p[i+1]) & 0x3f) << 6) | (uint(p[i + +2]) & 0x3f))
|
|
buf = dumpUnicodeCharacter(buf, value)
|
|
} else {
|
|
buf = append(buf, p[i:i+3]...)
|
|
}
|
|
i += 2
|
|
} else if (c & 0xf8) == 0xf0 {
|
|
// four-byte sequence
|
|
if i+3 >= e {
|
|
return WithStack(InvalidUtf8SequenceError)
|
|
}
|
|
|
|
if d.options.EscapeUnicode {
|
|
value := (((uint(p[i]) & 0x0f) << 18) | ((uint(p[i+1]) & 0x3f) << 12) | ((uint(p[i+2]) & 0x3f) << 6) | (uint(p[i+3]) & 0x3f))
|
|
// construct the surrogate pairs
|
|
value -= 0x10000
|
|
high := (((value & 0xffc00) >> 10) + 0xd800)
|
|
buf = dumpUnicodeCharacter(buf, high)
|
|
low := (value & 0x3ff) + 0xdc00
|
|
buf = dumpUnicodeCharacter(buf, low)
|
|
} else {
|
|
buf = append(buf, p[i:i+4]...)
|
|
}
|
|
i += 3
|
|
}
|
|
if _, err := d.w.Write(buf); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
}
|
|
if _, err := d.w.Write(doubleQuoteSeq); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *Dumper) appendArray(v Slice) error {
|
|
w := d.w
|
|
it, err := NewArrayIterator(v)
|
|
if err != nil {
|
|
return WithStack(err)
|
|
}
|
|
if _, err := w.Write([]byte{'['}); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
for it.IsValid() {
|
|
if !it.IsFirst() {
|
|
if _, err := w.Write([]byte{','}); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
}
|
|
if value, err := it.Value(); err != nil {
|
|
return WithStack(err)
|
|
} else if err := d.Append(value); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
if err := it.Next(); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
}
|
|
if _, err := w.Write([]byte{']'}); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *Dumper) appendObject(v Slice) error {
|
|
w := d.w
|
|
it, err := NewObjectIterator(v)
|
|
if err != nil {
|
|
return WithStack(err)
|
|
}
|
|
if _, err := w.Write([]byte{'{'}); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
for it.IsValid() {
|
|
if !it.IsFirst() {
|
|
if _, err := w.Write([]byte{','}); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
}
|
|
if key, err := it.Key(true); err != nil {
|
|
return WithStack(err)
|
|
} else if err := d.Append(key); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
if _, err := w.Write([]byte{':'}); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
if value, err := it.Value(); err != nil {
|
|
return WithStack(err)
|
|
} else if err := d.Append(value); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
if err := it.Next(); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
}
|
|
if _, err := w.Write([]byte{'}'}); err != nil {
|
|
return WithStack(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func dumpUnicodeCharacter(dst []byte, value uint) []byte {
|
|
dst = append(dst, '\\', 'u')
|
|
|
|
mask := uint(0xf000)
|
|
shift := uint(12)
|
|
for i := 3; i >= 0; i-- {
|
|
p := (value & mask) >> shift
|
|
dst = append(dst, hexChar(p))
|
|
if i > 0 {
|
|
mask = mask >> 4
|
|
shift -= 4
|
|
}
|
|
}
|
|
return dst
|
|
}
|
|
|
|
func hexChar(v uint) byte {
|
|
v = v & uint(0x0f)
|
|
if v < 10 {
|
|
return byte('0' + v)
|
|
}
|
|
return byte('A' + v - 10)
|
|
}
|
|
|