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.
373 lines
8.0 KiB
373 lines
8.0 KiB
// ------------------------------------------------------------------------
|
|
// Project atila
|
|
// Active Thing (activething.com) git.activething.com/go
|
|
//
|
|
// File name builder.go
|
|
// Created by DEV
|
|
// Modified 18/01/2024
|
|
//
|
|
// Copyright 2024 activething.com
|
|
// ------------------------------------------------------------------------
|
|
// 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.
|
|
// ------------------------------------------------------------------------
|
|
|
|
package _pjson
|
|
|
|
import (
|
|
"activething.com/go/gogo/core/bins"
|
|
"activething.com/go/gogo/core/errs"
|
|
"activething.com/go/gogo/core/opts"
|
|
"activething.com/go/gogo/packs"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"math"
|
|
"math/cmplx"
|
|
"time"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
type (
|
|
|
|
Builder struct {
|
|
options Options
|
|
encoder *json.Encoder
|
|
dateLayout string
|
|
scratch [64]byte
|
|
state packs.BuilderState
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
func NewBuilder (options ...opts.OptionFnc[Builder]) *Builder {
|
|
return opts.Apply[Builder](&Builder{
|
|
state : *packs.NewBuilderState(),
|
|
dateLayout: time.RFC822Z,
|
|
options : JSONOptionsQuote | JSONOptionsEscapeHTML,
|
|
}, options ...)
|
|
}
|
|
|
|
|
|
|
|
func (p *Builder) Data () []byte {
|
|
return p.state.Bytes()
|
|
}
|
|
|
|
func (p *Builder) Error() error {
|
|
return p.state.Error
|
|
}
|
|
|
|
func (p *Builder) SetError (err error) {
|
|
p.state.SetError(err)
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Builder) Reset() {
|
|
p.encoder = nil
|
|
p.state.Reset()
|
|
}
|
|
|
|
|
|
|
|
func (p *Builder) Any (value any) {
|
|
switch value.(type) {
|
|
case json.Marshaler :
|
|
p.state.SetError(p.jsonEncoder().Encode(value))
|
|
|
|
default:
|
|
packs.BuildAny(p ,value,func(v any) error { return p.jsonEncoder().Encode(value) })
|
|
}
|
|
}
|
|
|
|
|
|
func (p *Builder) Binary (value []byte) {
|
|
if value == nil {
|
|
p.state.Write(G.nullValue)
|
|
return
|
|
}
|
|
if len(value) == 0{
|
|
p.state.Write(G.stringEmpty)
|
|
return
|
|
}
|
|
|
|
p.state.WriteByte('"')
|
|
ln := base64.StdEncoding.EncodedLen(len(value))
|
|
if ln <= len(p.scratch) {
|
|
dst := p.scratch[:ln]
|
|
base64.StdEncoding.Encode(dst, value)
|
|
p.state.Write(dst)
|
|
} else if ln <= 1024 {
|
|
dst := make([]byte, ln)
|
|
base64.StdEncoding.Encode(dst, value)
|
|
p.state.Write(dst)
|
|
} else {
|
|
enc := base64.NewEncoder(base64.StdEncoding, p.state)
|
|
enc.Write(value)
|
|
enc.Close()
|
|
}
|
|
p.state.WriteByte('"')
|
|
}
|
|
|
|
|
|
func (p *Builder) String(value string) {
|
|
if !p.state.SetValue() {
|
|
panic(p.state.Error)
|
|
}
|
|
if len(value) == 0 {
|
|
p.state.Write(G.stringEmpty)
|
|
} else {
|
|
p.string(value,p.options.EscapeHTML())
|
|
}
|
|
return
|
|
}
|
|
|
|
|
|
func (p *Builder) Bool (value bool) {
|
|
if !p.state.SetValue() {
|
|
panic(p.state.Error)
|
|
}
|
|
if value {
|
|
p.state.Write(G.trueValue)
|
|
} else {
|
|
p.state.Write(G.falseValue)
|
|
}
|
|
}
|
|
|
|
|
|
func (p *Builder) Rune (value rune) {
|
|
p.UInt64(uint64(value))
|
|
}
|
|
|
|
|
|
func (p *Builder) UInt(value uint) { p.UInt64(uint64(value)) }
|
|
func (p *Builder) UInt8(value uint8) { p.UInt64(uint64(value)) }
|
|
func (p *Builder) UInt16(value uint16) { p.UInt64(uint64(value)) }
|
|
func (p *Builder) UInt32(value uint32) { p.UInt64(uint64(value)) }
|
|
func (p *Builder) UInt64(value uint64) {
|
|
if !p.state.SetValue() {
|
|
panic(p.state.Error)
|
|
}
|
|
if value == 0 {
|
|
p.state.WriteByte('0')
|
|
} else {
|
|
p.state.WriteUIntStr(value, 10)
|
|
}
|
|
}
|
|
|
|
func (p *Builder) Int (value int) { p.Int64(int64(value)) }
|
|
func (p *Builder) Int8 (value int8) { p.Int64(int64(value)) }
|
|
func (p *Builder) Int16(value int16) { p.Int64(int64(value)) }
|
|
func (p *Builder) Int32(value int32) { p.Int64(int64(value)) }
|
|
func (p *Builder) Int64(value int64) {
|
|
if !p.state.SetValue() {
|
|
panic(p.state.Error)
|
|
}
|
|
if value == 0 {
|
|
if p.options.IgnoreDefaults() && p.state.Node.Kind == packs.NodeKindObject {
|
|
p.state.Truncate(p.state.Len())
|
|
return
|
|
}
|
|
p.state.WriteByte('0')
|
|
} else {
|
|
p.state.WriteIntStr(value, 10)
|
|
}
|
|
}
|
|
|
|
|
|
func (p *Builder) Float32(value float32) { p.Float64(float64(value)) }
|
|
|
|
func (p *Builder) Float64(value float64) {
|
|
if !p.state.SetValue() {
|
|
panic(p.state.Error)
|
|
}
|
|
if p.options.QuoteFloats() {
|
|
p.state.WriteByte('"')
|
|
}
|
|
switch {
|
|
case value == 0 : p.state.Write(G.zeroValue)
|
|
case math.IsNaN(value) : p.state.Write(G.floatNaN)
|
|
case math.IsInf(value, 1) : p.state.Write(G.floatInfPos)
|
|
case math.IsInf(value, -1) : p.state.Write(G.floatInfNeg)
|
|
default:
|
|
p.state.WriteFloatStr(value, 'f', -1)
|
|
}
|
|
if p.options.QuoteFloats() {
|
|
p.state.WriteByte('"')
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func (p *Builder) Complex64(value complex64) { p.Complex128(complex128(value)) }
|
|
func (p *Builder) Complex128(value complex128){
|
|
if !p.state.SetValue() {
|
|
panic(p.state.Error)
|
|
}
|
|
if p.options.QuoteFloats() {
|
|
p.state.WriteByte('"')
|
|
}
|
|
switch {
|
|
case value == 0 : p.state.Write(G.zeroValue)
|
|
case cmplx.IsInf(value): p.state.Write(G.floatInfPos)
|
|
case cmplx.IsNaN(value): p.state.Write(G.floatNaN)
|
|
default:
|
|
r, i := real(value), imag(value)
|
|
p.state.WriteByte('"')
|
|
p.state.WriteFloatStr(r, 'f', -1)
|
|
p.state.WriteByte('+')
|
|
p.state.WriteFloatStr(i, 'f', -1)
|
|
p.state.Write2Byte('i', '"')
|
|
}
|
|
if p.options.QuoteFloats() {
|
|
p.state.WriteByte('"')
|
|
}
|
|
}
|
|
|
|
func (p *Builder) Date (value time.Time) {
|
|
if !p.state.SetValue() {
|
|
panic(p.state.Error)
|
|
}
|
|
p.state.WriteByte('"')
|
|
p.state.AppendWith(bins.AppendFnc(func(d []byte) []byte {
|
|
return value.AppendFormat(d,p.dateLayout)
|
|
}))
|
|
p.state.WriteByte('"')
|
|
}
|
|
|
|
|
|
func (p *Builder) List(buildFnc func(builder packs.BuilderList)) {
|
|
p.packCompound(packs.NodeKindList, '[', ']', func() { buildFnc(p) })
|
|
}
|
|
|
|
func (p *Builder) Map(buildFnc func(builder packs.BuilderMap)) {
|
|
p.packCompound(packs.NodeKindMap, '{', '}', func() { buildFnc(p) })
|
|
}
|
|
|
|
func (p *Builder) Object(buildFnc func(builder packs.BuilderObject)) {
|
|
p.packCompound(packs.NodeKindObject, '{', '}', func() { buildFnc(p) })
|
|
}
|
|
|
|
|
|
func (p *Builder) packCompound(kind packs.NodeKind, opening, closing byte, buildFnc func()) {
|
|
dn := false
|
|
defer func(d *bool) {
|
|
if !(*d) {
|
|
p.state.SetError(errs.ToError(recover()))
|
|
}
|
|
}(&dn)
|
|
if p.open(kind, opening) {
|
|
buildFnc()
|
|
p.close(closing)
|
|
}
|
|
dn = true
|
|
}
|
|
|
|
|
|
|
|
func (p *Builder) string(s string, scapeHTML bool) {
|
|
p.state.WriteByte('"')
|
|
start := 0
|
|
for i := 0; i < len(s); {
|
|
if b := s[i]; b < utf8.RuneSelf {
|
|
if !G.jsonChars[b].isHtmlUnsafe() || (!scapeHTML && !G.jsonChars[b].isUnsafe()) {
|
|
i++
|
|
continue
|
|
}
|
|
|
|
if start < i {
|
|
p.state.WriteString(s[start:i])
|
|
}
|
|
p.state.WriteByte('\\')
|
|
switch b {
|
|
case '\\', '"': p.state.WriteByte(b)
|
|
case '\n': p.state.WriteByte('n')
|
|
case '\r': p.state.WriteByte('r')
|
|
case '\t': p.state.WriteByte('t')
|
|
default:
|
|
p.state.WriteString(`u00`)
|
|
p.state.Write2Byte(G.hex[b>>4], G.hex[b&0xF])
|
|
}
|
|
i++
|
|
start = i
|
|
continue
|
|
}
|
|
c, size := utf8.DecodeRuneInString(s[i:])
|
|
if c == utf8.RuneError && size == 1 {
|
|
if start < i {
|
|
p.state.WriteString(s[start:i])
|
|
}
|
|
p.state.WriteString(`\ufffd`)
|
|
i += size
|
|
start = i
|
|
continue
|
|
}
|
|
|
|
if c == '\u2028' || c == '\u2029' {
|
|
if start < i {
|
|
p.state.WriteString(s[start:i])
|
|
}
|
|
p.state.WriteString(`\u202`)
|
|
p.state.WriteByte(G.hex[c&0xF])
|
|
i += size
|
|
start = i
|
|
continue
|
|
}
|
|
i += size
|
|
}
|
|
if start < len(s) {
|
|
p.state.WriteString(s[start:])
|
|
}
|
|
p.state.WriteByte('"')
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (p *Builder) throwPanicIfError() {
|
|
if p.state.Error != nil {
|
|
panic(p.state.Error)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func (p *Builder) jsonEncoder () *json.Encoder {
|
|
if p.encoder == nil {
|
|
p.encoder = json.NewEncoder(p.state)
|
|
p.encoder.SetEscapeHTML(p.options.EscapeHTML())
|
|
}
|
|
return p.encoder
|
|
}
|
|
|
|
func (p *Builder) open(kind packs.NodeKind, opening byte) bool {
|
|
if p.state.Open(kind) {
|
|
p.addSep()
|
|
p.state.WriteByte(opening)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (p *Builder) close(closing byte) bool {
|
|
if p.state.Close() {
|
|
p.state.WriteByte(closing)
|
|
return true
|
|
}
|
|
return false
|
|
}
|