diff --git a/examples/data1.yaml b/examples/data1.yaml
index 3ef8ec68..bfea295a 100644
--- a/examples/data1.yaml
+++ b/examples/data1.yaml
@@ -1,15 +1,8 @@
-strings:
- - a: banana
- - a: cat
- - a: apple
-
-numbers:
- - a: 12
- - a: 13
- - a: 120
-
-
-obj:
- - a: {cat: "adog"}
- - a: {cat: "doga"}
- - a: apple
+# above_cat
+cat: # inline_cat
+ # above_array
+ array: # inline_array
+ - 3 # inline_3
+ # above_4
+ - 4 # inline_4
+# below_cat
\ No newline at end of file
diff --git a/pkg/yqlib/doc/usage/xml.md b/pkg/yqlib/doc/usage/xml.md
index 17c19871..3edc6758 100644
--- a/pkg/yqlib/doc/usage/xml.md
+++ b/pkg/yqlib/doc/usage/xml.md
@@ -108,7 +108,8 @@ yq e -o=xml '.' sample.yml
```
will output
```xml
-purrs```
+purrs
+```
## Encode xml: array
Given a sample.yml file of:
@@ -127,7 +128,8 @@ will output
purrs
meows
-```
+
+```
## Encode xml: attributes
Fields with the matching xml-attribute-prefix are assumed to be attributes.
@@ -147,7 +149,8 @@ will output
```xml
true
-```
+
+```
## Encode xml: attributes with content
Fields with the matching xml-content-name is assumed to be content.
@@ -165,5 +168,33 @@ yq e -o=xml '.' sample.yml
```
will output
```xml
-cool```
+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
+
+```
diff --git a/pkg/yqlib/encoder_properties.go b/pkg/yqlib/encoder_properties.go
index 94de0cb5..8cae7ee1 100644
--- a/pkg/yqlib/encoder_properties.go
+++ b/pkg/yqlib/encoder_properties.go
@@ -3,7 +3,6 @@ package yqlib
import (
"fmt"
"io"
- "strings"
"github.com/magiconair/properties"
yaml "gopkg.in/yaml.v3"
@@ -30,9 +29,7 @@ func (pe *propertiesEncoder) Encode(node *yaml.Node) error {
}
func (pe *propertiesEncoder) doEncode(p *properties.Properties, node *yaml.Node, path string) error {
- p.SetComment(path,
- strings.Replace(node.HeadComment, "#", "", 1)+
- strings.Replace(node.LineComment, "#", "", 1))
+ p.SetComment(path, headAndLineComment(node))
switch node.Kind {
case yaml.ScalarNode:
_, _, err := p.Set(path, node.Value)
diff --git a/pkg/yqlib/encoder_xml.go b/pkg/yqlib/encoder_xml.go
index f1cac841..255a1279 100644
--- a/pkg/yqlib/encoder_xml.go
+++ b/pkg/yqlib/encoder_xml.go
@@ -32,14 +32,20 @@ func (e *xmlEncoder) Encode(node *yaml.Node) error {
if err != nil {
return err
}
- var charData xml.CharData = []byte("\n")
- err = e.xmlEncoder.EncodeToken(charData)
+ case yaml.DocumentNode:
+ err := e.encodeComment(headAndLineComment(node))
+ if err != nil {
+ return err
+ }
+
+ err = e.Encode(unwrapDoc(node))
+ if err != nil {
+ return err
+ }
+ err = e.encodeComment(footComment(node))
if err != nil {
return err
}
- return e.xmlEncoder.Flush()
- case yaml.DocumentNode:
- return e.Encode(unwrapDoc(node))
case yaml.ScalarNode:
var charData xml.CharData = []byte(node.Value)
err := e.xmlEncoder.EncodeToken(charData)
@@ -47,8 +53,12 @@ func (e *xmlEncoder) Encode(node *yaml.Node) error {
return err
}
return e.xmlEncoder.Flush()
+ default:
+ return fmt.Errorf("unsupported type %v", node.Tag)
}
- return fmt.Errorf("unsupported type %v", node.Tag)
+ var charData xml.CharData = []byte("\n")
+ return e.xmlEncoder.EncodeToken(charData)
+
}
func (e *xmlEncoder) encodeTopLevelMap(node *yaml.Node) error {
@@ -57,7 +67,16 @@ func (e *xmlEncoder) encodeTopLevelMap(node *yaml.Node) error {
value := node.Content[i+1]
start := xml.StartElement{Name: xml.Name{Local: key.Value}}
- err := e.doEncode(value, start)
+ err := e.encodeComment(headAndLineComment(key))
+ if err != nil {
+ return err
+ }
+
+ err = e.doEncode(value, start)
+ if err != nil {
+ return err
+ }
+ err = e.encodeComment(footComment(key))
if err != nil {
return err
}
@@ -65,6 +84,22 @@ func (e *xmlEncoder) encodeTopLevelMap(node *yaml.Node) error {
return nil
}
+func (e *xmlEncoder) encodeStart(node *yaml.Node, start xml.StartElement) error {
+ err := e.xmlEncoder.EncodeToken(start)
+ if err != nil {
+ return err
+ }
+ return e.encodeComment(headAndLineComment(node))
+}
+
+func (e *xmlEncoder) encodeEnd(node *yaml.Node, start xml.StartElement) error {
+ err := e.xmlEncoder.EncodeToken(start.End())
+ if err != nil {
+ return err
+ }
+ return e.encodeComment(footComment(node))
+}
+
func (e *xmlEncoder) doEncode(node *yaml.Node, start xml.StartElement) error {
switch node.Kind {
case yaml.MappingNode:
@@ -72,22 +107,33 @@ func (e *xmlEncoder) doEncode(node *yaml.Node, start xml.StartElement) error {
case yaml.SequenceNode:
return e.encodeArray(node, start)
case yaml.ScalarNode:
- err := e.xmlEncoder.EncodeToken(start)
+ err := e.encodeStart(node, start)
if err != nil {
return err
}
var charData xml.CharData = []byte(node.Value)
err = e.xmlEncoder.EncodeToken(charData)
-
if err != nil {
return err
}
- return e.xmlEncoder.EncodeToken(start.End())
+
+ return e.encodeEnd(node, start)
}
return fmt.Errorf("unsupported type %v", node.Tag)
}
+func (e *xmlEncoder) encodeComment(commentStr string) error {
+ if commentStr != "" {
+ var comment xml.Comment = []byte(commentStr)
+ err := e.xmlEncoder.EncodeToken(comment)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
func (e *xmlEncoder) encodeArray(node *yaml.Node, start xml.StartElement) error {
for i := 0; i < len(node.Content); i++ {
value := node.Content[i]
@@ -116,7 +162,7 @@ func (e *xmlEncoder) encodeMap(node *yaml.Node, start xml.StartElement) error {
}
}
- err := e.xmlEncoder.EncodeToken(start)
+ err := e.encodeStart(node, start)
if err != nil {
return err
}
@@ -126,6 +172,11 @@ func (e *xmlEncoder) encodeMap(node *yaml.Node, start xml.StartElement) error {
key := node.Content[i]
value := node.Content[i+1]
+ err := e.encodeComment(headAndLineComment(key))
+ if err != nil {
+ return err
+ }
+
if !strings.HasPrefix(key.Value, e.attributePrefix) && key.Value != e.contentName {
start := xml.StartElement{Name: xml.Name{Local: key.Value}}
err := e.doEncode(value, start)
@@ -140,7 +191,11 @@ func (e *xmlEncoder) encodeMap(node *yaml.Node, start xml.StartElement) error {
return err
}
}
+ err = e.encodeComment(footComment(key))
+ if err != nil {
+ return err
+ }
}
- return e.xmlEncoder.EncodeToken(start.End())
+ return e.encodeEnd(node, start)
}
diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go
index 9524dddc..e1fb18b6 100644
--- a/pkg/yqlib/lib.go
+++ b/pkg/yqlib/lib.go
@@ -236,6 +236,15 @@ func createScalarNode(value interface{}, stringValue string) *yaml.Node {
return node
}
+func headAndLineComment(node *yaml.Node) string {
+ return strings.Replace(node.HeadComment, "#", "", 1) +
+ strings.Replace(node.LineComment, "#", "", 1)
+}
+
+func footComment(node *yaml.Node) string {
+ return strings.Replace(node.FootComment, "#", "", 1)
+}
+
func createValueOperation(value interface{}, stringValue string) *Operation {
var node *yaml.Node = createScalarNode(value, stringValue)
diff --git a/pkg/yqlib/xml_test.go b/pkg/yqlib/xml_test.go
index eb0c2c72..2eeecf06 100644
--- a/pkg/yqlib/xml_test.go
+++ b/pkg/yqlib/xml_test.go
@@ -52,6 +52,22 @@ type xmlScenario struct {
encodeScenario bool
}
+var yamlWithComments = `# above_cat
+cat: # inline_cat
+ # above_array
+ array: # inline_array
+ - val1 # inline_val1
+ # above_val2
+ - val2 # inline_val2
+# below_cat
+`
+
+var expectedXmlWithComments = `
+ val1
+ val2
+
+`
+
var xmlScenarios = []xmlScenario{
{
description: "Parse xml: simple",
@@ -79,33 +95,40 @@ var xmlScenarios = []xmlScenario{
{
description: "Encode xml: simple",
input: "cat: purrs",
- expected: "purrs",
+ expected: "purrs\n",
encodeScenario: true,
},
{
description: "Encode xml: array",
input: "pets:\n cat:\n - purrs\n - meows",
- expected: "\n purrs\n meows\n",
+ expected: "\n purrs\n meows\n\n",
encodeScenario: true,
},
{
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",
+ expected: "\n true\n\n",
encodeScenario: true,
},
{
skipDoc: true,
input: "cat:\n ++name: tiger\n meows: true\n",
- expected: "\n true\n",
+ expected: "\n true\n\n",
encodeScenario: true,
},
{
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",
+ expected: "cool\n",
+ encodeScenario: true,
+ },
+ {
+ description: "Encode xml: comments",
+ subdescription: "A best attempt is made to copy comments to xml.",
+ input: yamlWithComments,
+ expected: expectedXmlWithComments,
encodeScenario: true,
},
}