Upstream mikefarah/yq state of the touched files before 54b19a577e
This commit is contained in:
1 parent
472a5336be
commit
d4883e8aa2
2 files changed
+1176
No files matched your search
@@ -0,0 +1,335 @@
|
|||||||
|
//go:build !yq_noxml
|
||||||
|
|
||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type xmlEncoder struct {
|
||||||
|
indentString string
|
||||||
|
writer io.Writer
|
||||||
|
prefs XmlPreferences
|
||||||
|
leadingContent string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewXMLEncoder(prefs XmlPreferences) Encoder {
|
||||||
|
var indentString = ""
|
||||||
|
|
||||||
|
for index := 0; index < prefs.Indent; index++ {
|
||||||
|
indentString = indentString + " "
|
||||||
|
}
|
||||||
|
return &xmlEncoder{indentString, nil, prefs, ""}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *xmlEncoder) CanHandleAliases() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *xmlEncoder) PrintDocumentSeparator(_ io.Writer) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *xmlEncoder) PrintLeadingContent(_ io.Writer, content string) error {
|
||||||
|
e.leadingContent = content
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *xmlEncoder) Encode(writer io.Writer, node *CandidateNode) error {
|
||||||
|
encoder := xml.NewEncoder(writer)
|
||||||
|
// hack so we can manually add newlines to procInst and directives
|
||||||
|
e.writer = writer
|
||||||
|
encoder.Indent("", e.indentString)
|
||||||
|
var newLine xml.CharData = []byte("\n")
|
||||||
|
|
||||||
|
if node.Tag == "!!map" {
|
||||||
|
// make sure <?xml .. ?> processing instructions are encoded first
|
||||||
|
for i := 0; i < len(node.Content); i += 2 {
|
||||||
|
key := node.Content[i]
|
||||||
|
value := node.Content[i+1]
|
||||||
|
|
||||||
|
if key.Value == (e.prefs.ProcInstPrefix + "xml") {
|
||||||
|
name := strings.Replace(key.Value, e.prefs.ProcInstPrefix, "", 1)
|
||||||
|
procInst := xml.ProcInst{Target: name, Inst: []byte(value.Value)}
|
||||||
|
if err := encoder.EncodeToken(procInst); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := e.writer.Write([]byte("\n")); err != nil {
|
||||||
|
log.Warningf("Unable to write newline, skipping: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.leadingContent != "" {
|
||||||
|
|
||||||
|
// remove first and last newlines if present
|
||||||
|
err := e.encodeComment(encoder, e.leadingContent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = encoder.EncodeToken(newLine)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch node.Kind {
|
||||||
|
case MappingNode:
|
||||||
|
err := e.encodeTopLevelMap(encoder, node)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case ScalarNode:
|
||||||
|
var charData xml.CharData = []byte(node.Value)
|
||||||
|
err := encoder.EncodeToken(charData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return encoder.Flush()
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("cannot encode %v to XML - only maps can be encoded", node.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
return encoder.EncodeToken(newLine)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *xmlEncoder) encodeTopLevelMap(encoder *xml.Encoder, node *CandidateNode) error {
|
||||||
|
err := e.encodeComment(encoder, headAndLineComment(node))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := 0; i < len(node.Content); i += 2 {
|
||||||
|
key := node.Content[i]
|
||||||
|
value := node.Content[i+1]
|
||||||
|
|
||||||
|
start := xml.StartElement{Name: xml.Name{Local: key.Value}}
|
||||||
|
log.Debugf("comments of key %v", key.Value)
|
||||||
|
err := e.encodeComment(encoder, headAndLineComment(key))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if headAndLineComment(key) != "" {
|
||||||
|
var newLine xml.CharData = []byte("\n")
|
||||||
|
err = encoder.EncodeToken(newLine)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if key.Value == (e.prefs.ProcInstPrefix + "xml") { //nolint
|
||||||
|
// dont double process these.
|
||||||
|
} else if strings.HasPrefix(key.Value, e.prefs.ProcInstPrefix) {
|
||||||
|
name := strings.Replace(key.Value, e.prefs.ProcInstPrefix, "", 1)
|
||||||
|
procInst := xml.ProcInst{Target: name, Inst: []byte(value.Value)}
|
||||||
|
if err := encoder.EncodeToken(procInst); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := e.writer.Write([]byte("\n")); err != nil {
|
||||||
|
log.Warningf("Unable to write newline, skipping: %v", err)
|
||||||
|
}
|
||||||
|
} else if key.Value == e.prefs.DirectiveName {
|
||||||
|
var directive xml.Directive = []byte(value.Value)
|
||||||
|
if err := encoder.EncodeToken(directive); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := e.writer.Write([]byte("\n")); err != nil {
|
||||||
|
log.Warningf("Unable to write newline, skipping: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
log.Debugf("recursing")
|
||||||
|
|
||||||
|
err = e.doEncode(encoder, value, start)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = e.encodeComment(encoder, footComment(key))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e.encodeComment(encoder, footComment(node))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *xmlEncoder) encodeStart(encoder *xml.Encoder, node *CandidateNode, start xml.StartElement) error {
|
||||||
|
err := encoder.EncodeToken(start)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.encodeComment(encoder, headComment(node))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *xmlEncoder) encodeEnd(encoder *xml.Encoder, node *CandidateNode, start xml.StartElement) error {
|
||||||
|
err := encoder.EncodeToken(start.End())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.encodeComment(encoder, footComment(node))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *xmlEncoder) doEncode(encoder *xml.Encoder, node *CandidateNode, start xml.StartElement) error {
|
||||||
|
switch node.Kind {
|
||||||
|
case MappingNode:
|
||||||
|
return e.encodeMap(encoder, node, start)
|
||||||
|
case SequenceNode:
|
||||||
|
return e.encodeArray(encoder, node, start)
|
||||||
|
case ScalarNode:
|
||||||
|
err := e.encodeStart(encoder, node, start)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var charData xml.CharData = []byte(node.Value)
|
||||||
|
err = encoder.EncodeToken(charData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = e.encodeComment(encoder, lineComment(node)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.encodeEnd(encoder, node, start)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("unsupported type %v", node.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xmlEncodeMultilineCommentRegex = regexp.MustCompile(`(^|\n) *# ?(.*)`)
|
||||||
|
var xmlEncodeSingleLineCommentRegex = regexp.MustCompile(`^\s*#(.*)\n?`)
|
||||||
|
var chompRegexp = regexp.MustCompile(`\n$`)
|
||||||
|
|
||||||
|
func (e *xmlEncoder) encodeComment(encoder *xml.Encoder, commentStr string) error {
|
||||||
|
if commentStr != "" {
|
||||||
|
log.Debugf("got comment [%v]", commentStr)
|
||||||
|
// multi line string
|
||||||
|
if len(commentStr) > 2 && strings.Contains(commentStr[1:len(commentStr)-1], "\n") {
|
||||||
|
commentStr = chompRegexp.ReplaceAllString(commentStr, "")
|
||||||
|
log.Debugf("chompRegexp [%v]", commentStr)
|
||||||
|
commentStr = xmlEncodeMultilineCommentRegex.ReplaceAllString(commentStr, "$1$2")
|
||||||
|
log.Debugf("processed multiline [%v]", commentStr)
|
||||||
|
// if the first line is non blank, add a space
|
||||||
|
if commentStr[0] != '\n' && commentStr[0] != ' ' {
|
||||||
|
commentStr = " " + commentStr
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
commentStr = xmlEncodeSingleLineCommentRegex.ReplaceAllString(commentStr, "$1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasSuffix(commentStr, " ") && !strings.HasSuffix(commentStr, "\n") {
|
||||||
|
commentStr = commentStr + " "
|
||||||
|
log.Debugf("added suffix [%v]", commentStr)
|
||||||
|
}
|
||||||
|
log.Debugf("encoding comment [%v]", commentStr)
|
||||||
|
|
||||||
|
var comment xml.Comment = []byte(commentStr)
|
||||||
|
err := encoder.EncodeToken(comment)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *xmlEncoder) encodeArray(encoder *xml.Encoder, node *CandidateNode, start xml.StartElement) error {
|
||||||
|
|
||||||
|
if err := e.encodeComment(encoder, headAndLineComment(node)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(node.Content); i++ {
|
||||||
|
value := node.Content[i]
|
||||||
|
if err := e.doEncode(encoder, value, start.Copy()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e.encodeComment(encoder, footComment(node))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *xmlEncoder) isAttribute(name string) bool {
|
||||||
|
return strings.HasPrefix(name, e.prefs.AttributePrefix) &&
|
||||||
|
name != e.prefs.ContentName &&
|
||||||
|
name != e.prefs.DirectiveName &&
|
||||||
|
!strings.HasPrefix(name, e.prefs.ProcInstPrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *xmlEncoder) encodeMap(encoder *xml.Encoder, node *CandidateNode, start xml.StartElement) error {
|
||||||
|
log.Debug("its a map")
|
||||||
|
|
||||||
|
//first find all the attributes and put them on the start token
|
||||||
|
for i := 0; i < len(node.Content); i += 2 {
|
||||||
|
key := node.Content[i]
|
||||||
|
value := node.Content[i+1]
|
||||||
|
|
||||||
|
if e.isAttribute(key.Value) {
|
||||||
|
if value.Kind == ScalarNode {
|
||||||
|
attributeName := strings.Replace(key.Value, e.prefs.AttributePrefix, "", 1)
|
||||||
|
start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: attributeName}, Value: value.Value})
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("cannot use %v as attribute, only scalars are supported", value.Tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := e.encodeStart(encoder, node, start)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//now we encode non attribute tokens
|
||||||
|
for i := 0; i < len(node.Content); i += 2 {
|
||||||
|
key := node.Content[i]
|
||||||
|
value := node.Content[i+1]
|
||||||
|
|
||||||
|
err := e.encodeComment(encoder, headAndLineComment(key))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(key.Value, e.prefs.ProcInstPrefix) {
|
||||||
|
name := strings.Replace(key.Value, e.prefs.ProcInstPrefix, "", 1)
|
||||||
|
procInst := xml.ProcInst{Target: name, Inst: []byte(value.Value)}
|
||||||
|
if err := encoder.EncodeToken(procInst); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if key.Value == e.prefs.DirectiveName {
|
||||||
|
var directive xml.Directive = []byte(value.Value)
|
||||||
|
if err := encoder.EncodeToken(directive); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if key.Value == e.prefs.ContentName {
|
||||||
|
// directly encode the contents
|
||||||
|
err = e.encodeComment(encoder, headAndLineComment(value))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var charData xml.CharData = []byte(value.Value)
|
||||||
|
err = encoder.EncodeToken(charData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = e.encodeComment(encoder, footComment(value))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if !e.isAttribute(key.Value) {
|
||||||
|
start := xml.StartElement{Name: xml.Name{Local: key.Value}}
|
||||||
|
err := e.doEncode(encoder, value, start)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = e.encodeComment(encoder, footComment(key))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.encodeEnd(encoder, node, start)
|
||||||
|
}
|
||||||
@@ -0,0 +1,841 @@
|
|||||||
|
//go:build !yq_noxml
|
||||||
|
|
||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mikefarah/yq/v4/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
const yamlInputWithProcInstAndHeadComment = `# cats
|
||||||
|
+p_xml: version="1.0"
|
||||||
|
this: is some xml`
|
||||||
|
|
||||||
|
const expectedXmlProcInstAndHeadComment = `<?xml version="1.0"?>
|
||||||
|
<!-- cats -->
|
||||||
|
<this>is some xml</this>
|
||||||
|
`
|
||||||
|
|
||||||
|
const xmlProcInstAndHeadCommentBlock = `<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
cats
|
||||||
|
-->
|
||||||
|
<this>is some xml</this>
|
||||||
|
`
|
||||||
|
|
||||||
|
const expectedYamlProcInstAndHeadCommentBlock = `#
|
||||||
|
# cats
|
||||||
|
#
|
||||||
|
+p_xml: version="1.0"
|
||||||
|
this: is some xml
|
||||||
|
`
|
||||||
|
|
||||||
|
const inputXMLWithComments = `
|
||||||
|
<!-- before cat -->
|
||||||
|
<cat>
|
||||||
|
<!-- in cat before -->
|
||||||
|
<x>3<!-- multi
|
||||||
|
line comment
|
||||||
|
for x --></x>
|
||||||
|
<!-- before y -->
|
||||||
|
<y>
|
||||||
|
<!-- in y before -->
|
||||||
|
<d><!-- in d before -->z<!-- in d after --></d>
|
||||||
|
|
||||||
|
<!-- in y after -->
|
||||||
|
</y>
|
||||||
|
<!-- in_cat_after -->
|
||||||
|
</cat>
|
||||||
|
<!-- after cat -->
|
||||||
|
`
|
||||||
|
const inputXMLWithCommentsWithSubChild = `
|
||||||
|
<!-- before cat -->
|
||||||
|
<cat>
|
||||||
|
<!-- in cat before -->
|
||||||
|
<x>3<!-- multi
|
||||||
|
line comment
|
||||||
|
for x --></x>
|
||||||
|
<!-- before y -->
|
||||||
|
<y>
|
||||||
|
<!-- in y before -->
|
||||||
|
<d><!-- in d before --><z sweet="cool"/><!-- in d after --></d>
|
||||||
|
|
||||||
|
<!-- in y after -->
|
||||||
|
</y>
|
||||||
|
<!-- in_cat_after -->
|
||||||
|
</cat>
|
||||||
|
<!-- after cat -->
|
||||||
|
`
|
||||||
|
|
||||||
|
const expectedDecodeYamlWithSubChild = `# before cat
|
||||||
|
cat:
|
||||||
|
# in cat before
|
||||||
|
x: "3" # multi
|
||||||
|
# line comment
|
||||||
|
# for x
|
||||||
|
# before y
|
||||||
|
|
||||||
|
y:
|
||||||
|
# in y before
|
||||||
|
d:
|
||||||
|
# in d before
|
||||||
|
z:
|
||||||
|
+@sweet: cool
|
||||||
|
# in d after
|
||||||
|
# in y after
|
||||||
|
# in_cat_after
|
||||||
|
# after cat
|
||||||
|
`
|
||||||
|
|
||||||
|
const inputXMLWithCommentsWithArray = `
|
||||||
|
<!-- before cat -->
|
||||||
|
<cat>
|
||||||
|
<!-- in cat before -->
|
||||||
|
<x>3<!-- multi
|
||||||
|
line comment
|
||||||
|
for x --></x>
|
||||||
|
<!-- before y -->
|
||||||
|
<y>
|
||||||
|
<!-- in y before -->
|
||||||
|
<d><!-- in d before --><z sweet="cool"/><!-- in d after --></d>
|
||||||
|
<d><!-- in d2 before --><z sweet="cool2"/><!-- in d2 after --></d>
|
||||||
|
|
||||||
|
<!-- in y after -->
|
||||||
|
</y>
|
||||||
|
<!-- in_cat_after -->
|
||||||
|
</cat>
|
||||||
|
<!-- after cat -->
|
||||||
|
`
|
||||||
|
|
||||||
|
const expectedDecodeYamlWithArray = `# before cat
|
||||||
|
cat:
|
||||||
|
# in cat before
|
||||||
|
x: "3" # multi
|
||||||
|
# line comment
|
||||||
|
# for x
|
||||||
|
# before y
|
||||||
|
|
||||||
|
y:
|
||||||
|
# in y before
|
||||||
|
d:
|
||||||
|
- # in d before
|
||||||
|
z:
|
||||||
|
+@sweet: cool
|
||||||
|
# in d after
|
||||||
|
- # in d2 before
|
||||||
|
z:
|
||||||
|
+@sweet: cool2
|
||||||
|
# in d2 after
|
||||||
|
# in y after
|
||||||
|
# in_cat_after
|
||||||
|
# after cat
|
||||||
|
`
|
||||||
|
|
||||||
|
const expectedDecodeYamlWithComments = `# before cat
|
||||||
|
cat:
|
||||||
|
# in cat before
|
||||||
|
x: "3" # multi
|
||||||
|
# line comment
|
||||||
|
# for x
|
||||||
|
# before y
|
||||||
|
|
||||||
|
y:
|
||||||
|
# in y before
|
||||||
|
# in d before
|
||||||
|
d: z # in d after
|
||||||
|
# in y after
|
||||||
|
# in_cat_after
|
||||||
|
# after cat
|
||||||
|
`
|
||||||
|
|
||||||
|
const expectedRoundtripXMLWithComments = `<!-- before cat -->
|
||||||
|
<cat><!-- in cat before -->
|
||||||
|
<x>3<!-- multi
|
||||||
|
line comment
|
||||||
|
for x --></x><!-- before y -->
|
||||||
|
<y><!-- in y before
|
||||||
|
in d before -->
|
||||||
|
<d>z<!-- in d after --></d><!-- in y after -->
|
||||||
|
</y><!-- in_cat_after -->
|
||||||
|
</cat><!-- after cat -->
|
||||||
|
`
|
||||||
|
|
||||||
|
const yamlWithComments = `#
|
||||||
|
# header comment
|
||||||
|
# above_cat
|
||||||
|
#
|
||||||
|
cat: # inline_cat
|
||||||
|
# above_array
|
||||||
|
array: # inline_array
|
||||||
|
- val1 # inline_val1
|
||||||
|
# above_val2
|
||||||
|
- val2 # inline_val2
|
||||||
|
# below_cat
|
||||||
|
`
|
||||||
|
|
||||||
|
const expectedXMLWithComments = `<!--
|
||||||
|
header comment
|
||||||
|
above_cat
|
||||||
|
-->
|
||||||
|
<!-- inline_cat -->
|
||||||
|
<cat><!-- above_array inline_array -->
|
||||||
|
<array>val1<!-- inline_val1 --></array>
|
||||||
|
<array><!-- above_val2 -->val2<!-- inline_val2 --></array>
|
||||||
|
</cat><!-- below_cat -->
|
||||||
|
`
|
||||||
|
|
||||||
|
const inputXMLWithNamespacedAttr = `<?xml version="1.0"?>
|
||||||
|
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url">
|
||||||
|
<item foo="bar">baz</item>
|
||||||
|
<xsi:item>foobar</xsi:item>
|
||||||
|
</map>
|
||||||
|
`
|
||||||
|
|
||||||
|
const expectedYAMLWithNamespacedAttr = `+p_xml: version="1.0"
|
||||||
|
map:
|
||||||
|
+@xmlns: some-namespace
|
||||||
|
+@xmlns:xsi: some-instance
|
||||||
|
+@xsi:schemaLocation: some-url
|
||||||
|
item:
|
||||||
|
+content: baz
|
||||||
|
+@foo: bar
|
||||||
|
xsi:item: foobar
|
||||||
|
`
|
||||||
|
|
||||||
|
const expectedYAMLWithRawNamespacedAttr = `+p_xml: version="1.0"
|
||||||
|
map:
|
||||||
|
+@xmlns: some-namespace
|
||||||
|
+@xmlns:xsi: some-instance
|
||||||
|
+@xsi:schemaLocation: some-url
|
||||||
|
item:
|
||||||
|
+content: baz
|
||||||
|
+@foo: bar
|
||||||
|
xsi:item: foobar
|
||||||
|
`
|
||||||
|
|
||||||
|
const expectedYAMLWithoutRawNamespacedAttr = `+p_xml: version="1.0"
|
||||||
|
some-namespace:map:
|
||||||
|
+@xmlns: some-namespace
|
||||||
|
+@xmlns:xsi: some-instance
|
||||||
|
+@some-instance:schemaLocation: some-url
|
||||||
|
some-namespace:item:
|
||||||
|
+content: baz
|
||||||
|
+@foo: bar
|
||||||
|
some-instance:item: foobar
|
||||||
|
`
|
||||||
|
|
||||||
|
const xmlWithCustomDtd = `
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE root [
|
||||||
|
<!ENTITY writer "Blah.">
|
||||||
|
<!ENTITY copyright "Blah">
|
||||||
|
]>
|
||||||
|
<root>
|
||||||
|
<item>&writer;©right;</item>
|
||||||
|
</root>`
|
||||||
|
|
||||||
|
const expectedDtd = `<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE root [
|
||||||
|
<!ENTITY writer "Blah.">
|
||||||
|
<!ENTITY copyright "Blah">
|
||||||
|
]>
|
||||||
|
<root>
|
||||||
|
<item>&writer;&copyright;</item>
|
||||||
|
</root>
|
||||||
|
`
|
||||||
|
|
||||||
|
const expectedSkippedDtd = `<?xml version="1.0"?>
|
||||||
|
<root>
|
||||||
|
<item>&writer;&copyright;</item>
|
||||||
|
</root>
|
||||||
|
`
|
||||||
|
|
||||||
|
const xmlWithProcInstAndDirectives = `<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE config SYSTEM "/etc/iwatch/iwatch.dtd" >
|
||||||
|
<apple>
|
||||||
|
<?coolioo version="1.0"?>
|
||||||
|
<!CATYPE meow purr puss >
|
||||||
|
<b>things</b>
|
||||||
|
</apple>
|
||||||
|
`
|
||||||
|
|
||||||
|
const yamlWithProcInstAndDirectives = `+p_xml: version="1.0"
|
||||||
|
+directive: 'DOCTYPE config SYSTEM "/etc/iwatch/iwatch.dtd" '
|
||||||
|
apple:
|
||||||
|
+p_coolioo: version="1.0"
|
||||||
|
+directive: 'CATYPE meow purr puss '
|
||||||
|
b: things
|
||||||
|
`
|
||||||
|
|
||||||
|
const expectedXmlWithProcInstAndDirectives = `<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE config SYSTEM "/etc/iwatch/iwatch.dtd" >
|
||||||
|
<apple><?coolioo version="1.0"?><!CATYPE meow purr puss >
|
||||||
|
<b>things</b>
|
||||||
|
</apple>
|
||||||
|
`
|
||||||
|
|
||||||
|
var xmlScenarios = []formatScenario{
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
description: "bad xml",
|
||||||
|
input: `<?xml version="1.0" encoding="UTF-8"?></Child></Root>`,
|
||||||
|
expected: "+p_xml: version=\"1.0\" encoding=\"UTF-8\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
input: " <root>value<!-- comment--> </root>",
|
||||||
|
expected: "root: value # comment\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
input: "value<root>value</root>",
|
||||||
|
expectedError: "bad file 'sample.yml': invalid XML: Encountered chardata [value] outside of XML node",
|
||||||
|
scenarioType: "decode-error",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
input: "<root><!-- comment-->value</root>",
|
||||||
|
expected: "# comment\nroot: value\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
input: "<root> <!-- comment--></root>",
|
||||||
|
expected: "root: # comment\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
input: "<root>value<!-- comment-->anotherValue </root>",
|
||||||
|
expected: "root:\n # comment\n - value\n - anotherValue\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
input: "<root><cats><cat>quick</cat><cat>soft</cat><!-- kitty_comment--><cat>squishy</cat></cats></root>",
|
||||||
|
expected: "root:\n cats:\n cat:\n - quick\n - soft\n # kitty_comment\n\n - squishy\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "ProcInst with head comment",
|
||||||
|
skipDoc: true,
|
||||||
|
input: yamlInputWithProcInstAndHeadComment,
|
||||||
|
expected: expectedXmlProcInstAndHeadComment,
|
||||||
|
scenarioType: "encode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Scalar roundtrip",
|
||||||
|
skipDoc: true,
|
||||||
|
input: "<mike>cat</mike>",
|
||||||
|
expression: ".mike",
|
||||||
|
expected: "cat",
|
||||||
|
scenarioType: "roundtrip",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "ProcInst with head comment round trip",
|
||||||
|
skipDoc: true,
|
||||||
|
input: expectedXmlProcInstAndHeadComment,
|
||||||
|
expected: expectedXmlProcInstAndHeadComment,
|
||||||
|
scenarioType: "roundtrip",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "ProcInst with block head comment to yaml",
|
||||||
|
skipDoc: true,
|
||||||
|
input: xmlProcInstAndHeadCommentBlock,
|
||||||
|
expected: expectedYamlProcInstAndHeadCommentBlock,
|
||||||
|
scenarioType: "decode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "ProcInst with block head comment from yaml",
|
||||||
|
skipDoc: true,
|
||||||
|
input: expectedYamlProcInstAndHeadCommentBlock,
|
||||||
|
expected: xmlProcInstAndHeadCommentBlock,
|
||||||
|
scenarioType: "encode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "ProcInst with head comment round trip block",
|
||||||
|
skipDoc: true,
|
||||||
|
input: xmlProcInstAndHeadCommentBlock,
|
||||||
|
expected: xmlProcInstAndHeadCommentBlock,
|
||||||
|
scenarioType: "roundtrip",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Parse xml: simple",
|
||||||
|
subdescription: "Notice how all the values are strings, see the next example on how you can fix that.",
|
||||||
|
input: "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<cat>\n <says>meow</says>\n <legs>4</legs>\n <cute>true</cute>\n</cat>",
|
||||||
|
expected: "+p_xml: version=\"1.0\" encoding=\"UTF-8\"\ncat:\n says: meow\n legs: \"4\"\n cute: \"true\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Parse xml: number",
|
||||||
|
subdescription: "All values are assumed to be strings when parsing XML, but you can use the `from_yaml` operator on all the strings values to autoparse into the correct type.",
|
||||||
|
input: "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<cat>\n <says>meow</says>\n <legs>4</legs>\n <cute>true</cute>\n</cat>",
|
||||||
|
expression: " (.. | select(tag == \"!!str\")) |= from_yaml",
|
||||||
|
expected: "+p_xml: version=\"1.0\" encoding=\"UTF-8\"\ncat:\n says: meow\n legs: 4\n cute: true\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Parse xml: array",
|
||||||
|
subdescription: "Consecutive nodes with identical xml names are assumed to be arrays.",
|
||||||
|
input: "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<animal>cat</animal>\n<animal>goat</animal>",
|
||||||
|
expected: "+p_xml: version=\"1.0\" encoding=\"UTF-8\"\nanimal:\n - cat\n - goat\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Parse xml: force as an array",
|
||||||
|
subdescription: "In XML, if your array has a single item, then yq doesn't know its an array. This is how you can consistently force it to be an array. This handles the 3 scenarios of having nothing in the array, having a single item and having multiple.",
|
||||||
|
input: "<zoo><animal>cat</animal></zoo>",
|
||||||
|
expression: ".zoo.animal |= ([] + .)",
|
||||||
|
expected: "zoo:\n animal:\n - cat\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Parse xml: force all as an array",
|
||||||
|
input: "<zoo><thing><frog>boing</frog></thing></zoo>",
|
||||||
|
expression: ".. |= [] + .",
|
||||||
|
expected: "- zoo:\n - thing:\n - frog:\n - boing\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Parse xml: attributes",
|
||||||
|
subdescription: "Attributes are converted to fields, with the default attribute prefix '+'. Use '--xml-attribute-prefix` to set your own.",
|
||||||
|
input: "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<cat legs=\"4\">\n <legs>7</legs>\n</cat>",
|
||||||
|
expected: "+p_xml: version=\"1.0\" encoding=\"UTF-8\"\ncat:\n +@legs: \"4\"\n legs: \"7\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Parse xml: attributes with content",
|
||||||
|
subdescription: "Content is added as a field, using the default content name of `+content`. Use `--xml-content-name` to set your own.",
|
||||||
|
input: "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<cat legs=\"4\">meow</cat>",
|
||||||
|
expected: "+p_xml: version=\"1.0\" encoding=\"UTF-8\"\ncat:\n +content: meow\n +@legs: \"4\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Parse xml: content split between comments/children",
|
||||||
|
subdescription: "Multiple content texts are collected into a sequence.",
|
||||||
|
input: "<root> value <!-- comment-->anotherValue <a>frog</a> cool!</root>",
|
||||||
|
expected: "root:\n +content: # comment\n - value\n - anotherValue\n - cool!\n a: frog\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Parse xml: custom dtd",
|
||||||
|
subdescription: "DTD entities are processed as directives.",
|
||||||
|
input: xmlWithCustomDtd,
|
||||||
|
expected: expectedDtd,
|
||||||
|
scenarioType: "roundtrip",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Roundtrip with name spaced attributes",
|
||||||
|
skipDoc: true,
|
||||||
|
input: inputXMLWithNamespacedAttr,
|
||||||
|
expected: inputXMLWithNamespacedAttr,
|
||||||
|
scenarioType: "roundtrip",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Parse xml: skip custom dtd",
|
||||||
|
subdescription: "DTDs are directives, skip over directives to skip DTDs.",
|
||||||
|
input: xmlWithCustomDtd,
|
||||||
|
expected: expectedSkippedDtd,
|
||||||
|
scenarioType: "roundtrip-skip-directives",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Parse xml: with comments",
|
||||||
|
subdescription: "A best attempt is made to preserve comments.",
|
||||||
|
input: inputXMLWithComments,
|
||||||
|
expected: expectedDecodeYamlWithComments,
|
||||||
|
scenarioType: "decode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Empty doc",
|
||||||
|
skipDoc: true,
|
||||||
|
input: "",
|
||||||
|
expected: "\n",
|
||||||
|
scenarioType: "decode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Empty single node",
|
||||||
|
skipDoc: true,
|
||||||
|
input: "<a/>",
|
||||||
|
expected: "a:\n",
|
||||||
|
scenarioType: "decode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Empty close node",
|
||||||
|
skipDoc: true,
|
||||||
|
input: "<a></a>",
|
||||||
|
expected: "a:\n",
|
||||||
|
scenarioType: "decode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Nested empty",
|
||||||
|
skipDoc: true,
|
||||||
|
input: "<a><b/></a>",
|
||||||
|
expected: "a:\n b:\n",
|
||||||
|
scenarioType: "decode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Parse xml: with comments subchild",
|
||||||
|
skipDoc: true,
|
||||||
|
input: inputXMLWithCommentsWithSubChild,
|
||||||
|
expected: expectedDecodeYamlWithSubChild,
|
||||||
|
scenarioType: "decode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Parse xml: with comments array",
|
||||||
|
skipDoc: true,
|
||||||
|
input: inputXMLWithCommentsWithArray,
|
||||||
|
expected: expectedDecodeYamlWithArray,
|
||||||
|
scenarioType: "decode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Parse xml: keep attribute namespace",
|
||||||
|
subdescription: fmt.Sprintf(`Defaults to %v`, ConfiguredXMLPreferences.KeepNamespace),
|
||||||
|
skipDoc: false,
|
||||||
|
input: inputXMLWithNamespacedAttr,
|
||||||
|
expected: expectedYAMLWithNamespacedAttr,
|
||||||
|
scenarioType: "decode-keep-ns",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Parse xml: keep raw attribute namespace",
|
||||||
|
skipDoc: true,
|
||||||
|
input: inputXMLWithNamespacedAttr,
|
||||||
|
expected: expectedYAMLWithRawNamespacedAttr,
|
||||||
|
scenarioType: "decode-raw-token",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Parse xml: keep raw attribute namespace",
|
||||||
|
subdescription: fmt.Sprintf(`Defaults to %v`, ConfiguredXMLPreferences.UseRawToken),
|
||||||
|
skipDoc: false,
|
||||||
|
input: inputXMLWithNamespacedAttr,
|
||||||
|
expected: expectedYAMLWithoutRawNamespacedAttr,
|
||||||
|
scenarioType: "decode-raw-token-off",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Encode xml: simple",
|
||||||
|
input: "cat: purrs",
|
||||||
|
expected: "<cat>purrs</cat>\n",
|
||||||
|
scenarioType: "encode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "includes map tags",
|
||||||
|
skipDoc: true,
|
||||||
|
input: "<cat>purrs</cat>\n",
|
||||||
|
expression: `tag`,
|
||||||
|
expected: "!!map\n",
|
||||||
|
scenarioType: "decode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "includes array tags",
|
||||||
|
skipDoc: true,
|
||||||
|
input: "<cat>purrs</cat><cat>purrs</cat>\n",
|
||||||
|
expression: `.cat | tag`,
|
||||||
|
expected: "!!seq\n",
|
||||||
|
scenarioType: "decode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Encode xml: array",
|
||||||
|
input: "pets:\n cat:\n - purrs\n - meows",
|
||||||
|
expected: "<pets>\n <cat>purrs</cat>\n <cat>meows</cat>\n</pets>\n",
|
||||||
|
scenarioType: "encode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Encode xml: attributes",
|
||||||
|
subdescription: "Fields with the matching xml-attribute-prefix are assumed to be attributes.",
|
||||||
|
input: "cat:\n +@name: tiger\n meows: true\n",
|
||||||
|
expected: "<cat name=\"tiger\">\n <meows>true</meows>\n</cat>\n",
|
||||||
|
scenarioType: "encode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "double prefix",
|
||||||
|
skipDoc: true,
|
||||||
|
input: "cat:\n +@+@name: tiger\n meows: true\n",
|
||||||
|
expected: "<cat +@name=\"tiger\">\n <meows>true</meows>\n</cat>\n",
|
||||||
|
scenarioType: "encode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "arrays cannot be encoded",
|
||||||
|
skipDoc: true,
|
||||||
|
input: "[cat, dog, fish]",
|
||||||
|
expectedError: "cannot encode !!seq to XML - only maps can be encoded",
|
||||||
|
scenarioType: "encode-error",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "arrays cannot be encoded - 2",
|
||||||
|
skipDoc: true,
|
||||||
|
input: "[cat, dog]",
|
||||||
|
expectedError: "cannot encode !!seq to XML - only maps can be encoded",
|
||||||
|
scenarioType: "encode-error",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Encode xml: attributes with content",
|
||||||
|
subdescription: "Fields with the matching xml-content-name is assumed to be content.",
|
||||||
|
input: "cat:\n +@name: tiger\n +content: cool\n",
|
||||||
|
expected: "<cat name=\"tiger\">cool</cat>\n",
|
||||||
|
scenarioType: "encode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "round trip multiline 1",
|
||||||
|
skipDoc: true,
|
||||||
|
input: "<x><!-- cats --></x>\n",
|
||||||
|
expected: "<x><!-- cats --></x>\n",
|
||||||
|
scenarioType: "roundtrip",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "round trip multiline 2",
|
||||||
|
skipDoc: true,
|
||||||
|
input: "<x><!--\n cats\n --></x>\n",
|
||||||
|
expected: "<x><!--\ncats\n--></x>\n",
|
||||||
|
scenarioType: "roundtrip",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "round trip multiline 3",
|
||||||
|
skipDoc: true,
|
||||||
|
input: "<x><!--\n\tcats\n --></x>\n",
|
||||||
|
expected: "<x><!--\n\tcats\n--></x>\n",
|
||||||
|
scenarioType: "roundtrip",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "round trip multiline 4",
|
||||||
|
skipDoc: true,
|
||||||
|
input: "<x><!--\n\tcats\n\tdogs\n--></x>\n",
|
||||||
|
expected: "<x><!--\n\tcats\n\tdogs\n--></x>\n",
|
||||||
|
scenarioType: "roundtrip",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "round trip multiline 5",
|
||||||
|
skipDoc: true, // pity spaces aren't kept atm.
|
||||||
|
input: "<x><!--\ncats\ndogs\n--></x>\n",
|
||||||
|
expected: "<x><!--\ncats\ndogs\n--></x>\n",
|
||||||
|
scenarioType: "roundtrip",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Encode xml: comments",
|
||||||
|
subdescription: "A best attempt is made to copy comments to xml.",
|
||||||
|
input: yamlWithComments,
|
||||||
|
expected: expectedXMLWithComments,
|
||||||
|
scenarioType: "encode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Encode: doctype and xml declaration",
|
||||||
|
subdescription: "Use the special xml names to add/modify proc instructions and directives.",
|
||||||
|
input: yamlWithProcInstAndDirectives,
|
||||||
|
expected: expectedXmlWithProcInstAndDirectives,
|
||||||
|
scenarioType: "encode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Round trip: with comments",
|
||||||
|
subdescription: "A best effort is made, but comment positions and white space are not preserved perfectly.",
|
||||||
|
input: inputXMLWithComments,
|
||||||
|
expected: expectedRoundtripXMLWithComments,
|
||||||
|
scenarioType: "roundtrip",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Roundtrip: with doctype and declaration",
|
||||||
|
subdescription: "yq parses XML proc instructions and directives into nodes.\nUnfortunately the underlying XML parser loses whitespace information.",
|
||||||
|
input: xmlWithProcInstAndDirectives,
|
||||||
|
expected: expectedXmlWithProcInstAndDirectives,
|
||||||
|
scenarioType: "roundtrip",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func testXMLScenario(t *testing.T, s formatScenario) {
|
||||||
|
switch s.scenarioType {
|
||||||
|
case "", "decode":
|
||||||
|
yamlPrefs := ConfiguredYamlPreferences.Copy()
|
||||||
|
yamlPrefs.Indent = 4
|
||||||
|
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewXMLDecoder(ConfiguredXMLPreferences), NewYamlEncoder(yamlPrefs)), s.description)
|
||||||
|
case "encode":
|
||||||
|
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewXMLEncoder(ConfiguredXMLPreferences)), s.description)
|
||||||
|
case "roundtrip":
|
||||||
|
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewXMLDecoder(ConfiguredXMLPreferences), NewXMLEncoder(ConfiguredXMLPreferences)), s.description)
|
||||||
|
case "decode-keep-ns":
|
||||||
|
prefs := NewDefaultXmlPreferences()
|
||||||
|
prefs.KeepNamespace = true
|
||||||
|
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewXMLDecoder(prefs), NewYamlEncoder(ConfiguredYamlPreferences)), s.description)
|
||||||
|
case "decode-raw-token":
|
||||||
|
prefs := NewDefaultXmlPreferences()
|
||||||
|
prefs.UseRawToken = true
|
||||||
|
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewXMLDecoder(prefs), NewYamlEncoder(ConfiguredYamlPreferences)), s.description)
|
||||||
|
case "decode-raw-token-off":
|
||||||
|
prefs := NewDefaultXmlPreferences()
|
||||||
|
prefs.UseRawToken = false
|
||||||
|
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewXMLDecoder(prefs), NewYamlEncoder(ConfiguredYamlPreferences)), s.description)
|
||||||
|
case "roundtrip-skip-directives":
|
||||||
|
prefs := NewDefaultXmlPreferences()
|
||||||
|
prefs.SkipDirectives = true
|
||||||
|
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewXMLDecoder(prefs), NewXMLEncoder(prefs)), s.description)
|
||||||
|
case "decode-error":
|
||||||
|
result, err := processFormatScenario(s, NewXMLDecoder(NewDefaultXmlPreferences()), NewYamlEncoder(ConfiguredYamlPreferences))
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected error '%v' but it worked: %v", s.expectedError, result)
|
||||||
|
} else {
|
||||||
|
test.AssertResultComplexWithContext(t, s.expectedError, err.Error(), s.description)
|
||||||
|
}
|
||||||
|
case "encode-error":
|
||||||
|
result, err := processFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewXMLEncoder(NewDefaultXmlPreferences()))
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected error '%v' but it worked: %v", s.expectedError, result)
|
||||||
|
} else {
|
||||||
|
test.AssertResultComplexWithContext(t, s.expectedError, err.Error(), s.description)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unhandled scenario type %q", s.scenarioType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func documentXMLScenario(_ *testing.T, w *bufio.Writer, i interface{}) {
|
||||||
|
s := i.(formatScenario)
|
||||||
|
|
||||||
|
if s.skipDoc {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch s.scenarioType {
|
||||||
|
case "", "decode":
|
||||||
|
documentXMLDecodeScenario(w, s)
|
||||||
|
case "encode":
|
||||||
|
documentXMLEncodeScenario(w, s)
|
||||||
|
case "roundtrip":
|
||||||
|
documentXMLRoundTripScenario(w, s)
|
||||||
|
case "decode-keep-ns":
|
||||||
|
documentXMLDecodeKeepNsScenario(w, s)
|
||||||
|
case "decode-raw-token-off":
|
||||||
|
documentXMLDecodeKeepNsRawTokenScenario(w, s)
|
||||||
|
case "roundtrip-skip-directives":
|
||||||
|
documentXMLSkipDirectivesScenario(w, s)
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unhandled scenario type %q", s.scenarioType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func documentXMLDecodeScenario(w *bufio.Writer, s formatScenario) {
|
||||||
|
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
|
||||||
|
|
||||||
|
if s.subdescription != "" {
|
||||||
|
writeOrPanic(w, s.subdescription)
|
||||||
|
writeOrPanic(w, "\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
writeOrPanic(w, "Given a sample.xml file of:\n")
|
||||||
|
writeOrPanic(w, fmt.Sprintf("```xml\n%v\n```\n", s.input))
|
||||||
|
|
||||||
|
writeOrPanic(w, "then\n")
|
||||||
|
expression := s.expression
|
||||||
|
if s.expression != "" {
|
||||||
|
expression = fmt.Sprintf(" '%v'", s.expression)
|
||||||
|
}
|
||||||
|
writeOrPanic(w, fmt.Sprintf("```bash\nyq -oy%v sample.xml\n```\n", expression))
|
||||||
|
writeOrPanic(w, "will output\n")
|
||||||
|
|
||||||
|
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", mustProcessFormatScenario(s, NewXMLDecoder(ConfiguredXMLPreferences), NewYamlEncoder(ConfiguredYamlPreferences))))
|
||||||
|
}
|
||||||
|
|
||||||
|
func documentXMLDecodeKeepNsScenario(w *bufio.Writer, s formatScenario) {
|
||||||
|
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
|
||||||
|
|
||||||
|
if s.subdescription != "" {
|
||||||
|
writeOrPanic(w, s.subdescription)
|
||||||
|
writeOrPanic(w, "\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
writeOrPanic(w, "Given a sample.xml file of:\n")
|
||||||
|
writeOrPanic(w, fmt.Sprintf("```xml\n%v\n```\n", s.input))
|
||||||
|
|
||||||
|
writeOrPanic(w, "then\n")
|
||||||
|
writeOrPanic(w, "```bash\nyq --xml-keep-namespace=false sample.xml\n```\n")
|
||||||
|
writeOrPanic(w, "will output\n")
|
||||||
|
prefs := NewDefaultXmlPreferences()
|
||||||
|
prefs.KeepNamespace = false
|
||||||
|
writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", mustProcessFormatScenario(s, NewXMLDecoder(prefs), NewXMLEncoder(prefs))))
|
||||||
|
|
||||||
|
prefsWithout := NewDefaultXmlPreferences()
|
||||||
|
prefs.KeepNamespace = true
|
||||||
|
writeOrPanic(w, "instead of\n")
|
||||||
|
writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", mustProcessFormatScenario(s, NewXMLDecoder(prefsWithout), NewXMLEncoder(prefsWithout))))
|
||||||
|
}
|
||||||
|
|
||||||
|
func documentXMLDecodeKeepNsRawTokenScenario(w *bufio.Writer, s formatScenario) {
|
||||||
|
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
|
||||||
|
|
||||||
|
if s.subdescription != "" {
|
||||||
|
writeOrPanic(w, s.subdescription)
|
||||||
|
writeOrPanic(w, "\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
writeOrPanic(w, "Given a sample.xml file of:\n")
|
||||||
|
writeOrPanic(w, fmt.Sprintf("```xml\n%v\n```\n", s.input))
|
||||||
|
|
||||||
|
writeOrPanic(w, "then\n")
|
||||||
|
writeOrPanic(w, "```bash\nyq --xml-raw-token=false sample.xml\n```\n")
|
||||||
|
writeOrPanic(w, "will output\n")
|
||||||
|
|
||||||
|
prefs := NewDefaultXmlPreferences()
|
||||||
|
prefs.UseRawToken = false
|
||||||
|
|
||||||
|
writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", mustProcessFormatScenario(s, NewXMLDecoder(prefs), NewXMLEncoder(prefs))))
|
||||||
|
|
||||||
|
prefsWithout := NewDefaultXmlPreferences()
|
||||||
|
prefsWithout.UseRawToken = true
|
||||||
|
|
||||||
|
writeOrPanic(w, "instead of\n")
|
||||||
|
writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", mustProcessFormatScenario(s, NewXMLDecoder(prefsWithout), NewXMLEncoder(prefsWithout))))
|
||||||
|
}
|
||||||
|
|
||||||
|
func documentXMLEncodeScenario(w *bufio.Writer, s formatScenario) {
|
||||||
|
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
|
||||||
|
|
||||||
|
if s.subdescription != "" {
|
||||||
|
writeOrPanic(w, s.subdescription)
|
||||||
|
writeOrPanic(w, "\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
writeOrPanic(w, "Given a sample.yml file of:\n")
|
||||||
|
writeOrPanic(w, fmt.Sprintf("```yaml\n%v\n```\n", s.input))
|
||||||
|
|
||||||
|
writeOrPanic(w, "then\n")
|
||||||
|
writeOrPanic(w, "```bash\nyq -o=xml sample.yml\n```\n")
|
||||||
|
writeOrPanic(w, "will output\n")
|
||||||
|
|
||||||
|
writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewXMLEncoder(ConfiguredXMLPreferences))))
|
||||||
|
}
|
||||||
|
|
||||||
|
func documentXMLRoundTripScenario(w *bufio.Writer, s formatScenario) {
|
||||||
|
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
|
||||||
|
|
||||||
|
if s.subdescription != "" {
|
||||||
|
writeOrPanic(w, s.subdescription)
|
||||||
|
writeOrPanic(w, "\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
writeOrPanic(w, "Given a sample.xml file of:\n")
|
||||||
|
writeOrPanic(w, fmt.Sprintf("```xml\n%v\n```\n", s.input))
|
||||||
|
|
||||||
|
writeOrPanic(w, "then\n")
|
||||||
|
writeOrPanic(w, "```bash\nyq sample.xml\n```\n")
|
||||||
|
writeOrPanic(w, "will output\n")
|
||||||
|
|
||||||
|
writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", mustProcessFormatScenario(s, NewXMLDecoder(ConfiguredXMLPreferences), NewXMLEncoder(ConfiguredXMLPreferences))))
|
||||||
|
}
|
||||||
|
|
||||||
|
func documentXMLSkipDirectivesScenario(w *bufio.Writer, s formatScenario) {
|
||||||
|
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
|
||||||
|
|
||||||
|
if s.subdescription != "" {
|
||||||
|
writeOrPanic(w, s.subdescription)
|
||||||
|
writeOrPanic(w, "\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
writeOrPanic(w, "Given a sample.xml file of:\n")
|
||||||
|
writeOrPanic(w, fmt.Sprintf("```xml\n%v\n```\n", s.input))
|
||||||
|
|
||||||
|
writeOrPanic(w, "then\n")
|
||||||
|
writeOrPanic(w, "```bash\nyq --xml-skip-directives sample.xml\n```\n")
|
||||||
|
writeOrPanic(w, "will output\n")
|
||||||
|
prefs := NewDefaultXmlPreferences()
|
||||||
|
prefs.SkipDirectives = true
|
||||||
|
|
||||||
|
writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", mustProcessFormatScenario(s, NewXMLDecoder(prefs), NewXMLEncoder(prefs))))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestXMLScenarios(t *testing.T) {
|
||||||
|
for _, tt := range xmlScenarios {
|
||||||
|
testXMLScenario(t, tt)
|
||||||
|
}
|
||||||
|
genericScenarios := make([]interface{}, len(xmlScenarios))
|
||||||
|
for i, s := range xmlScenarios {
|
||||||
|
genericScenarios[i] = s
|
||||||
|
}
|
||||||
|
documentScenarios(t, "usage", "xml", genericScenarios, documentXMLScenario)
|
||||||
|
}
|
||||||
Reference in new issue
Block a user