diff --git a/pkg/yqlib/doc/usage/xml.md b/pkg/yqlib/doc/usage/xml.md
index f3396539..9b49b961 100644
--- a/pkg/yqlib/doc/usage/xml.md
+++ b/pkg/yqlib/doc/usage/xml.md
@@ -22,6 +22,81 @@ XML nodes that have attributes then plain content, e.g:
The content of the node will be set as a field in the map with the key "+content". Use the `--xml-content-name` flag to change this.
+## Parse xml: simple
+Given a sample.xml file of:
+```xml
+
+meow
+```
+then
+```bash
+yq e -p=xml '.' sample.xml
+```
+will output
+```yaml
+cat: meow
+```
+
+## Parse xml: array
+Consecutive nodes with identical xml names are assumed to be arrays.
+
+Given a sample.xml file of:
+```xml
+
+1
+2
+```
+then
+```bash
+yq e -p=xml '.' sample.xml
+```
+will output
+```yaml
+animal:
+ - "1"
+ - "2"
+```
+
+## Parse xml: attributes
+Attributes are converted to fields, with the attribute prefix.
+
+Given a sample.xml file of:
+```xml
+
+
+ 7
+
+```
+then
+```bash
+yq e -p=xml '.' sample.xml
+```
+will output
+```yaml
+cat:
+ +legs: "4"
+ legs: "7"
+```
+
+## Parse xml: attributes with content
+Content is added as a field, using the content name
+
+Given a sample.xml file of:
+```xml
+
+meow
+```
+then
+```bash
+yq e -p=xml '.' sample.xml
+```
+will output
+```yaml
+cat:
+ +content: meow
+ +legs: "4"
+```
+
## Parse xml: with comments
A best attempt is made to preserve comments.
@@ -69,3 +144,151 @@ cat:
# after cat
```
+## Encode xml: simple
+Given a sample.yml file of:
+```yaml
+cat: purrs
+```
+then
+```bash
+yq e -o=xml '.' sample.yml
+```
+will output
+```xml
+purrs
+```
+
+## Encode xml: array
+Given a sample.yml file of:
+```yaml
+pets:
+ cat:
+ - purrs
+ - meows
+```
+then
+```bash
+yq e -o=xml '.' sample.yml
+```
+will output
+```xml
+
+ purrs
+ meows
+
+```
+
+## Encode xml: attributes
+Fields with the matching xml-attribute-prefix are assumed to be attributes.
+
+Given a sample.yml file of:
+```yaml
+cat:
+ +name: tiger
+ meows: true
+
+```
+then
+```bash
+yq e -o=xml '.' sample.yml
+```
+will output
+```xml
+
+ true
+
+```
+
+## Encode xml: attributes with content
+Fields with the matching xml-content-name is assumed to be content.
+
+Given a sample.yml file of:
+```yaml
+cat:
+ +name: tiger
+ +content: cool
+
+```
+then
+```bash
+yq e -o=xml '.' sample.yml
+```
+will output
+```xml
+cool
+```
+
+## Encode xml: comments
+A best attempt is made to copy comments to xml.
+
+Given a sample.yml file of:
+```yaml
+# above_cat
+cat: # inline_cat
+ # above_array
+ array: # inline_array
+ - val1 # inline_val1
+ # above_val2
+ - val2 # inline_val2
+# below_cat
+
+```
+then
+```bash
+yq e -o=xml '.' sample.yml
+```
+will output
+```xml
+
+ val1
+ val2
+
+```
+
+## Round trip: with comments
+A best effort is made, but comment positions and white space are not preserved perfectly.
+
+Given a sample.xml file of:
+```xml
+
+
+
+
+ 3
+
+
+
+ z
+
+
+
+
+
+
+
+```
+then
+```bash
+yq e -p=xml '.' sample.xml
+```
+will output
+```yaml
+# 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
+
+# after cat
+```
+
diff --git a/pkg/yqlib/encoder_xml.go b/pkg/yqlib/encoder_xml.go
index 254d8f1c..c97c3abb 100644
--- a/pkg/yqlib/encoder_xml.go
+++ b/pkg/yqlib/encoder_xml.go
@@ -154,6 +154,10 @@ func (e *xmlEncoder) doEncode(encoder *xml.Encoder, node *yaml.Node, start xml.S
func (e *xmlEncoder) encodeComment(encoder *xml.Encoder, commentStr string) error {
if commentStr != "" {
log.Debugf("encoding comment %v", commentStr)
+ if !strings.HasSuffix(commentStr, " ") {
+ commentStr = commentStr + " "
+ }
+
var comment xml.Comment = []byte(commentStr)
err := encoder.EncodeToken(comment)
if err != nil {
diff --git a/pkg/yqlib/xml_test.go b/pkg/yqlib/xml_test.go
index e1ed8de1..12fb81bc 100644
--- a/pkg/yqlib/xml_test.go
+++ b/pkg/yqlib/xml_test.go
@@ -133,10 +133,11 @@ cat:
var expectedRoundtripXmlWithComments = `
3
-
- 4
+
+ z
`
@@ -151,36 +152,36 @@ cat: # inline_cat
# below_cat
`
-var expectedXmlWithComments = `
- val1
- val2
-
+var expectedXmlWithComments = `
+ val1
+ val2
+
`
var xmlScenarios = []xmlScenario{
- // {
- // description: "Parse xml: simple",
- // input: "\nmeow",
- // expected: "D0, P[], (doc)::cat: meow\n",
- // },
- // {
- // description: "Parse xml: array",
- // subdescription: "Consecutive nodes with identical xml names are assumed to be arrays.",
- // input: "\n1\n2",
- // expected: "D0, P[], (doc)::animal:\n - \"1\"\n - \"2\"\n",
- // },
- // {
- // description: "Parse xml: attributes",
- // subdescription: "Attributes are converted to fields, with the attribute prefix.",
- // input: "\n\n 7\n",
- // expected: "D0, P[], (doc)::cat:\n +legs: \"4\"\n legs: \"7\"\n",
- // },
- // {
- // description: "Parse xml: attributes with content",
- // subdescription: "Content is added as a field, using the content name",
- // input: "\nmeow",
- // expected: "D0, P[], (doc)::cat:\n +content: meow\n +legs: \"4\"\n",
- // },
+ {
+ description: "Parse xml: simple",
+ input: "\nmeow",
+ expected: "D0, P[], (doc)::cat: meow\n",
+ },
+ {
+ description: "Parse xml: array",
+ subdescription: "Consecutive nodes with identical xml names are assumed to be arrays.",
+ input: "\n1\n2",
+ expected: "D0, P[], (doc)::animal:\n - \"1\"\n - \"2\"\n",
+ },
+ {
+ description: "Parse xml: attributes",
+ subdescription: "Attributes are converted to fields, with the attribute prefix.",
+ input: "\n\n 7\n",
+ expected: "D0, P[], (doc)::cat:\n +legs: \"4\"\n legs: \"7\"\n",
+ },
+ {
+ description: "Parse xml: attributes with content",
+ subdescription: "Content is added as a field, using the content name",
+ input: "\nmeow",
+ expected: "D0, P[], (doc)::cat:\n +content: meow\n +legs: \"4\"\n",
+ },
{
description: "Parse xml: with comments",
subdescription: "A best attempt is made to preserve comments.",
@@ -188,52 +189,52 @@ var xmlScenarios = []xmlScenario{
expected: expectedDecodeYamlWithComments,
scenarioType: "decode",
},
- // {
- // description: "Encode xml: simple",
- // input: "cat: purrs",
- // expected: "purrs\n",
- // scenarioType: "encode",
- // },
- // {
- // description: "Encode xml: array",
- // input: "pets:\n cat:\n - purrs\n - meows",
- // expected: "\n purrs\n meows\n\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: "\n true\n\n",
- // scenarioType: "encode",
- // },
- // {
- // skipDoc: true,
- // input: "cat:\n ++name: tiger\n meows: true\n",
- // expected: "\n true\n\n",
- // scenarioType: "encode",
- // },
- // {
- // 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: "cool\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",
- // },
+ {
+ description: "Encode xml: simple",
+ input: "cat: purrs",
+ expected: "purrs\n",
+ scenarioType: "encode",
+ },
+ {
+ description: "Encode xml: array",
+ input: "pets:\n cat:\n - purrs\n - meows",
+ expected: "\n purrs\n meows\n\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: "\n true\n\n",
+ scenarioType: "encode",
+ },
+ {
+ skipDoc: true,
+ input: "cat:\n ++name: tiger\n meows: true\n",
+ expected: "\n true\n\n",
+ scenarioType: "encode",
+ },
+ {
+ 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: "cool\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",
+ },
}
func testXmlScenario(t *testing.T, s xmlScenario) {