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

//
// 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)
}