Files
mstore/vendor/go.yaml.in/yaml/v4/internal/libyaml/serializer.go
T
2026-06-16 08:02:19 +02:00

423 lines
12 KiB
Go

// Copyright 2011-2019 Canonical Ltd
// Copyright 2025 The go-yaml Project Contributors
// SPDX-License-Identifier: Apache-2.0
// Serializer stage: Converts representation tree (Nodes) to event stream.
// Walks the node tree and produces events for the emitter.
package libyaml
import (
"errors"
"fmt"
"io"
"strings"
"unicode/utf8"
)
// Serializer handles serialization of YAML nodes to event stream.
type Serializer struct {
Emitter Emitter
out []byte
lineWidth int
explicitStart bool
explicitEnd bool
flowSimpleCollections bool
quotePreference QuoteStyle
doneInit bool
}
// NewSerializer creates a new Serializer with the given options.
func NewSerializer(w io.Writer, opts *Options) *Serializer {
emitter := NewEmitter()
emitter.CompactSequenceIndent = opts.CompactSeqIndent
emitter.quotePreference = opts.QuotePreference
emitter.SetWidth(opts.LineWidth)
emitter.SetUnicode(opts.Unicode)
emitter.SetCanonical(opts.Canonical)
emitter.SetLineBreak(opts.LineBreak)
// Set indentation (defaults to 2 if not specified)
indent := opts.Indent
if indent == 0 {
indent = 2
}
emitter.BestIndent = indent
if w != nil {
emitter.SetOutputWriter(w)
}
return &Serializer{
Emitter: emitter,
lineWidth: opts.LineWidth,
explicitStart: opts.ExplicitStart,
explicitEnd: opts.ExplicitEnd,
flowSimpleCollections: opts.FlowSimpleCollections,
quotePreference: opts.QuotePreference,
}
}
// Serialize walks a Node tree and emits events to produce YAML output.
// This is the primary method for the Serializer stage.
func (s *Serializer) Serialize(node *Node) {
s.init()
s.node(node, "")
}
// Sentinel values for event creation.
// These provide clarity at call sites, similar to http.NoBody.
var (
noVersionDirective *VersionDirective = nil
noTagDirective []TagDirective = nil
)
// init initializes the serializer by emitting a STREAM_START event.
func (s *Serializer) init() {
if s.doneInit {
return
}
s.emit(NewStreamStartEvent(UTF8_ENCODING))
s.doneInit = true
}
// Finish completes serialization by emitting a STREAM_END event.
func (s *Serializer) Finish() {
s.Emitter.OpenEnded = false
s.emit(NewStreamEndEvent())
}
// node serializes a Node tree into YAML events.
// This is the core of the serializer stage - it walks the tree and produces
// events.
func (s *Serializer) node(node *Node, tail string) {
// Zero nodes behave as nil.
if node.Kind == 0 && node.IsZero() {
s.emitScalar("null", "", "", PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
return
}
// Tags have been processed by Desolver:
// - Empty tag = can be inferred or style handles it
// - Non-empty tag = emit explicitly
// Style has also been set by Desolver for quoting needs
tag := node.Tag
var forceQuoting bool
if tag == "" && node.Kind == ScalarNode {
// Empty tag with quoting style means the string type needs to
// be preserved
if node.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0 {
forceQuoting = true
}
}
switch node.Kind {
case DocumentNode:
event := NewDocumentStartEvent(noVersionDirective, noTagDirective, !s.explicitStart)
event.HeadComment = []byte(node.HeadComment)
s.emit(event)
for _, node := range node.Content {
s.node(node, "")
}
event = NewDocumentEndEvent(!s.explicitEnd)
event.FootComment = []byte(node.FootComment)
s.emit(event)
case SequenceNode:
style := BLOCK_SEQUENCE_STYLE
// Use flow style if explicitly requested or if it's a simple
// collection (scalar-only contents that fit within line width,
// enabled via WithFlowSimpleCollections)
if node.Style&FlowStyle != 0 || s.isSimpleCollection(node) {
style = FLOW_SEQUENCE_STYLE
}
event := NewSequenceStartEvent([]byte(node.Anchor), []byte(longTag(tag)), tag == "", style)
event.HeadComment = []byte(node.HeadComment)
s.emit(event)
for _, node := range node.Content {
s.node(node, "")
}
event = NewSequenceEndEvent()
event.LineComment = []byte(node.LineComment)
event.FootComment = []byte(node.FootComment)
s.emit(event)
case MappingNode:
style := BLOCK_MAPPING_STYLE
// Use flow style if explicitly requested or if it's a simple
// collection (scalar-only contents that fit within line width,
// enabled via WithFlowSimpleCollections)
if node.Style&FlowStyle != 0 || s.isSimpleCollection(node) {
style = FLOW_MAPPING_STYLE
}
event := NewMappingStartEvent([]byte(node.Anchor), []byte(longTag(tag)), tag == "", style)
event.TailComment = []byte(tail)
event.HeadComment = []byte(node.HeadComment)
s.emit(event)
// The tail logic below moves the foot comment of prior keys to
// the following key, since the value for each key may be a
// nested structure and the foot needs to be processed only the
// entirety of the value is streamed. The last tail is
// processed with the mapping end event.
var tail string
for i := 0; i+1 < len(node.Content); i += 2 {
k := node.Content[i]
foot := k.FootComment
if foot != "" {
kopy := *k
kopy.FootComment = ""
k = &kopy
}
s.node(k, tail)
tail = foot
v := node.Content[i+1]
s.node(v, "")
}
event = NewMappingEndEvent()
event.TailComment = []byte(tail)
event.LineComment = []byte(node.LineComment)
event.FootComment = []byte(node.FootComment)
s.emit(event)
case AliasNode:
event := NewAliasEvent([]byte(node.Value))
event.HeadComment = []byte(node.HeadComment)
event.LineComment = []byte(node.LineComment)
event.FootComment = []byte(node.FootComment)
s.emit(event)
case ScalarNode:
value := node.Value
if !utf8.ValidString(value) {
stag := shortTag(tag)
if stag == binaryTag {
failDumpf(SerializerStage, "explicitly tagged !!binary data must be base64-encoded")
}
if stag != "" {
failDumpf(SerializerStage, "cannot marshal invalid UTF-8 data as %s", stag)
}
// It can't be represented directly as YAML so use a binary tag
// and represent it as base64.
tag = binaryTag
value = encodeBase64(value)
}
style := PLAIN_SCALAR_STYLE
switch {
case node.Style&DoubleQuotedStyle != 0:
style = DOUBLE_QUOTED_SCALAR_STYLE
case node.Style&SingleQuotedStyle != 0:
style = SINGLE_QUOTED_SCALAR_STYLE
case node.Style&LiteralStyle != 0:
style = LITERAL_SCALAR_STYLE
case node.Style&FoldedStyle != 0:
style = FOLDED_SCALAR_STYLE
case strings.Contains(value, "\n"):
style = LITERAL_SCALAR_STYLE
case forceQuoting:
style = s.quotePreference.ScalarStyle()
}
s.emitScalar(value, node.Anchor, tag, style, []byte(node.HeadComment), []byte(node.LineComment), []byte(node.FootComment), []byte(tail))
default:
failDumpf(SerializerStage, "cannot represent node with unknown kind %d", node.Kind)
}
}
// emit sends an event to the underlying emitter.
func (s *Serializer) emit(event Event) {
s.must(s.Emitter.Emit(&event))
}
// must panics if the given error is non-nil, routing to the appropriate stage.
func (s *Serializer) must(err error) {
if err == nil {
return
}
var ee EmitterError
if errors.As(err, &ee) {
failDumpf(EmitterStage, "%s", ee.Message)
}
var we WriterError
if errors.As(err, &we) {
// Unwrap to get the original I/O error, stripping the
// "write error: " prefix that WriterError adds internally.
cause := we.Err
if unwrapped := errors.Unwrap(we.Err); unwrapped != nil {
cause = unwrapped
}
failDump(WriterStage, cause)
}
msg := err.Error()
if msg == "" {
msg = fmt.Sprintf("unknown problem generating YAML content with %T", err)
}
failDumpf(SerializerStage, "%s", msg)
}
// emitScalar emits a scalar event with the given value, anchor, tag, style,
// and associated comments.
func (s *Serializer) emitScalar(
value, anchor, tag string, style ScalarStyle, head, line, foot, tail []byte,
) {
implicit := tag == ""
if !implicit {
tag = longTag(tag)
}
event := NewScalarEvent([]byte(anchor), []byte(tag), []byte(value), implicit, implicit, style)
event.HeadComment = head
event.LineComment = line
event.FootComment = foot
event.TailComment = tail
s.emit(event)
}
// isSimpleCollection checks if a node contains only scalar values and would
// fit within the line width when rendered in flow style.
func (s *Serializer) isSimpleCollection(node *Node) bool {
if !s.flowSimpleCollections {
return false
}
if node.Kind != SequenceNode && node.Kind != MappingNode {
return false
}
// Check all children are scalars
for _, child := range node.Content {
if child.Kind != ScalarNode {
return false
}
}
// Estimate flow style length
estimatedLen := s.estimateFlowLength(node)
width := s.lineWidth
if width <= 0 {
width = 80 // Default width if not set
}
return estimatedLen > 0 && estimatedLen <= width
}
// estimateFlowLength estimates the character length of a node in flow style.
func (s *Serializer) estimateFlowLength(node *Node) int {
if node.Kind == SequenceNode {
// [item1, item2, ...] = 2 + sum(len(items)) + 2*(len-1)
length := 2 // []
for i, child := range node.Content {
if i > 0 {
length += 2 // ", "
}
length += len(child.Value)
}
return length
}
if node.Kind == MappingNode {
// {key1: val1, key2: val2} = 2 + sum(key: val) + 2*(pairs-1)
length := 2 // {}
for i := 0; i < len(node.Content); i += 2 {
if i > 0 {
length += 2 // ", "
}
length += len(node.Content[i].Value) + 2 + len(node.Content[i+1].Value) // "key: val"
}
return length
}
return 0
}
// NewStreamStartEvent creates a new STREAM-START event.
func NewStreamStartEvent(encoding Encoding) Event {
return Event{
Type: STREAM_START_EVENT,
encoding: encoding,
}
}
// NewStreamEndEvent creates a new STREAM-END event.
func NewStreamEndEvent() Event {
return Event{
Type: STREAM_END_EVENT,
}
}
// NewDocumentStartEvent creates a new DOCUMENT-START event.
func NewDocumentStartEvent(version_directive *VersionDirective, tag_directives []TagDirective, implicit bool) Event {
return Event{
Type: DOCUMENT_START_EVENT,
versionDirective: version_directive,
tagDirectives: tag_directives,
Implicit: implicit,
}
}
// NewDocumentEndEvent creates a new DOCUMENT-END event.
func NewDocumentEndEvent(implicit bool) Event {
return Event{
Type: DOCUMENT_END_EVENT,
Implicit: implicit,
}
}
// NewAliasEvent creates a new ALIAS event.
func NewAliasEvent(anchor []byte) Event {
return Event{
Type: ALIAS_EVENT,
Anchor: anchor,
}
}
// NewScalarEvent creates a new SCALAR event.
func NewScalarEvent(anchor, tag, value []byte, plain_implicit, quoted_implicit bool, style ScalarStyle) Event {
return Event{
Type: SCALAR_EVENT,
Anchor: anchor,
Tag: tag,
Value: value,
Implicit: plain_implicit,
quoted_implicit: quoted_implicit,
Style: Style(style),
}
}
// NewSequenceStartEvent creates a new SEQUENCE-START event.
func NewSequenceStartEvent(anchor, tag []byte, implicit bool, style SequenceStyle) Event {
return Event{
Type: SEQUENCE_START_EVENT,
Anchor: anchor,
Tag: tag,
Implicit: implicit,
Style: Style(style),
}
}
// NewSequenceEndEvent creates a new SEQUENCE-END event.
func NewSequenceEndEvent() Event {
return Event{
Type: SEQUENCE_END_EVENT,
}
}
// NewMappingStartEvent creates a new MAPPING-START event.
func NewMappingStartEvent(anchor, tag []byte, implicit bool, style MappingStyle) Event {
return Event{
Type: MAPPING_START_EVENT,
Anchor: anchor,
Tag: tag,
Implicit: implicit,
Style: Style(style),
}
}
// NewMappingEndEvent creates a new MAPPING-END event.
func NewMappingEndEvent() Event {
return Event{
Type: MAPPING_END_EVENT,
}
}
// Delete an event object.
func (e *Event) Delete() {
*e = Event{}
}