2021-12-21 04:02:07 +00:00
package yqlib
import (
"bufio"
"bytes"
"fmt"
"strings"
"testing"
"github.com/mikefarah/yq/v4/test"
yaml "gopkg.in/yaml.v3"
)
2022-01-22 01:35:33 +00:00
func decodeXml ( t * testing . T , s formatScenario ) * CandidateNode {
2021-12-21 04:02:07 +00:00
decoder := NewXmlDecoder ( "+" , "+content" )
2022-01-22 01:35:33 +00:00
decoder . Init ( strings . NewReader ( s . input ) )
2021-12-21 04:02:07 +00:00
node := & yaml . Node { }
err := decoder . Decode ( node )
if err != nil {
2022-01-22 01:35:33 +00:00
t . Error ( err , "fail to decode" , s . input )
2021-12-21 04:02:07 +00:00
}
2022-01-22 01:35:33 +00:00
expression := s . expression
if expression == "" {
expression = "."
}
exp , err := NewExpressionParser ( ) . ParseExpression ( expression )
if err != nil {
t . Error ( err )
return nil
}
candidateNode := CandidateNode { Node : node }
context , err := NewDataTreeNavigator ( ) . GetMatchingNodes ( Context { MatchingNodes : candidateNode . AsList ( ) } , exp )
if err != nil {
t . Error ( err )
return nil
}
return context . MatchingNodes . Front ( ) . Value . ( * CandidateNode )
2021-12-21 04:02:07 +00:00
}
2022-01-15 07:18:52 +00:00
func processXmlScenario ( s formatScenario ) string {
2021-12-21 04:56:08 +00:00
var output bytes . Buffer
writer := bufio . NewWriter ( & output )
2022-01-15 00:57:59 +00:00
var encoder = NewXmlEncoder ( 2 , "+" , "+content" )
var decoder = NewYamlDecoder ( )
if s . scenarioType == "roundtrip" {
decoder = NewXmlDecoder ( "+" , "+content" )
}
inputs , err := readDocuments ( strings . NewReader ( s . input ) , "sample.yml" , 0 , decoder )
2021-12-21 04:56:08 +00:00
if err != nil {
panic ( err )
}
node := inputs . Front ( ) . Value . ( * CandidateNode ) . Node
2022-01-15 00:57:59 +00:00
err = encoder . Encode ( writer , node )
2021-12-21 04:56:08 +00:00
if err != nil {
panic ( err )
}
writer . Flush ( )
2022-01-15 00:57:59 +00:00
return output . String ( )
2021-12-21 04:56:08 +00:00
}
2022-01-15 07:18:52 +00:00
type formatScenario struct {
2021-12-21 04:56:08 +00:00
input string
2022-01-21 09:26:09 +00:00
indent int
expression string
2021-12-21 04:02:07 +00:00
expected string
description string
subdescription string
skipDoc bool
2022-01-15 00:57:59 +00:00
scenarioType string
2021-12-21 04:02:07 +00:00
}
2022-01-15 00:57:59 +00:00
var 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 -- >
`
var 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 -- >
`
var expectedDecodeYamlWithSubChild = ` D0 , P [ ] , ( doc ) : : # 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
`
var 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 -- >
`
var expectedDecodeYamlWithArray = ` D0 , P [ ] , ( doc ) : : # 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
`
var expectedDecodeYamlWithComments = ` D0 , P [ ] , ( doc ) : : # 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
`
var 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 -- >
`
var yamlWithComments = ` # above_cat
cat : # inline_cat
# above_array
array : # inline_array
- val1 # inline_val1
# above_val2
- val2 # inline_val2
# below_cat
`
var expectedXmlWithComments = ` < ! -- above_cat inline_cat -- > < cat > < ! -- above_array inline_array -- >
< array > val1 < ! -- inline_val1 -- > < / array >
< array > < ! -- above_val2 -- > val2 < ! -- inline_val2 -- > < / array >
< / cat > < ! -- below_cat -- >
`
2022-01-15 07:18:52 +00:00
var xmlScenarios = [ ] formatScenario {
2021-12-21 04:02:07 +00:00
{
2022-01-22 01:35:33 +00:00
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 : "D0, P[], (doc)::cat:\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 : "D0, P[], ()::cat:\n says: meow\n legs: 4\n cute: true\n" ,
2021-12-21 04:02:07 +00:00
} ,
{
description : "Parse xml: array" ,
subdescription : "Consecutive nodes with identical xml names are assumed to be arrays." ,
2022-01-22 01:35:33 +00:00
input : "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<animal>cat</animal>\n<animal>goat</animal>" ,
expected : "D0, P[], (doc)::animal:\n - cat\n - goat\n" ,
2021-12-21 04:02:07 +00:00
} ,
{
description : "Parse xml: attributes" ,
2022-01-15 00:57:59 +00:00
subdescription : "Attributes are converted to fields, with the default attribute prefix '+'. Use '--xml-attribute-prefix` to set your own." ,
2021-12-21 04:56:08 +00:00
input : "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<cat legs=\"4\">\n <legs>7</legs>\n</cat>" ,
2021-12-21 04:02:07 +00:00
expected : "D0, P[], (doc)::cat:\n +legs: \"4\"\n legs: \"7\"\n" ,
} ,
{
description : "Parse xml: attributes with content" ,
2022-01-22 01:35:33 +00:00
subdescription : "Content is added as a field, using the default content name of `+content`. Use `--xml-content-name` to set your own." ,
2021-12-21 04:56:08 +00:00
input : "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<cat legs=\"4\">meow</cat>" ,
2021-12-21 04:02:07 +00:00
expected : "D0, P[], (doc)::cat:\n +content: meow\n +legs: \"4\"\n" ,
} ,
2021-12-21 04:56:08 +00:00
{
2022-01-15 00:57:59 +00:00
description : "Parse xml: with comments" ,
subdescription : "A best attempt is made to preserve comments." ,
input : inputXmlWithComments ,
expected : expectedDecodeYamlWithComments ,
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 : "Encode xml: simple" ,
input : "cat: purrs" ,
expected : "<cat>purrs</cat>\n" ,
scenarioType : "encode" ,
2021-12-21 04:56:08 +00:00
} ,
{
2022-01-15 00:57:59 +00:00
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" ,
2021-12-21 04:56:08 +00:00
} ,
2021-12-21 05:08:37 +00:00
{
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" ,
2022-01-15 00:57:59 +00:00
expected : "<cat name=\"tiger\">\n <meows>true</meows>\n</cat>\n" ,
scenarioType : "encode" ,
2021-12-21 05:08:37 +00:00
} ,
{
2022-01-15 00:57:59 +00:00
skipDoc : true ,
input : "cat:\n ++name: tiger\n meows: true\n" ,
expected : "<cat +name=\"tiger\">\n <meows>true</meows>\n</cat>\n" ,
scenarioType : "encode" ,
2021-12-21 05:08:37 +00:00
} ,
2021-12-21 05:19:27 +00:00
{
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" ,
2022-01-15 00:57:59 +00:00
expected : "<cat name=\"tiger\">cool</cat>\n" ,
scenarioType : "encode" ,
} ,
{
description : "Encode xml: comments" ,
subdescription : "A best attempt is made to copy comments to xml." ,
input : yamlWithComments ,
expected : expectedXmlWithComments ,
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" ,
2021-12-21 05:19:27 +00:00
} ,
2021-12-21 04:02:07 +00:00
}
2022-01-15 07:18:52 +00:00
func testXmlScenario ( t * testing . T , s formatScenario ) {
2022-01-15 00:57:59 +00:00
if s . scenarioType == "encode" || s . scenarioType == "roundtrip" {
2022-01-15 07:18:52 +00:00
test . AssertResultWithContext ( t , s . expected , processXmlScenario ( s ) , s . description )
2021-12-21 04:56:08 +00:00
} else {
2022-01-22 01:35:33 +00:00
var actual = resultToString ( t , decodeXml ( t , s ) )
2021-12-21 04:56:08 +00:00
test . AssertResultWithContext ( t , s . expected , actual , s . description )
}
2021-12-21 04:02:07 +00:00
}
func documentXmlScenario ( t * testing . T , w * bufio . Writer , i interface { } ) {
2022-01-15 07:18:52 +00:00
s := i . ( formatScenario )
2021-12-21 04:02:07 +00:00
if s . skipDoc {
return
}
2022-01-15 00:57:59 +00:00
if s . scenarioType == "encode" {
2021-12-21 05:19:27 +00:00
documentXmlEncodeScenario ( w , s )
2022-01-15 00:57:59 +00:00
} else if s . scenarioType == "roundtrip" {
documentXmlRoundTripScenario ( w , s )
2021-12-21 04:56:08 +00:00
} else {
documentXmlDecodeScenario ( t , w , s )
}
}
2022-01-15 07:18:52 +00:00
func documentXmlDecodeScenario ( t * testing . T , w * bufio . Writer , s formatScenario ) {
2021-12-21 04:02:07 +00:00
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" )
2021-12-21 04:56:08 +00:00
writeOrPanic ( w , fmt . Sprintf ( "```xml\n%v\n```\n" , s . input ) )
2021-12-21 04:02:07 +00:00
writeOrPanic ( w , "then\n" )
2022-01-22 01:35:33 +00:00
expression := s . expression
if expression == "" {
expression = "."
}
writeOrPanic ( w , fmt . Sprintf ( "```bash\nyq e -p=xml '%v' sample.xml\n```\n" , expression ) )
2021-12-21 04:02:07 +00:00
writeOrPanic ( w , "will output\n" )
var output bytes . Buffer
2022-01-15 00:57:59 +00:00
printer := NewSimpleYamlPrinter ( bufio . NewWriter ( & output ) , YamlOutputFormat , true , false , 2 , true )
2021-12-21 04:02:07 +00:00
2022-01-22 01:35:33 +00:00
node := decodeXml ( t , s )
2021-12-21 04:02:07 +00:00
err := printer . PrintResults ( node . AsList ( ) )
if err != nil {
t . Error ( err )
return
}
writeOrPanic ( w , fmt . Sprintf ( "```yaml\n%v```\n\n" , output . String ( ) ) )
2021-12-21 04:56:08 +00:00
}
2022-01-15 07:18:52 +00:00
func documentXmlEncodeScenario ( w * bufio . Writer , s formatScenario ) {
2021-12-21 04:56:08 +00:00
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 e -o=xml '.' sample.yml\n```\n" )
writeOrPanic ( w , "will output\n" )
2021-12-21 04:02:07 +00:00
2022-01-15 07:18:52 +00:00
writeOrPanic ( w , fmt . Sprintf ( "```xml\n%v```\n\n" , processXmlScenario ( s ) ) )
2022-01-15 00:57:59 +00:00
}
2022-01-15 07:18:52 +00:00
func documentXmlRoundTripScenario ( w * bufio . Writer , s formatScenario ) {
2022-01-15 00:57:59 +00:00
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 e -p=xml -o=xml '.' sample.xml\n```\n" )
writeOrPanic ( w , "will output\n" )
2022-01-15 07:18:52 +00:00
writeOrPanic ( w , fmt . Sprintf ( "```xml\n%v```\n\n" , processXmlScenario ( s ) ) )
2021-12-21 04:02:07 +00:00
}
func TestXmlScenarios ( t * testing . T ) {
for _ , tt := range xmlScenarios {
2022-01-15 00:57:59 +00:00
testXmlScenario ( t , tt )
2021-12-21 04:02:07 +00:00
}
genericScenarios := make ( [ ] interface { } , len ( xmlScenarios ) )
for i , s := range xmlScenarios {
genericScenarios [ i ] = s
}
documentScenarios ( t , "usage" , "xml" , genericScenarios , documentXmlScenario )
}