gitee.com/sy_183/go-common@v1.0.5-0.20231205030221-958cfe129b47/yaml/encode.go (about)

     1  //
     2  // Copyright (c) 2011-2019 Canonical Ltd
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  package yaml
    17  
    18  import (
    19  	"encoding"
    20  	"fmt"
    21  	"io"
    22  	"reflect"
    23  	"regexp"
    24  	"sort"
    25  	"strconv"
    26  	"strings"
    27  	"time"
    28  	"unicode/utf8"
    29  )
    30  
    31  type encoder struct {
    32  	emitter  yaml_emitter_t
    33  	event    yaml_event_t
    34  	out      []byte
    35  	flow     bool
    36  	indent   int
    37  	doneInit bool
    38  }
    39  
    40  func newEncoder() *encoder {
    41  	e := &encoder{}
    42  	yaml_emitter_initialize(&e.emitter)
    43  	yaml_emitter_set_output_string(&e.emitter, &e.out)
    44  	yaml_emitter_set_unicode(&e.emitter, true)
    45  	return e
    46  }
    47  
    48  func newEncoderWithWriter(w io.Writer) *encoder {
    49  	e := &encoder{}
    50  	yaml_emitter_initialize(&e.emitter)
    51  	yaml_emitter_set_output_writer(&e.emitter, w)
    52  	yaml_emitter_set_unicode(&e.emitter, true)
    53  	return e
    54  }
    55  
    56  func (e *encoder) init() {
    57  	if e.doneInit {
    58  		return
    59  	}
    60  	if e.indent == 0 {
    61  		e.indent = 4
    62  	}
    63  	e.emitter.best_indent = e.indent
    64  	yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING)
    65  	e.emit()
    66  	e.doneInit = true
    67  }
    68  
    69  func (e *encoder) finish() {
    70  	e.emitter.open_ended = false
    71  	yaml_stream_end_event_initialize(&e.event)
    72  	e.emit()
    73  }
    74  
    75  func (e *encoder) destroy() {
    76  	yaml_emitter_delete(&e.emitter)
    77  }
    78  
    79  func (e *encoder) emit() {
    80  	// This will internally delete the e.event value.
    81  	e.must(yaml_emitter_emit(&e.emitter, &e.event))
    82  }
    83  
    84  func (e *encoder) must(ok bool) {
    85  	if !ok {
    86  		msg := e.emitter.problem
    87  		if msg == "" {
    88  			msg = "unknown problem generating YAML content"
    89  		}
    90  		failf("%s", msg)
    91  	}
    92  }
    93  
    94  func (e *encoder) marshalDoc(tag string, in reflect.Value) {
    95  	e.init()
    96  	var node *Node
    97  	if in.IsValid() {
    98  		node, _ = in.Interface().(*Node)
    99  	}
   100  	if node != nil && node.Kind == DocumentNode {
   101  		e.nodev(in)
   102  	} else {
   103  		yaml_document_start_event_initialize(&e.event, nil, nil, true)
   104  		e.emit()
   105  		e.marshal(tag, in)
   106  		yaml_document_end_event_initialize(&e.event, true)
   107  		e.emit()
   108  	}
   109  }
   110  
   111  func (e *encoder) marshal(tag string, in reflect.Value) {
   112  	tag = shortTag(tag)
   113  	if !in.IsValid() || in.Kind() == reflect.Ptr && in.IsNil() {
   114  		e.nilv()
   115  		return
   116  	}
   117  	iface := in.Interface()
   118  	switch value := iface.(type) {
   119  	case *Node:
   120  		e.nodev(in)
   121  		return
   122  	case Node:
   123  		if !in.CanAddr() {
   124  			var n = reflect.New(in.Type()).Elem()
   125  			n.Set(in)
   126  			in = n
   127  		}
   128  		e.nodev(in.Addr())
   129  		return
   130  	case time.Time:
   131  		e.timev(tag, in)
   132  		return
   133  	case *time.Time:
   134  		e.timev(tag, in.Elem())
   135  		return
   136  	case time.Duration:
   137  		e.stringv(tag, reflect.ValueOf(value.String()))
   138  		return
   139  	case Marshaler:
   140  		v, err := value.MarshalYAML()
   141  		if err != nil {
   142  			fail(err)
   143  		}
   144  		if v == nil {
   145  			e.nilv()
   146  			return
   147  		}
   148  		e.marshal(tag, reflect.ValueOf(v))
   149  		return
   150  	case encoding.TextMarshaler:
   151  		text, err := value.MarshalText()
   152  		if err != nil {
   153  			fail(err)
   154  		}
   155  		in = reflect.ValueOf(string(text))
   156  	case nil:
   157  		e.nilv()
   158  		return
   159  	}
   160  	switch in.Kind() {
   161  	case reflect.Interface:
   162  		e.marshal(tag, in.Elem())
   163  	case reflect.Map:
   164  		e.mapv(tag, in)
   165  	case reflect.Ptr:
   166  		e.marshal(tag, in.Elem())
   167  	case reflect.Struct:
   168  		e.structv(tag, in)
   169  	case reflect.Slice, reflect.Array:
   170  		e.slicev(tag, in)
   171  	case reflect.String:
   172  		e.stringv(tag, in)
   173  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   174  		e.intv(tag, in)
   175  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
   176  		e.uintv(tag, in)
   177  	case reflect.Float32, reflect.Float64:
   178  		e.floatv(tag, in)
   179  	case reflect.Bool:
   180  		e.boolv(tag, in)
   181  	default:
   182  		panic("cannot marshal type: " + in.Type().String())
   183  	}
   184  }
   185  
   186  func (e *encoder) mapv(tag string, in reflect.Value) {
   187  	e.mappingv(tag, func() {
   188  		keys := keyList(in.MapKeys())
   189  		sort.Sort(keys)
   190  		for _, k := range keys {
   191  			e.marshal("", k)
   192  			e.marshal("", in.MapIndex(k))
   193  		}
   194  	})
   195  }
   196  
   197  func (e *encoder) fieldByIndex(v reflect.Value, index []int) (field reflect.Value) {
   198  	for _, num := range index {
   199  		for {
   200  			if v.Kind() == reflect.Ptr {
   201  				if v.IsNil() {
   202  					return reflect.Value{}
   203  				}
   204  				v = v.Elem()
   205  				continue
   206  			}
   207  			break
   208  		}
   209  		v = v.Field(num)
   210  	}
   211  	return v
   212  }
   213  
   214  func (e *encoder) structv(tag string, in reflect.Value) {
   215  	sinfo, err := getStructInfo(in.Type())
   216  	if err != nil {
   217  		panic(err)
   218  	}
   219  	e.mappingv(tag, func() {
   220  		for _, info := range sinfo.FieldsList {
   221  			var value reflect.Value
   222  			if info.Inline == nil {
   223  				value = in.Field(info.Num)
   224  			} else {
   225  				value = e.fieldByIndex(in, info.Inline)
   226  				if !value.IsValid() {
   227  					continue
   228  				}
   229  			}
   230  			if info.OmitEmpty && isZero(value) {
   231  				continue
   232  			}
   233  			e.marshal("", reflect.ValueOf(info.Key))
   234  			e.flow = info.Flow
   235  			e.marshal("", value)
   236  		}
   237  		if sinfo.InlineMap >= 0 {
   238  			m := in.Field(sinfo.InlineMap)
   239  			if m.Len() > 0 {
   240  				e.flow = false
   241  				keys := keyList(m.MapKeys())
   242  				sort.Sort(keys)
   243  				for _, k := range keys {
   244  					if _, found := sinfo.FieldsMap[k.String()]; found {
   245  						panic(fmt.Sprintf("cannot have key %q in inlined map: conflicts with struct field", k.String()))
   246  					}
   247  					e.marshal("", k)
   248  					e.flow = false
   249  					e.marshal("", m.MapIndex(k))
   250  				}
   251  			}
   252  		}
   253  	})
   254  }
   255  
   256  func (e *encoder) mappingv(tag string, f func()) {
   257  	implicit := tag == ""
   258  	style := yaml_BLOCK_MAPPING_STYLE
   259  	if e.flow {
   260  		e.flow = false
   261  		style = yaml_FLOW_MAPPING_STYLE
   262  	}
   263  	yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)
   264  	e.emit()
   265  	f()
   266  	yaml_mapping_end_event_initialize(&e.event)
   267  	e.emit()
   268  }
   269  
   270  func (e *encoder) slicev(tag string, in reflect.Value) {
   271  	implicit := tag == ""
   272  	style := yaml_BLOCK_SEQUENCE_STYLE
   273  	if e.flow {
   274  		e.flow = false
   275  		style = yaml_FLOW_SEQUENCE_STYLE
   276  	}
   277  	e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style))
   278  	e.emit()
   279  	n := in.Len()
   280  	for i := 0; i < n; i++ {
   281  		e.marshal("", in.Index(i))
   282  	}
   283  	e.must(yaml_sequence_end_event_initialize(&e.event))
   284  	e.emit()
   285  }
   286  
   287  // isBase60 returns whether s is in base 60 notation as defined in YAML 1.1.
   288  //
   289  // The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported
   290  // in YAML 1.2 and by this package, but these should be marshalled quoted for
   291  // the time being for compatibility with other parsers.
   292  func isBase60Float(s string) (result bool) {
   293  	// Fast path.
   294  	if s == "" {
   295  		return false
   296  	}
   297  	c := s[0]
   298  	if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 {
   299  		return false
   300  	}
   301  	// Do the full match.
   302  	return base60float.MatchString(s)
   303  }
   304  
   305  // From http://yaml.org/type/float.html, except the regular expression there
   306  // is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix.
   307  var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`)
   308  
   309  // isOldBool returns whether s is bool notation as defined in YAML 1.1.
   310  //
   311  // We continue to force strings that YAML 1.1 would interpret as booleans to be
   312  // rendered as quotes strings so that the marshalled output valid for YAML 1.1
   313  // parsing.
   314  func isOldBool(s string) (result bool) {
   315  	switch s {
   316  	case "y", "Y", "yes", "Yes", "YES", "on", "On", "ON",
   317  		"n", "N", "no", "No", "NO", "off", "Off", "OFF":
   318  		return true
   319  	default:
   320  		return false
   321  	}
   322  }
   323  
   324  func (e *encoder) stringv(tag string, in reflect.Value) {
   325  	var style yaml_scalar_style_t
   326  	s := in.String()
   327  	canUsePlain := true
   328  	switch {
   329  	case !utf8.ValidString(s):
   330  		if tag == binaryTag {
   331  			failf("explicitly tagged !!binary data must be base64-encoded")
   332  		}
   333  		if tag != "" {
   334  			failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag))
   335  		}
   336  		// It can't be encoded directly as YAML so use a binary tag
   337  		// and encode it as base64.
   338  		tag = binaryTag
   339  		s = encodeBase64(s)
   340  	case tag == "":
   341  		// Check to see if it would resolve to a specific
   342  		// tag when encoded unquoted. If it doesn't,
   343  		// there's no need to quote it.
   344  		rtag, _ := resolve("", s)
   345  		canUsePlain = rtag == strTag && !(isBase60Float(s) || isOldBool(s))
   346  	}
   347  	// Note: it's possible for user code to emit invalid YAML
   348  	// if they explicitly specify a tag and a string containing
   349  	// text that's incompatible with that tag.
   350  	switch {
   351  	case strings.Contains(s, "\n"):
   352  		if e.flow {
   353  			style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
   354  		} else {
   355  			style = yaml_LITERAL_SCALAR_STYLE
   356  		}
   357  	case canUsePlain:
   358  		style = yaml_PLAIN_SCALAR_STYLE
   359  	default:
   360  		style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
   361  	}
   362  	e.emitScalar(s, "", tag, style, nil, nil, nil, nil)
   363  }
   364  
   365  func (e *encoder) boolv(tag string, in reflect.Value) {
   366  	var s string
   367  	if in.Bool() {
   368  		s = "true"
   369  	} else {
   370  		s = "false"
   371  	}
   372  	e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
   373  }
   374  
   375  func (e *encoder) intv(tag string, in reflect.Value) {
   376  	s := strconv.FormatInt(in.Int(), 10)
   377  	e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
   378  }
   379  
   380  func (e *encoder) uintv(tag string, in reflect.Value) {
   381  	s := strconv.FormatUint(in.Uint(), 10)
   382  	e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
   383  }
   384  
   385  func (e *encoder) timev(tag string, in reflect.Value) {
   386  	t := in.Interface().(time.Time)
   387  	s := t.Format(time.RFC3339Nano)
   388  	e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
   389  }
   390  
   391  func (e *encoder) floatv(tag string, in reflect.Value) {
   392  	// Issue #352: When formatting, use the precision of the underlying value
   393  	precision := 64
   394  	if in.Kind() == reflect.Float32 {
   395  		precision = 32
   396  	}
   397  
   398  	s := strconv.FormatFloat(in.Float(), 'g', -1, precision)
   399  	switch s {
   400  	case "+Inf":
   401  		s = ".inf"
   402  	case "-Inf":
   403  		s = "-.inf"
   404  	case "NaN":
   405  		s = ".nan"
   406  	}
   407  	e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
   408  }
   409  
   410  func (e *encoder) nilv() {
   411  	e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
   412  }
   413  
   414  func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t, head, line, foot, tail []byte) {
   415  	// TODO Kill this function. Replace all initialize calls by their underlining Go literals.
   416  	implicit := tag == ""
   417  	if !implicit {
   418  		tag = longTag(tag)
   419  	}
   420  	e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style))
   421  	e.event.head_comment = head
   422  	e.event.line_comment = line
   423  	e.event.foot_comment = foot
   424  	e.event.tail_comment = tail
   425  	e.emit()
   426  }
   427  
   428  func (e *encoder) nodev(in reflect.Value) {
   429  	e.node(in.Interface().(*Node), "")
   430  }
   431  
   432  func (e *encoder) node(node *Node, tail string) {
   433  	// Zero nodes behave as nil.
   434  	if node.Kind == 0 && node.IsZero() {
   435  		e.nilv()
   436  		return
   437  	}
   438  
   439  	// If the tag was not explicitly requested, and dropping it won't change the
   440  	// implicit tag of the value, don't include it in the presentation.
   441  	var tag = node.Tag
   442  	var stag = shortTag(tag)
   443  	var forceQuoting bool
   444  	if tag != "" && node.Style&TaggedStyle == 0 {
   445  		if node.Kind == ScalarNode {
   446  			if stag == strTag && node.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0 {
   447  				tag = ""
   448  			} else {
   449  				rtag, _ := resolve("", node.Value)
   450  				if rtag == stag {
   451  					tag = ""
   452  				} else if stag == strTag {
   453  					tag = ""
   454  					forceQuoting = true
   455  				}
   456  			}
   457  		} else {
   458  			var rtag string
   459  			switch node.Kind {
   460  			case MappingNode:
   461  				rtag = mapTag
   462  			case SequenceNode:
   463  				rtag = seqTag
   464  			}
   465  			if rtag == stag {
   466  				tag = ""
   467  			}
   468  		}
   469  	}
   470  
   471  	switch node.Kind {
   472  	case DocumentNode:
   473  		yaml_document_start_event_initialize(&e.event, nil, nil, true)
   474  		e.event.head_comment = []byte(node.HeadComment)
   475  		e.emit()
   476  		for _, node := range node.Content {
   477  			e.node(node, "")
   478  		}
   479  		yaml_document_end_event_initialize(&e.event, true)
   480  		e.event.foot_comment = []byte(node.FootComment)
   481  		e.emit()
   482  
   483  	case SequenceNode:
   484  		style := yaml_BLOCK_SEQUENCE_STYLE
   485  		if node.Style&FlowStyle != 0 {
   486  			style = yaml_FLOW_SEQUENCE_STYLE
   487  		}
   488  		e.must(yaml_sequence_start_event_initialize(&e.event, []byte(node.Anchor), []byte(longTag(tag)), tag == "", style))
   489  		e.event.head_comment = []byte(node.HeadComment)
   490  		e.emit()
   491  		for _, node := range node.Content {
   492  			e.node(node, "")
   493  		}
   494  		e.must(yaml_sequence_end_event_initialize(&e.event))
   495  		e.event.line_comment = []byte(node.LineComment)
   496  		e.event.foot_comment = []byte(node.FootComment)
   497  		e.emit()
   498  
   499  	case MappingNode:
   500  		style := yaml_BLOCK_MAPPING_STYLE
   501  		if node.Style&FlowStyle != 0 {
   502  			style = yaml_FLOW_MAPPING_STYLE
   503  		}
   504  		yaml_mapping_start_event_initialize(&e.event, []byte(node.Anchor), []byte(longTag(tag)), tag == "", style)
   505  		e.event.tail_comment = []byte(tail)
   506  		e.event.head_comment = []byte(node.HeadComment)
   507  		e.emit()
   508  
   509  		// The tail logic below moves the foot comment of prior keys to the following key,
   510  		// since the value for each key may be a nested structure and the foot needs to be
   511  		// processed only the entirety of the value is streamed. The last tail is processed
   512  		// with the mapping end event.
   513  		var tail string
   514  		for i := 0; i+1 < len(node.Content); i += 2 {
   515  			k := node.Content[i]
   516  			foot := k.FootComment
   517  			if foot != "" {
   518  				kopy := *k
   519  				kopy.FootComment = ""
   520  				k = &kopy
   521  			}
   522  			e.node(k, tail)
   523  			tail = foot
   524  
   525  			v := node.Content[i+1]
   526  			e.node(v, "")
   527  		}
   528  
   529  		yaml_mapping_end_event_initialize(&e.event)
   530  		e.event.tail_comment = []byte(tail)
   531  		e.event.line_comment = []byte(node.LineComment)
   532  		e.event.foot_comment = []byte(node.FootComment)
   533  		e.emit()
   534  
   535  	case AliasNode:
   536  		yaml_alias_event_initialize(&e.event, []byte(node.Value))
   537  		e.event.head_comment = []byte(node.HeadComment)
   538  		e.event.line_comment = []byte(node.LineComment)
   539  		e.event.foot_comment = []byte(node.FootComment)
   540  		e.emit()
   541  
   542  	case ScalarNode:
   543  		value := node.Value
   544  		if !utf8.ValidString(value) {
   545  			if stag == binaryTag {
   546  				failf("explicitly tagged !!binary data must be base64-encoded")
   547  			}
   548  			if stag != "" {
   549  				failf("cannot marshal invalid UTF-8 data as %s", stag)
   550  			}
   551  			// It can't be encoded directly as YAML so use a binary tag
   552  			// and encode it as base64.
   553  			tag = binaryTag
   554  			value = encodeBase64(value)
   555  		}
   556  
   557  		style := yaml_PLAIN_SCALAR_STYLE
   558  		switch {
   559  		case node.Style&DoubleQuotedStyle != 0:
   560  			style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
   561  		case node.Style&SingleQuotedStyle != 0:
   562  			style = yaml_SINGLE_QUOTED_SCALAR_STYLE
   563  		case node.Style&LiteralStyle != 0:
   564  			style = yaml_LITERAL_SCALAR_STYLE
   565  		case node.Style&FoldedStyle != 0:
   566  			style = yaml_FOLDED_SCALAR_STYLE
   567  		case strings.Contains(value, "\n"):
   568  			style = yaml_LITERAL_SCALAR_STYLE
   569  		case forceQuoting:
   570  			style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
   571  		}
   572  
   573  		e.emitScalar(value, node.Anchor, tag, style, []byte(node.HeadComment), []byte(node.LineComment), []byte(node.FootComment), []byte(tail))
   574  	default:
   575  		failf("cannot encode node with unknown kind %d", node.Kind)
   576  	}
   577  }